MDL-49329 admin: Convert install plugins tool to use new APIs
authorDavid Mudrák <david@moodle.com>
Thu, 8 Oct 2015 10:51:27 +0000 (12:51 +0200)
committerDavid Mudrák <david@moodle.com>
Fri, 9 Oct 2015 07:50:46 +0000 (09:50 +0200)
Most of the functionality provided by this tool (typically the
validation and actual deployment of the plugin package) has been moved
to the core level. So this is becoming just a thin wrapper and user
interface for installing new plugins via the administration UI.

Also fixes MDL-49600 as we no longer keep the unzipped contents of the
packages in the persistent temp directories.

12 files changed:
admin/tool/installaddon/classes/installer.php
admin/tool/installaddon/classes/installfromzip_form.php
admin/tool/installaddon/deploy.php [deleted file]
admin/tool/installaddon/index.php
admin/tool/installaddon/lang/en/tool_installaddon.php
admin/tool/installaddon/permcheck.php
admin/tool/installaddon/renderer.php
admin/tool/installaddon/settings.php
admin/tool/installaddon/styles.css
admin/tool/installaddon/tests/fixtures/testable_installer.php [new file with mode: 0644]
admin/tool/installaddon/tests/installer_test.php
admin/tool/installaddon/validate.php [deleted file]

index 6b4061d..07e1fe4 100644 (file)
@@ -16,7 +16,7 @@
 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 
 /**
- * Provides tool_installaddon_installer related classes
+ * Provides tool_installaddon_installer class.
  *
  * @package     tool_installaddon
  * @subpackage  classes
@@ -103,69 +103,16 @@ class tool_installaddon_installer {
     }
 
     /**
-     * Saves the ZIP file from the {@link tool_installaddon_installfromzip_form} form
+     * Makes a unique writable storage for uploaded ZIP packages.
      *
-     * The file is saved into the given temporary location for inspection and eventual
-     * deployment. The form is expected to be submitted and validated.
+     * We need the saved ZIP to survive across multiple requests so that it can
+     * be used by the plugin manager after the installation is confirmed. In
+     * other words, we cannot use make_request_directory() here.
      *
-     * @param tool_installaddon_installfromzip_form $form
-     * @param string $targetdir full path to the directory where the ZIP should be stored to
-     * @return string filename of the saved file relative to the given target
+     * @return string full path to the directory
      */
-    public function save_installfromzip_file(tool_installaddon_installfromzip_form $form, $targetdir) {
-
-        $filename = clean_param($form->get_new_filename('zipfile'), PARAM_FILE);
-        $form->save_file('zipfile', $targetdir.'/'.$filename);
-
-        return $filename;
-    }
-
-    /**
-     * Extracts the saved file previously saved by {self::save_installfromzip_file()}
-     *
-     * The list of files found in the ZIP is returned via $zipcontentfiles parameter
-     * by reference. The format of that list is array of (string)filerelpath => (bool|string)
-     * where the array value is either true or a string describing the problematic file.
-     *
-     * @see zip_packer::extract_to_pathname()
-     * @param string $zipfilepath full path to the saved ZIP file
-     * @param string $targetdir full path to the directory to extract the ZIP file to
-     * @param string $rootdir explicitly rename the root directory of the ZIP into this non-empty value
-     * @param array list of extracted files as returned by {@link zip_packer::extract_to_pathname()}
-     */
-    public function extract_installfromzip_file($zipfilepath, $targetdir, $rootdir = '') {
-        global $CFG;
-        require_once($CFG->libdir.'/filelib.php');
-
-        $fp = get_file_packer('application/zip');
-        $files = $fp->extract_to_pathname($zipfilepath, $targetdir);
-
-        if (!$files) {
-            return array();
-        }
-
-        if (!empty($rootdir)) {
-            $files = $this->rename_extracted_rootdir($targetdir, $rootdir, $files);
-        }
-
-        // Sometimes zip may not contain all parent directories, add them to make it consistent.
-        foreach ($files as $path => $status) {
-            if ($status !== true) {
-                continue;
-            }
-            $parts = explode('/', trim($path, '/'));
-            while (array_pop($parts)) {
-                if (empty($parts)) {
-                    break;
-                }
-                $dir = implode('/', $parts).'/';
-                if (!isset($files[$dir])) {
-                    $files[$dir] = true;
-                }
-            }
-        }
-
-        return $files;
+    public function make_installfromzip_storage() {
+        return make_unique_writable_directory(make_temp_directory('tool_installaddon'));
     }
 
     /**
@@ -186,57 +133,6 @@ class tool_installaddon_installer {
         return $menu;
     }
 
-    /**
-     * Returns the full path of the root of the given plugin type
-     *
-     * Null is returned if the plugin type is not known. False is returned if the plugin type
-     * root is expected but not found. Otherwise, string is returned.
-     *
-     * @param string $plugintype
-     * @return string|bool|null
-     */
-    public function get_plugintype_root($plugintype) {
-
-        $plugintypepath = null;
-        foreach (core_component::get_plugin_types() as $type => $fullpath) {
-            if ($type === $plugintype) {
-                $plugintypepath = $fullpath;
-                break;
-            }
-        }
-        if (is_null($plugintypepath)) {
-            return null;
-        }
-
-        if (!is_dir($plugintypepath)) {
-            return false;
-        }
-
-        return $plugintypepath;
-    }
-
-    /**
-     * Is it possible to create a new plugin directory for the given plugin type?
-     *
-     * @throws coding_exception for invalid plugin types or non-existing plugin type locations
-     * @param string $plugintype
-     * @return boolean
-     */
-    public function is_plugintype_writable($plugintype) {
-
-        $plugintypepath = $this->get_plugintype_root($plugintype);
-
-        if (is_null($plugintypepath)) {
-            throw new coding_exception('Unknown plugin type!');
-        }
-
-        if ($plugintypepath === false) {
-            throw new coding_exception('Plugin type location does not exist!');
-        }
-
-        return is_writable($plugintypepath);
-    }
-
     /**
      * Hook method to handle the remote request to install an add-on
      *
@@ -245,13 +141,12 @@ class tool_installaddon_installer {
      * it.
      *
      * This hook is called early from admin/tool/installaddon/index.php page so that
-     * it has opportunity to take over the UI.
+     * it has opportunity to take over the UI and display the first confirmation screen.
      *
      * @param tool_installaddon_renderer $output
      * @param string|null $request
-     * @param bool $confirmed
      */
-    public function handle_remote_request(tool_installaddon_renderer $output, $request, $confirmed = false) {
+    public function handle_remote_request(tool_installaddon_renderer $output, $request) {
 
         if (is_null($request)) {
             return;
@@ -265,192 +160,34 @@ class tool_installaddon_installer {
         }
 
         list($plugintype, $pluginname) = core_component::normalize_component($data->component);
+        $pluginman = core_plugin_manager::instance();
 
-        $plugintypepath = $this->get_plugintype_root($plugintype);
+        $plugintypepath = $pluginman->get_plugintype_root($plugintype);
 
         if (file_exists($plugintypepath.'/'.$pluginname)) {
             echo $output->remote_request_alreadyinstalled_page($data, $this->index_url());
             exit();
         }
 
-        if (!$this->is_plugintype_writable($plugintype)) {
+        if (!$pluginman->is_plugintype_writable($plugintype)) {
             $continueurl = $this->index_url(array('installaddonrequest' => $request));
             echo $output->remote_request_permcheck_page($data, $plugintypepath, $continueurl, $this->index_url());
             exit();
         }
 
-        $continueurl = $this->index_url(array(
-            'installaddonrequest' => $request,
-            'confirm' => 1,
-            'sesskey' => sesskey()));
-
-        if (!$confirmed) {
-            echo $output->remote_request_confirm_page($data, $continueurl, $this->index_url());
+        if (!$pluginman->is_remote_plugin_installable($data->component, $data->version, $reason)) {
+            $data->reason = $reason;
+            echo $output->remote_request_non_installable_page($data, $this->index_url());
             exit();
         }
 
-        // The admin has confirmed their intention to install the add-on.
-        require_sesskey();
-
-        // Fetch the plugin info. The essential information is the URL to download the ZIP
-        // and the MD5 hash of the ZIP, obtained via HTTPS.
-        $client = \core\update\api::client();
-        $pluginfo = $client->get_plugin_info($data->component, $data->version);
-
-        if (empty($pluginfo) or empty($pluginfo->version)) {
-            echo $output->remote_request_pluginfo_failure($data, $this->index_url());
-            exit();
-        }
-
-        // Fetch the ZIP with the plugin version
-        $jobid = md5(rand().uniqid('', true));
-        $sourcedir = make_temp_directory('tool_installaddon/'.$jobid.'/source');
-        $zipfilename = 'downloaded.zip';
-
-        try {
-            $this->download_file($pluginfo->version->downloadurl, $sourcedir.'/'.$zipfilename);
-
-        } catch (tool_installaddon_installer_exception $e) {
-            if (debugging()) {
-                throw $e;
-            } else {
-                echo $output->installer_exception($e, $this->index_url());
-                exit();
-            }
-        }
-
-        // Check the MD5 checksum
-        $md5expected = $pluginfo->version->downloadmd5;
-        $md5actual = md5_file($sourcedir.'/'.$zipfilename);
-        if ($md5expected !== $md5actual) {
-            $e = new tool_installaddon_installer_exception('err_zip_md5', array('expected' => $md5expected, 'actual' => $md5actual));
-            if (debugging()) {
-                throw $e;
-            } else {
-                echo $output->installer_exception($e, $this->index_url());
-                exit();
-            }
-        }
-
-        // Redirect to the validation page.
-        $nexturl = new moodle_url('/admin/tool/installaddon/validate.php', array(
-            'sesskey' => sesskey(),
-            'jobid' => $jobid,
-            'zip' => $zipfilename,
-            'type' => $plugintype));
-        redirect($nexturl);
-    }
-
-    /**
-     * Download the given file into the given destination.
-     *
-     * This is basically a simplified version of {@link download_file_content()} from
-     * Moodle itself, tuned for fetching files from moodle.org servers. Same code is used
-     * in mdeploy.php for fetching available updates.
-     *
-     * TODO This all will be rewritten to use new plugin manager features.
-     *
-     * @param string $source file url starting with http(s)://
-     * @param string $target store the downloaded content to this file (full path)
-     * @throws tool_installaddon_installer_exception
-     */
-    public function download_file($source, $target) {
-        global $CFG;
-        require_once($CFG->libdir.'/filelib.php');
-
-        $targetfile = fopen($target, 'w');
-
-        if (!$targetfile) {
-            throw new tool_installaddon_installer_exception('err_download_write_file', $target);
-        }
-
-        $options = array(
-            'file' => $targetfile,
-            'timeout' => 300,
-            'followlocation' => true,
-            'maxredirs' => 3,
-            'ssl_verifypeer' => true,
-            'ssl_verifyhost' => 2,
-        );
-
-        $curl = new curl(array('proxy' => true));
-
-        $result = $curl->download_one($source, null, $options);
-
-        $curlinfo = $curl->get_info();
-
-        fclose($targetfile);
-
-        if ($result !== true) {
-            throw new tool_installaddon_installer_exception('err_curl_exec', array(
-                'url' => $source, 'errorno' => $curl->get_errno(), 'error' => $result));
-
-        } else if (empty($curlinfo['http_code']) or $curlinfo['http_code'] != 200) {
-            throw new tool_installaddon_installer_exception('err_curl_http_code', array(
-                'url' => $source, 'http_code' => $curlinfo['http_code']));
-
-        } else if (isset($curlinfo['ssl_verify_result']) and $curlinfo['ssl_verify_result'] != 0) {
-            throw new tool_installaddon_installer_exception('err_curl_ssl_verify', array(
-                'url' => $source, 'ssl_verify_result' => $curlinfo['ssl_verify_result']));
-        }
-    }
-
-    /**
-     * Moves the given source into a new location recursively
-     *
-     * This is cross-device safe implementation to be used instead of the native rename() function.
-     * See https://bugs.php.net/bug.php?id=54097 for more details.
-     *
-     * @param string $source full path to the existing directory
-     * @param string $target full path to the new location of the directory
-     * @param int $dirpermissions
-     * @param int $filepermissions
-     */
-    public function move_directory($source, $target, $dirpermissions, $filepermissions) {
-
-        if (file_exists($target)) {
-            throw new tool_installaddon_installer_exception('err_folder_already_exists', array('path' => $target));
-        }
-
-        if (is_dir($source)) {
-            $handle = opendir($source);
-        } else {
-            throw new tool_installaddon_installer_exception('err_no_such_folder', array('path' => $source));
-        }
-
-        if (!file_exists($target)) {
-            // Do not use make_writable_directory() here - it is intended for dataroot only.
-            mkdir($target, true);
-            @chmod($target, $dirpermissions);
-        }
-
-        if (!is_writable($target)) {
-            closedir($handle);
-            throw new tool_installaddon_installer_exception('err_folder_not_writable', array('path' => $target));
-        }
-
-        while ($filename = readdir($handle)) {
-            $sourcepath = $source.'/'.$filename;
-            $targetpath = $target.'/'.$filename;
-
-            if ($filename === '.' or $filename === '..') {
-                continue;
-            }
-
-            if (is_dir($sourcepath)) {
-                $this->move_directory($sourcepath, $targetpath, $dirpermissions, $filepermissions);
-
-            } else {
-                rename($sourcepath, $targetpath);
-                @chmod($targetpath, $filepermissions);
-            }
-        }
-
-        closedir($handle);
-
-        rmdir($source);
+        $continueurl = $this->index_url(array(
+            'installremote' => $data->component,
+            'installremoteversion' => $data->version
+        ));
 
-        clearstatcache();
+        echo $output->remote_request_confirm_page($data, $continueurl, $this->index_url());
+        exit();
     }
 
     /**
@@ -460,11 +197,11 @@ class tool_installaddon_installer {
      * are supported.
      *
      * @param string $zipfilepath full path to the saved ZIP file
-     * @param string $workdir full path to the directory we can use for extracting required bits from the archive
      * @return string|bool declared component name or false if unable to detect
      */
-    public function detect_plugin_component($zipfilepath, $workdir) {
+    public function detect_plugin_component($zipfilepath) {
 
+        $workdir = make_request_directory();
         $versionphp = $this->extract_versionphp_file($zipfilepath, $workdir);
 
         if (empty($versionphp)) {
@@ -533,58 +270,6 @@ class tool_installaddon_installer {
         return true;
     }
 
-    /**
-     * Renames the root directory of the extracted ZIP package.
-     *
-     * This method does not validate the presence of the single root directory
-     * (the validator does it later). It just searches for the first directory
-     * under the given location and renames it.
-     *
-     * The method will not rename the root if the requested location already
-     * exists.
-     *
-     * @param string $dirname the location of the extracted ZIP package
-     * @param string $rootdir the requested name of the root directory
-     * @param array $files list of extracted files
-     * @return array eventually amended list of extracted files
-     */
-    protected function rename_extracted_rootdir($dirname, $rootdir, array $files) {
-
-        if (!is_dir($dirname)) {
-            debugging('Unable to rename rootdir of non-existing content', DEBUG_DEVELOPER);
-            return $files;
-        }
-
-        if (file_exists($dirname.'/'.$rootdir)) {
-            debugging('Unable to rename rootdir to already existing folder', DEBUG_DEVELOPER);
-            return $files;
-        }
-
-        $found = null; // The name of the first subdirectory under the $dirname.
-        foreach (scandir($dirname) as $item) {
-            if (substr($item, 0, 1) === '.') {
-                continue;
-            }
-            if (is_dir($dirname.'/'.$item)) {
-                $found = $item;
-                break;
-            }
-        }
-
-        if (!is_null($found)) {
-            if (rename($dirname.'/'.$found, $dirname.'/'.$rootdir)) {
-                $newfiles = array();
-                foreach ($files as $filepath => $status) {
-                    $newpath = preg_replace('~^'.preg_quote($found.'/').'~', preg_quote($rootdir.'/'), $filepath);
-                    $newfiles[$newpath] = $status;
-                }
-                return $newfiles;
-            }
-        }
-
-        return $files;
-    }
-
     /**
      * Decode the request from the Moodle Plugins directory
      *
@@ -722,21 +407,3 @@ class tool_installaddon_installer {
         return false;
     }
 }
-
-
-/**
- * General exception thrown by {@link tool_installaddon_installer} class
- *
- * @copyright 2013 David Mudrak <david@moodle.com>
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class tool_installaddon_installer_exception extends moodle_exception {
-
-    /**
-     * @param string $errorcode exception description identifier
-     * @param mixed $debuginfo debugging data to display
-     */
-    public function __construct($errorcode, $a=null, $debuginfo=null) {
-        parent::__construct($errorcode, 'tool_installaddon', '', $a, print_r($debuginfo, true));
-    }
-}
index fc7c840..7aca466 100644 (file)
@@ -86,6 +86,21 @@ class tool_installaddon_installfromzip_form extends moodleform {
         $mform->insertElementBefore($typedetectionfailed, 'permcheck');
     }
 
+    /**
+     * Warn that the selected plugin type does not match the detected one.
+     *
+     * @param string $detected detected plugin type
+     */
+    public function selected_plugintype_mismatch($detected) {
+
+        $mform = $this->_form;
+        $mform->addRule('plugintype', get_string('required'), 'required', null, 'client');
+        $mform->setAdvanced('plugintype', false);
+        $mform->setAdvanced('permcheck', false);
+        $mform->insertElementBefore($mform->createElement('static', 'selectedplugintypemismatch', '',
+            html_writer::span(get_string('typedetectionmismatch', 'tool_installaddon', $detected), 'error')), 'permcheck');
+    }
+
     /**
      * Validate the form fields
      *
@@ -95,12 +110,12 @@ class tool_installaddon_installfromzip_form extends moodleform {
      */
     public function validation($data, $files) {
 
-        $installer = $this->_customdata['installer'];
+        $pluginman = core_plugin_manager::instance();
         $errors = parent::validation($data, $files);
 
         if (!empty($data['plugintype'])) {
-            if (!$installer->is_plugintype_writable($data['plugintype'])) {
-                $path = $installer->get_plugintype_root($data['plugintype']);
+            if (!$pluginman->is_plugintype_writable($data['plugintype'])) {
+                $path = $pluginman->get_plugintype_root($data['plugintype']);
                 $errors['plugintype'] = get_string('permcheckresultno', 'tool_installaddon', array('path' => $path));
             }
         }
diff --git a/admin/tool/installaddon/deploy.php b/admin/tool/installaddon/deploy.php
deleted file mode 100644 (file)
index b80af83..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-<?php
-
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * Deploy the validated contents of the ZIP package to the $CFG->dirroot
- *
- * @package     tool_installaddon
- * @copyright   2013 David Mudrak <david@moodle.com>
- * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-require(dirname(__FILE__) . '/../../../config.php');
-require_once($CFG->libdir.'/filelib.php');
-
-require_login();
-require_capability('moodle/site:config', context_system::instance());
-
-if (!empty($CFG->disableonclickaddoninstall)) {
-    notice(get_string('featuredisabled', 'tool_installaddon'));
-}
-
-require_sesskey();
-
-$jobid = required_param('jobid', PARAM_ALPHANUM);
-$plugintype = required_param('type', PARAM_ALPHANUMEXT);
-$pluginname = required_param('name', PARAM_PLUGIN);
-
-$zipcontentpath = $CFG->tempdir.'/tool_installaddon/'.$jobid.'/contents';
-
-if (!is_dir($zipcontentpath)) {
-    debugging('Invalid location of the extracted ZIP package: '.s($zipcontentpath), DEBUG_DEVELOPER);
-    redirect(new moodle_url('/admin/tool/installaddon/index.php'),
-        get_string('invaliddata', 'core_error'));
-}
-
-if (!is_dir($zipcontentpath.'/'.$pluginname)) {
-    debugging('Invalid location of the plugin root directory: '.$zipcontentpath.'/'.$pluginname, DEBUG_DEVELOPER);
-    redirect(new moodle_url('/admin/tool/installaddon/index.php'),
-        get_string('invaliddata', 'core_error'));
-}
-
-$installer = tool_installaddon_installer::instance();
-
-if (!$installer->is_plugintype_writable($plugintype)) {
-    debugging('Plugin type location not writable', DEBUG_DEVELOPER);
-    redirect(new moodle_url('/admin/tool/installaddon/index.php'),
-        get_string('invaliddata', 'core_error'));
-}
-
-$plugintypepath = $installer->get_plugintype_root($plugintype);
-
-if (file_exists($plugintypepath.'/'.$pluginname)) {
-    debugging('Target location already exists', DEBUG_DEVELOPER);
-    redirect(new moodle_url('/admin/tool/installaddon/index.php'),
-        get_string('invaliddata', 'core_error'));
-}
-
-// Copy permissions form the plugin type directory.
-$dirpermissions = fileperms($plugintypepath);
-$filepermissions = ($dirpermissions & 0666); // Strip execute flags.
-
-$installer->move_directory($zipcontentpath.'/'.$pluginname, $plugintypepath.'/'.$pluginname, $dirpermissions, $filepermissions);
-fulldelete($CFG->tempdir.'/tool_installaddon/'.$jobid);
-redirect(new moodle_url('/admin'));
index e493296..7bbe560 100644 (file)
@@ -32,6 +32,7 @@ if (!empty($CFG->disableonclickaddoninstall)) {
     notice(get_string('featuredisabled', 'tool_installaddon'));
 }
 
+$pluginman = core_plugin_manager::instance();
 $installer = tool_installaddon_installer::instance();
 
 $output = $PAGE->get_renderer('tool_installaddon');
@@ -39,8 +40,55 @@ $output->set_installer_instance($installer);
 
 // Handle the eventual request for installing from remote repository.
 $remoterequest = optional_param('installaddonrequest', null, PARAM_RAW);
-$confirmed = optional_param('confirm', false, PARAM_BOOL);
-$installer->handle_remote_request($output, $remoterequest, $confirmed);
+$installer->handle_remote_request($output, $remoterequest);
+
+// Handle the confirmed installation request.
+$installremote = optional_param('installremote', null, PARAM_COMPONENT);
+$installremoteversion = optional_param('installremoteversion', null, PARAM_INT);
+$installremoteconfirm = optional_param('installremoteconfirm', false, PARAM_BOOL);
+
+if ($installremote and $installremoteversion) {
+    require_sesskey();
+    require_once($CFG->libdir.'/upgradelib.php');
+
+    $PAGE->set_pagelayout('maintenance');
+    $PAGE->set_popup_notification_allowed(false);
+
+    if ($pluginman->is_remote_plugin_installable($installremote, $installremoteversion)) {
+        $installable = array($pluginman->get_remote_plugin_info($installremote, $installremoteversion, true));
+        upgrade_install_plugins($installable, $installremoteconfirm,
+            get_string('installfromrepo', 'tool_installaddon'),
+            new moodle_url($PAGE->url, array('installremote' => $installremote,
+                'installremoteversion' => $installremoteversion, 'installremoteconfirm' => 1)
+            )
+        );
+    }
+    // We should never get here.
+    throw new moodle_exception('installing_non_installable_component', 'tool_installaddon');
+}
+
+// Handle installation of a plugin from the ZIP file.
+$installzipcomponent = optional_param('installzipcomponent', null, PARAM_COMPONENT);
+$installzipstorage = optional_param('installzipstorage', null, PARAM_FILE);
+$installzipconfirm = optional_param('installzipconfirm', false, PARAM_BOOL);
+
+if ($installzipcomponent and $installzipstorage) {
+    require_sesskey();
+    require_once($CFG->libdir.'/upgradelib.php');
+
+    $PAGE->set_pagelayout('maintenance');
+    $PAGE->set_popup_notification_allowed(false);
+
+    $installable = array((object)array(
+        'component' => $installzipcomponent,
+        'zipfilepath' => make_temp_directory('tool_installaddon').'/'.$installzipstorage.'/plugin.zip',
+    ));
+    upgrade_install_plugins($installable, $installzipconfirm, get_string('installfromzip', 'tool_installaddon'),
+        new moodle_url($installer->index_url(), array('installzipcomponent' => $installzipcomponent,
+            'installzipstorage' => $installzipstorage, 'installzipconfirm' => 1)
+        )
+    );
+}
 
 $form = $installer->get_installfromzip_form();
 
@@ -48,35 +96,47 @@ if ($form->is_cancelled()) {
     redirect($PAGE->url);
 
 } else if ($data = $form->get_data()) {
-    // Save the ZIP file into a temporary location.
-    $jobid = md5(rand().uniqid('', true));
-    $sourcedir = make_temp_directory('tool_installaddon/'.$jobid.'/source');
-    $zipfilename = $installer->save_installfromzip_file($form, $sourcedir);
-    if (empty($data->plugintype)) {
-        $versiondir = make_temp_directory('tool_installaddon/'.$jobid.'/version');
-        $detected = $installer->detect_plugin_component($sourcedir.'/'.$zipfilename, $versiondir);
-        if (empty($detected)) {
+    $storage = $installer->make_installfromzip_storage();
+    $form->save_file('zipfile', $storage.'/plugin.zip');
+
+    $ziprootdir = $pluginman->get_plugin_zip_root_dir($storage.'/plugin.zip');
+    if (empty($ziprootdir)) {
+        echo $output->zip_not_valid_plugin_package_page($installer->index_url());
+        die();
+    }
+
+    $component = $installer->detect_plugin_component($storage.'/plugin.zip');
+    if (!empty($component) and !empty($data->plugintype)) {
+        // If the plugin type was explicitly set, make sure it matches the detected one.
+        list($detectedtype, $detectedname) = core_component::normalize_component($component);
+        if ($detectedtype !== $data->plugintype) {
+            $form->selected_plugintype_mismatch($detectedtype);
+            echo $output->index_page();
+            die();
+        }
+    }
+    if (empty($component)) {
+        // This should not happen as all plugins are supposed to declare their
+        // component. Still, let admins upload legacy packages if they want/need.
+        if (empty($data->plugintype)) {
             $form->require_explicit_plugintype();
+            echo $output->index_page();
+            die();
+        }
+        if (!empty($data->rootdir)) {
+            $usepluginname = $data->rootdir;
         } else {
-            list($detectedtype, $detectedname) = core_component::normalize_component($detected);
-            if ($detectedtype and $detectedname and $detectedtype !== 'core') {
-                $data->plugintype = $detectedtype;
-            } else {
-                $form->require_explicit_plugintype();
-            }
+            $usepluginname = $ziprootdir;
         }
+        $component = $data->plugintype.'_'.$usepluginname;
     }
-    // Redirect to the validation page.
-    if (!empty($data->plugintype)) {
-        $nexturl = new moodle_url('/admin/tool/installaddon/validate.php', array(
-            'sesskey' => sesskey(),
-            'jobid' => $jobid,
-            'zip' => $zipfilename,
-            'type' => $data->plugintype,
-            'rootdir' => $data->rootdir));
-        redirect($nexturl);
-    }
+
+    redirect($installer->index_url(array(
+        'installzipcomponent' => $component,
+        'installzipstorage' => basename($storage),
+        'sesskey' => sesskey(),
+    )));
 }
 
-// Output starts here.
+// Display the tool main page.
 echo $output->index_page();
index 06340ee..da1aee9 100644 (file)
@@ -31,13 +31,13 @@ $string['acknowledgementtext'] = 'I understand that it is my responsibility to h
 $string['featuredisabled'] = 'The plugin installer is disabled on this site.';
 $string['installaddon'] = 'Install plugin!';
 $string['installaddons'] = 'Install plugins';
-$string['installexception'] = 'Oops... An error occurred while trying to install the plugin. Turn debugging mode on to see details of the error.';
 $string['installfromrepo'] = 'Install plugins from the Moodle plugins directory';
 $string['installfromrepo_help'] = 'You will be redirected to the Moodle plugins directory to search for and install a plugin. Note that your site full name, URL and Moodle version will be sent as well, to make the installation process easier for you.';
 $string['installfromzip'] = 'Install plugin from ZIP file';
 $string['installfromzip_help'] = 'An alternative to installing a plugin directly from the Moodle plugins directory is to upload a ZIP package of the plugin. The ZIP package should have the same structure as a package downloaded from the Moodle plugins directory.';
 $string['installfromzipfile'] = 'ZIP package';
-$string['installfromzipfile_help'] = 'The plugin ZIP package must contain just one directory, named to match the plugin. The ZIP will be extracted into an appropriate location for the plugin type. If the package has been downloaded from the Moodle plugins directory then it will have this structure.';
+$string['installfromzipfile_help'] = 'The plugin ZIP package must contain just one directory, named to match the plugin name. The ZIP will be extracted into an appropriate location for the plugin type. If the package has been downloaded from the Moodle plugins directory then it will have this structure.';
+$string['installfromzipinvalid'] = 'The plugin ZIP package must contain just one directory, named to match the plugin name. Provided file is not a valid plugin ZIP package.';
 $string['installfromziprootdir'] = 'Rename the root directory';
 $string['installfromziprootdir_help'] = 'Some ZIP packages, such as those generated by Github, may contain an incorrect root directory name. If so, the correct name may be entered here.';
 $string['installfromzipsubmit'] = 'Install plugin from the ZIP file';
@@ -56,13 +56,6 @@ $string['remoterequestconfirm'] = 'There is a request to install plugin <strong>
 $string['remoterequestinvalid'] = 'There is a request to install a plugin from the Moodle plugins directory on this site. Unfortunately the request is not valid and so the plugin cannot be installed.';
 $string['remoterequestpermcheck'] = 'There is a request to install plugin {$a->name} ({$a->component}) version {$a->version} from the Moodle plugins directory on this site. However, the location <strong>{$a->typepath}</strong> is <strong>not writable</strong>. You need to give write access for the web server user to the location, then press the continue button to repeat the check.';
 $string['remoterequestpluginfoexception'] = 'Oops... An error occurred while trying to obtain information about the plugin {$a->name} ({$a->component}) version {$a->version}. The plugin cannot be installed. Turn debugging mode on to see details of the error.';
+$string['remoterequestnoninstallable'] = 'There is a request to install plugin {$a->name} ({$a->component}) version {$a->version} from the Moodle plugins directory on this site. However, the plugin installation pre-check failed (reason code: {$a->reason}).';
 $string['typedetectionfailed'] = 'Unable to detect the plugin type. Please choose the plugin type manually.';
-$string['validation'] = 'Plugin package validation';
-$string['validationresult0'] = 'Validation failed!';
-$string['validationresult0_help'] = 'A serious problem was detected and so it is not safe to install the plugin. See the validation log messages for details.';
-$string['validationresult1'] = 'Validation passed!';
-$string['validationresult2_help'] = 'No serious problems were detected. You can continue with the plugin installation. See the validation log messages for more details and eventual warnings.';
-$string['validationresult1_help'] = 'The plugin package has been validated and no serious problems were detected.';
-$string['validationresultinfo'] = 'Info';
-$string['validationresultmsg'] = 'Message';
-$string['validationresultstatus'] = 'Status';
+$string['typedetectionmismatch'] = 'The selected plugin type does not match the one declared by the plugin: {$a}';
index 9bd433f..e271b07 100644 (file)
@@ -52,9 +52,9 @@ if (is_null($plugintype)) {
     die();
 }
 
-$installer = tool_installaddon_installer::instance();
+$pluginman = core_plugin_manager::instance();
 
-$plugintypepath = $installer->get_plugintype_root($plugintype);
+$plugintypepath = $pluginman->get_plugintype_root($plugintype);
 
 if (empty($plugintypepath)) {
     header('HTTP/1.1 400 Bad Request');
@@ -63,7 +63,7 @@ if (empty($plugintypepath)) {
 
 $response = array('path' => $plugintypepath);
 
-if ($installer->is_plugintype_writable($plugintype)) {
+if ($pluginman->is_plugintype_writable($plugintype)) {
     $response['writable'] = 1;
 } else {
     $response['writable'] = 0;
index daa027f..e69aa67 100644 (file)
@@ -37,9 +37,6 @@ class tool_installaddon_renderer extends plugin_renderer_base {
     /** @var tool_installaddon_installer */
     protected $installer = null;
 
-    /** @var \core\update\validator */
-    protected $validator = null;
-
     /**
      * Sets the tool_installaddon_installer instance being used.
      *
@@ -54,20 +51,6 @@ class tool_installaddon_renderer extends plugin_renderer_base {
         }
     }
 
-    /**
-     * Sets the \core\update\validator instance being used.
-     *
-     * @throws coding_exception if the validator has been already set
-     * @param \core\update\validator $validator
-     */
-    public function set_validator_instance(\core\update\validator $validator) {
-        if (is_null($this->validator)) {
-            $this->validator = $validator;
-        } else {
-            throw new coding_exception('Attempting to reset the validator instance.');
-        }
-    }
-
     /**
      * Defines the index page layout
      *
@@ -96,22 +79,18 @@ class tool_installaddon_renderer extends plugin_renderer_base {
     }
 
     /**
-     * Defines the validation results page layout
+     * Inform the user that the ZIP is not a valid plugin package file.
      *
+     * @param moodle_url $continueurl
      * @return string
      */
-    public function validation_page() {
-
-        if (is_null($this->installer)) {
-            throw new coding_exception('Installer instance has not been set.');
-        }
-
-        if (is_null($this->validator)) {
-            throw new coding_exception('Validator instance has not been set.');
-        }
+    public function zip_not_valid_plugin_package_page(moodle_url $continueurl) {
 
-        $out = $this->validation_page_heading();
-        $out .= $this->validation_page_messages();
+        $out = $this->output->header();
+        $out .= $this->output->heading(get_string('installfromzip', 'tool_installaddon'));
+        $out .= $this->output->box(get_string('installfromzipinvalid', 'tool_installaddon'), 'generalbox', 'notice');
+        $out .= $this->output->continue_button($continueurl, 'get');
+        $out .= $this->output->footer();
 
         return $out;
     }
@@ -191,39 +170,17 @@ class tool_installaddon_renderer extends plugin_renderer_base {
     }
 
     /**
-     * Inform the user about pluginfo service call failure
-     *
-     * @param stdClass $data decoded request data
-     * @param moodle_url $continueurl
-     * @return string
-     */
-    public function remote_request_pluginfo_failure(stdClass $data, moodle_url $continueurl) {
-
-        $out = $this->output->header();
-        $out .= $this->output->heading(get_string('installfromrepo', 'tool_installaddon'));
-        $out .= $this->output->box(get_string('remoterequestpluginfoexception', 'tool_installaddon', $data), 'generalbox', 'notice');
-        $out .= $this->output->continue_button($continueurl, 'get');
-        $out .= $this->output->footer();
-
-        return $out;
-    }
-
-    /**
-     * Inform the user about the installer exception
-     *
-     * This implementation does not actually use the passed exception. Custom renderers might want to
-     * display additional data obtained via {@link get_exception_info()}. Also note, this method is called
-     * in non-debugging mode only. If debugging is allowed at the site, default exception handler is triggered.
+     * Inform the user that the requested remote plugin is not installable.
      *
-     * @param tool_installaddon_installer_exception $e thrown exception
+     * @param stdClass $data decoded request data with ->reason property added
      * @param moodle_url $continueurl
      * @return string
      */
-    public function installer_exception(tool_installaddon_installer_exception $e, moodle_url $continueurl) {
+    public function remote_request_non_installable_page(stdClass $data, moodle_url $continueurl) {
 
         $out = $this->output->header();
         $out .= $this->output->heading(get_string('installfromrepo', 'tool_installaddon'));
-        $out .= $this->output->box(get_string('installexception', 'tool_installaddon'), 'generalbox', 'notice');
+        $out .= $this->output->box(get_string('remoterequestnoninstallable', 'tool_installaddon', $data), 'generalbox', 'notice');
         $out .= $this->output->continue_button($continueurl, 'get');
         $out .= $this->output->footer();
 
@@ -276,83 +233,4 @@ class tool_installaddon_renderer extends plugin_renderer_base {
 
         return $out;
     }
-
-    /**
-     * Renders the page title and the overall validation verdict
-     *
-     * @return string
-     */
-    protected function validation_page_heading() {
-
-        $heading = $this->output->heading(get_string('validation', 'tool_installaddon'));
-
-        if ($this->validator->get_result()) {
-            $status = $this->output->container(
-                html_writer::span(get_string('validationresult1', 'tool_installaddon'), 'verdict').
-                    $this->output->help_icon('validationresult1', 'tool_installaddon'),
-                array('validationresult', 'success')
-            );
-        } else {
-            $status = $this->output->container(
-                html_writer::span(get_string('validationresult0', 'tool_installaddon'), 'verdict').
-                    $this->output->help_icon('validationresult0', 'tool_installaddon'),
-                array('validationresult', 'failure')
-            );
-        }
-
-        return $heading . $status;
-    }
-
-    /**
-     * Renders validation log messages.
-     *
-     * @return string
-     */
-    protected function validation_page_messages() {
-
-        $validator = $this->validator; // We need this to be able to use their constants.
-        $messages = $validator->get_messages();
-
-        if (empty($messages)) {
-            return '';
-        }
-
-        $table = new html_table();
-        $table->attributes['class'] = 'validationmessages generaltable';
-        $table->head = array(
-            get_string('validationresultstatus', 'tool_installaddon'),
-            get_string('validationresultmsg', 'tool_installaddon'),
-            get_string('validationresultinfo', 'tool_installaddon')
-        );
-        $table->colclasses = array('msgstatus', 'msgtext', 'msginfo');
-
-        $stringman = get_string_manager();
-
-        foreach ($messages as $message) {
-
-            if ($message->level === $validator::DEBUG and !debugging()) {
-                continue;
-            }
-
-            $msgstatus = $validator->message_level_name($message->level);
-            $msgtext = $validator->message_code_name($message->msgcode);
-            $msginfo = $validator->message_code_info($message->msgcode, $message->addinfo);
-            if (empty($msginfo) and $message->addinfo !== null) {
-                $msginfo = html_writer::tag('pre', s(print_r($message->addinfo, true)));
-            }
-            $msghelpicon = $validator->message_help_icon($message->msgcode);
-            if ($msghelpicon) {
-                $msghelp = $this->output->render($msghelpicon);
-            } else {
-                $msghelp = '';
-            }
-
-            $row = new html_table_row(array($msgstatus, $msgtext.$msghelp, $msginfo));
-            $row->attributes['class'] = 'level-'.$message->level.' '.$message->msgcode;
-
-            $table->data[] = $row;
-        }
-
-        return html_writer::table($table);
-    }
 }
index b533360..422930f 100644 (file)
@@ -30,10 +30,4 @@ if ($hassiteconfig and empty($CFG->disableonclickaddoninstall)) {
     $ADMIN->add('modules', new admin_externalpage('tool_installaddon_index',
         get_string('installaddons', 'tool_installaddon'),
         "$CFG->wwwroot/$CFG->admin/tool/installaddon/index.php"), 'modsettings');
-
-    $ADMIN->add('modules', new admin_externalpage('tool_installaddon_validate',
-        get_string('validation', 'tool_installaddon'),
-        "$CFG->wwwroot/$CFG->admin/tool/installaddon/validate.php",
-        'moodle/site:config',
-        true), 'modsettings');
 }
index 05155ca..3de50f3 100644 (file)
 #page-admin-tool-installaddon-index #installfromrepobox .singlebutton input[type=submit] {
     padding: 1em;
 }
-
-#page-admin-tool-installaddon-validate .validationresult {
-    margin: 2em auto;
-    text-align: center;
-}
-
-#page-admin-tool-installaddon-validate .validationresult .verdict {
-    margin: 0em 0.5em;
-    padding: 0.5em;
-    border: 2px solid;
-    -webkit-border-radius: 5px;
-    -moz-border-radius: 5px;
-    border-radius: 5px;
-    font-weight: bold;
-}
-
-#page-admin-tool-installaddon-validate .validationresult.success .verdict {
-    background-color: #e7f1c3;
-    border-color: #aaeeaa;
-}
-
-#page-admin-tool-installaddon-validate .validationresult.failure .verdict {
-    background-color: #ffd3d9;
-    border-color: #eeaaaa;
-}
-
-#page-admin-tool-installaddon-validate .validationmessages {
-    margin: 0px auto;
-}
-
-#page-admin-tool-installaddon-validate .validationmessages .level-error .msgstatus {
-    background-color: #ffd3d9;
-}
-
-#page-admin-tool-installaddon-validate .validationmessages .level-warning .msgstatus {
-    background-color: #f3f2aa;
-}
-
-#page-admin-tool-installaddon-validate .validationmessages .level-info .msgstatus {
-    background-color: #e7f1c3;
-}
-
-#page-admin-tool-installaddon-validate .validationmessages .level-debug .msgstatus {
-    background-color: #d2ebff;
-}
-
-#page-admin-tool-installaddon-validate .postvalidationbuttons {
-    text-align: center;
-    margin: 1em auto;
-}
-
-#page-admin-tool-installaddon-validate .postvalidationbuttons .singlebutton {
-    display: inline-block;
-    margin: 1em 1em;
-}
diff --git a/admin/tool/installaddon/tests/fixtures/testable_installer.php b/admin/tool/installaddon/tests/fixtures/testable_installer.php
new file mode 100644 (file)
index 0000000..8073820
--- /dev/null
@@ -0,0 +1,58 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * @package     tool_installaddon
+ * @subpackage  fixtures
+ * @category    test
+ * @copyright   2013, 2015 David Mudrak <david@moodle.com>
+ * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Testable subclass of the tested class
+ *
+ * @copyright 2013 David Mudrak <david@moodle.com>
+ * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class testable_tool_installaddon_installer extends tool_installaddon_installer {
+
+    public function get_site_fullname() {
+        return strip_tags('<h1 onmouseover="alert(\'Hello Moodle.org!\');">Nasty site</h1>');
+    }
+
+    public function get_site_url() {
+        return 'file:///etc/passwd';
+    }
+
+    public function get_site_major_version() {
+        return "2.5'; DROP TABLE mdl_user; --";
+    }
+
+    public function testable_decode_remote_request($request) {
+        return parent::decode_remote_request($request);
+    }
+
+    protected function should_send_site_info() {
+        return true;
+    }
+
+    public function testable_detect_plugin_component_from_versionphp($code) {
+        return parent::detect_plugin_component_from_versionphp($code);
+    }
+}
index 2b4ed9b..55a0608 100644 (file)
@@ -26,6 +26,8 @@
 
 defined('MOODLE_INTERNAL') || die();
 
+global $CFG;
+require_once(__DIR__.'/fixtures/testable_installer.php');
 
 /**
  * Unit tests for the {@link tool_installaddon_installer} class
@@ -49,31 +51,6 @@ class tool_installaddon_installer_testcase extends advanced_testcase {
         $this->assertSame("2.5'; DROP TABLE mdl_user; --", $site['majorversion']);
     }
 
-    public function test_extract_installfromzip_file() {
-        global $CFG;
-
-        $jobid = md5(rand().uniqid('test_', true));
-        $sourcedir = make_temp_directory('tool_installaddon/'.$jobid.'/source');
-        $contentsdir = make_temp_directory('tool_installaddon/'.$jobid.'/contents');
-        copy($CFG->libdir.'/tests/fixtures/update_validator/zips/invalidroot.zip', $sourcedir.'/testinvalidroot.zip');
-
-        $installer = tool_installaddon_installer::instance();
-        $files = $installer->extract_installfromzip_file($sourcedir.'/testinvalidroot.zip', $contentsdir, 'fixed_root');
-        $this->assertInternalType('array', $files);
-        $this->assertCount(4, $files);
-        $this->assertSame(true, $files['fixed_root/']);
-        $this->assertSame(true, $files['fixed_root/lang/']);
-        $this->assertSame(true, $files['fixed_root/lang/en/']);
-        $this->assertSame(true, $files['fixed_root/lang/en/fixed_root.php']);
-        foreach ($files as $file => $status) {
-            if (substr($file, -1) === '/') {
-                $this->assertTrue(is_dir($contentsdir.'/'.$file));
-            } else {
-                $this->assertTrue(is_file($contentsdir.'/'.$file));
-            }
-        }
-    }
-
     public function test_decode_remote_request() {
         $installer = testable_tool_installaddon_installer::instance();
 
@@ -132,68 +109,51 @@ class tool_installaddon_installer_testcase extends advanced_testcase {
         $this->assertSame(false, $installer->testable_decode_remote_request($request));
     }
 
-    public function test_move_directory() {
-        $jobid = md5(rand().uniqid('test_', true));
-        $jobroot = make_temp_directory('tool_installaddon/'.$jobid);
-        $contentsdir = make_temp_directory('tool_installaddon/'.$jobid.'/contents/sub/folder');
-        file_put_contents($contentsdir.'/readme.txt', 'Hello world!');
-
-        $installer = tool_installaddon_installer::instance();
-        $installer->move_directory($jobroot.'/contents', $jobroot.'/moved', 0777, 0666);
-
-        $this->assertFalse(is_dir($jobroot.'/contents'));
-        $this->assertTrue(is_file($jobroot.'/moved/sub/folder/readme.txt'));
-        $this->assertSame('Hello world!', file_get_contents($jobroot.'/moved/sub/folder/readme.txt'));
-    }
-
     public function test_detect_plugin_component() {
         global $CFG;
 
-        $jobid = md5(rand().uniqid('test_', true));
-        $workdir = make_temp_directory('tool_installaddon/'.$jobid.'/version');
-        $zipfile = $CFG->libdir.'/tests/fixtures/update_validator/zips/bar.zip';
         $installer = tool_installaddon_installer::instance();
-        $this->assertEquals('foo_bar', $installer->detect_plugin_component($zipfile, $workdir));
+
+        $zipfile = $CFG->libdir.'/tests/fixtures/update_validator/zips/bar.zip';
+        $this->assertEquals('foo_bar', $installer->detect_plugin_component($zipfile));
+
+        $zipfile = $CFG->libdir.'/tests/fixtures/update_validator/zips/invalidroot.zip';
+        $this->assertFalse($installer->detect_plugin_component($zipfile));
     }
 
     public function test_detect_plugin_component_from_versionphp() {
+        global $CFG;
+
         $installer = testable_tool_installaddon_installer::instance();
-        $this->assertEquals('bar_bar_conan', $installer->detect_plugin_component_from_versionphp('
+        $fixtures = $CFG->libdir.'/tests/fixtures/update_validator/';
+
+        $this->assertEquals('bar_bar_conan', $installer->testable_detect_plugin_component_from_versionphp('
 $plugin->version  = 2014121300;
   $plugin->component=   "bar_bar_conan"  ; // Go Arnie go!'));
-    }
-}
-
-
-/**
- * Testable subclass of the tested class
- *
- * @copyright 2013 David Mudrak <david@moodle.com>
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class testable_tool_installaddon_installer extends tool_installaddon_installer {
 
-    public function get_site_fullname() {
-        return strip_tags('<h1 onmouseover="alert(\'Hello Moodle.org!\');">Nasty site</h1>');
-    }
+        $versionphp = file_get_contents($fixtures.'/github/moodle-repository_mahara-master/version.php');
+        $this->assertEquals('repository_mahara', $installer->testable_detect_plugin_component_from_versionphp($versionphp));
 
-    public function get_site_url() {
-        return 'file:///etc/passwd';
+        $versionphp = file_get_contents($fixtures.'/nocomponent/baz/version.php');
+        $this->assertFalse($installer->testable_detect_plugin_component_from_versionphp($versionphp));
     }
 
-    public function get_site_major_version() {
-        return "2.5'; DROP TABLE mdl_user; --";
-    }
+    public function test_make_installfromzip_storage() {
+        $installer = testable_tool_installaddon_installer::instance();
 
-    public function testable_decode_remote_request($request) {
-        return parent::decode_remote_request($request);
-    }
+        // Check we get writable directory.
+        $storage1 = $installer->make_installfromzip_storage();
+        $this->assertTrue(is_dir($storage1));
+        $this->assertTrue(is_writable($storage1));
+        file_put_contents($storage1.'/hello.txt', 'Find me if you can!');
 
-    protected function should_send_site_info() {
-        return true;
-    }
+        // Check we get unique directory on each call.
+        $storage2 = $installer->make_installfromzip_storage();
+        $this->assertTrue(is_dir($storage2));
+        $this->assertTrue(is_writable($storage2));
+        $this->assertFalse(file_exists($storage2.'/hello.txt'));
 
-    public function detect_plugin_component_from_versionphp($code) {
-        return parent::detect_plugin_component_from_versionphp($code);
+        // Check both are in the same parent directory.
+        $this->assertEquals(dirname($storage1), dirname($storage2));
     }
 }
diff --git a/admin/tool/installaddon/validate.php b/admin/tool/installaddon/validate.php
deleted file mode 100644 (file)
index efd01e0..0000000
+++ /dev/null
@@ -1,92 +0,0 @@
-<?php
-
-// This file is part of Moodle - http://moodle.org/
-//
-// Moodle is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// Moodle is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-//
-// You should have received a copy of the GNU General Public License
-// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
-
-/**
- * The ZIP package validation.
- *
- * @package     tool_installaddon
- * @copyright   2013 David Mudrak <david@moodle.com>
- * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-require(dirname(__FILE__) . '/../../../config.php');
-require_once($CFG->libdir.'/adminlib.php');
-require_once($CFG->libdir.'/filelib.php');
-
-navigation_node::override_active_url(new moodle_url('/admin/tool/installaddon/index.php'));
-admin_externalpage_setup('tool_installaddon_validate');
-
-if (!empty($CFG->disableonclickaddoninstall)) {
-    notice(get_string('featuredisabled', 'tool_installaddon'));
-}
-
-require_sesskey();
-
-$jobid = required_param('jobid', PARAM_ALPHANUM);
-$zipfilename = required_param('zip', PARAM_FILE);
-$plugintype = required_param('type', PARAM_ALPHANUMEXT);
-$rootdir = optional_param('rootdir', '', PARAM_PLUGIN);
-
-$zipfilepath = $CFG->tempdir.'/tool_installaddon/'.$jobid.'/source/'.$zipfilename;
-if (!file_exists($zipfilepath)) {
-    redirect(new moodle_url('/admin/tool/installaddon/index.php'),
-        get_string('invaliddata', 'core_error'));
-}
-
-$installer = tool_installaddon_installer::instance();
-
-// Extract the ZIP contents.
-fulldelete($CFG->tempdir.'/tool_installaddon/'.$jobid.'/contents');
-$zipcontentpath = make_temp_directory('tool_installaddon/'.$jobid.'/contents');
-$zipcontentfiles = $installer->extract_installfromzip_file($zipfilepath, $zipcontentpath, $rootdir);
-
-// Validate the contents of the plugin ZIP file.
-$validator = \core\update\validator::instance($zipcontentpath, $zipcontentfiles);
-$validator->assert_plugin_type($plugintype);
-$validator->assert_moodle_version($CFG->version);
-$result = $validator->execute();
-
-// Display the validation results.
-$output = $PAGE->get_renderer('tool_installaddon');
-$output->set_installer_instance($installer);
-$output->set_validator_instance($validator);
-
-echo $output->header();
-echo $output->validation_page();
-
-if ($result) {
-    $conturl = new moodle_url('/admin/tool/installaddon/deploy.php', array(
-        'sesskey' => sesskey(),
-        'jobid' => $jobid,
-        'type' => $plugintype,
-        'name' => $validator->get_rootdir())
-    );
-    $contbutton = $output->single_button($conturl, get_string('installaddon', 'tool_installaddon'), 'post',
-        array('class' => 'singlebutton continuebutton'));
-    echo $output->heading(get_string('acknowledgement', 'tool_installaddon'), 3);
-    echo $output->container(get_string('acknowledgementtext', 'tool_installaddon'));
-
-} else {
-    $contbutton = '';
-    fulldelete($CFG->tempdir.'/tool_installaddon/'.$jobid);
-}
-
-$cancelbutton = $output->single_button(new moodle_url('/admin/tool/installaddon/index.php'), get_string('cancel', 'core'),
-    'get', array('class' => 'singlebutton cancelbutton'));
-
-echo $output->container($cancelbutton.$contbutton, 'postvalidationbuttons');
-echo $output->footer();