MDL-49329 admin: Get rid of mdeploy and \core\update\deployer class
authorDavid Mudrák <david@moodle.com>
Tue, 6 Oct 2015 08:17:41 +0000 (10:17 +0200)
committerDavid Mudrák <david@moodle.com>
Thu, 8 Oct 2015 21:32:04 +0000 (23:32 +0200)
The mdeploy.php standalone script (used to download and unzip plugins
into the dirroot) and the \core\update\deployer class (as a
communication bridge between the core and the mdeploy.php) was
originally designed and implemented with the assumption that it would be
eventually used for updating the Moodle core itself, too. Therefore it
was written as standalone utility without dependency on the Moodle core
libraries.

However, it never happened and there is no real demand for that. So now
there is no need to have and maintain a completely parallel solution for
common things like fetching and unzipping plugin ZIPs.

Additional reasoning for mdeploy.php was that the core is not very
reliable during the core upgrade and we could run into various troubles.
This does not seem to be that bad. We rely on a lot of core
functionality (such as output rendering, DB access etc) and plugins
deployment seems to work well (and better) with common core libraries.

So long mdeploy, and thanks for all the hard work you did for us.

admin/renderer.php
admin/tool/installaddon/classes/installer.php
lib/classes/plugin_manager.php
lib/classes/update/deployer.php [deleted file]
lib/phpunit/classes/util.php
mdeploy.php [deleted file]
mdeploytest.php [deleted file]

index dc61725..b8c713a 100644 (file)
@@ -241,62 +241,6 @@ class core_admin_renderer extends plugin_renderer_base {
         return $output;
     }
 
-    /**
-     * Prints a page with a summary of plugin deployment to be confirmed.
-     *
-     * @param \core\update\deployer $deployer
-     * @param array $data deployer's data package as returned by {@link \core\update\deployer::submitted_data()}
-     * @return string
-     */
-    public function upgrade_plugin_confirm_deploy_page(\core\update\deployer $deployer, array $data) {
-
-        if (!$deployer->initialized()) {
-            throw new coding_exception('Unable to render a page for non-initialized deployer.');
-        }
-
-        if (empty($data['updateinfo'])) {
-            throw new coding_exception('Missing required data component.');
-        }
-
-        $updateinfo = $data['updateinfo'];
-
-        $output  = '';
-        $output .= $this->header();
-        $output .= $this->container_start('generalbox updateplugin', 'notice');
-
-        $a = new stdClass();
-        if (get_string_manager()->string_exists('pluginname', $updateinfo->component)) {
-            $a->name = get_string('pluginname', $updateinfo->component);
-        } else {
-            $a->name = $updateinfo->component;
-        }
-
-        if (isset($updateinfo->release)) {
-            $a->version = $updateinfo->release . ' (' . $updateinfo->version . ')';
-        } else {
-            $a->version = $updateinfo->version;
-        }
-        $a->url = $updateinfo->download;
-
-        $output .= $this->output->heading(get_string('updatepluginconfirm', 'core_plugin'));
-        $output .= $this->output->container(format_text(get_string('updatepluginconfirminfo', 'core_plugin', $a)), 'updatepluginconfirminfo');
-        $output .= $this->output->container(get_string('updatepluginconfirmwarning', 'core_plugin', 'updatepluginconfirmwarning'));
-
-        if ($repotype = $deployer->plugin_external_source($data['updateinfo'])) {
-            $output .= $this->output->container(get_string('updatepluginconfirmexternal', 'core_plugin', $repotype), 'updatepluginconfirmexternal');
-        }
-
-        $widget = $deployer->make_execution_widget($data['updateinfo'], $data['returnurl']);
-        $output .= $this->output->render($widget);
-
-        $output .= $this->output->single_button($data['callerurl'], get_string('cancel', 'core'), 'get');
-
-        $output .= $this->container_end();
-        $output .= $this->footer();
-
-        return $output;
-    }
-
     /**
      * Display the admin notifications page.
      * @param int $maturity
@@ -981,7 +925,7 @@ class core_admin_renderer extends plugin_renderer_base {
                 $availableupdates = $plugin->available_updates();
                 if (!empty($availableupdates)) {
                     foreach ($availableupdates as $availableupdate) {
-                        $status .= $this->plugin_available_update_info($availableupdate);
+                        $status .= $this->plugin_available_update_info($pluginman, $availableupdate);
                     }
                 }
 
@@ -1617,7 +1561,7 @@ class core_admin_renderer extends plugin_renderer_base {
                 $updateinfo = '';
                 if (is_array($plugin->available_updates())) {
                     foreach ($plugin->available_updates() as $availableupdate) {
-                        $updateinfo .= $this->plugin_available_update_info($availableupdate);
+                        $updateinfo .= $this->plugin_available_update_info($pluginman, $availableupdate);
                     }
                 }
 
@@ -1636,12 +1580,10 @@ class core_admin_renderer extends plugin_renderer_base {
     /**
      * Helper method to render the information about the available plugin update
      *
-     * The passed objects always provides at least the 'version' property containing
-     * the (higher) version of the plugin available.
-     *
+     * @param core_plugin_manager $pluginman plugin manager instance
      * @param \core\update\info $updateinfo information about the available update for the plugin
      */
-    protected function plugin_available_update_info(\core\update\info $updateinfo) {
+    protected function plugin_available_update_info(core_plugin_manager $pluginman, \core\update\info $updateinfo) {
 
         $boxclasses = 'pluginupdateinfo';
         $info = array();
@@ -1670,19 +1612,18 @@ class core_admin_renderer extends plugin_renderer_base {
         $box .= html_writer::tag('div', get_string('updateavailable', 'core_plugin', $updateinfo->version), array('class' => 'version'));
         $box .= $this->output->box(implode(html_writer::tag('span', ' ', array('class' => 'separator')), $info), '');
 
-        $deployer = \core\update\deployer::instance();
-        if ($deployer->initialized()) {
-            $impediments = $deployer->deployment_impediments($updateinfo);
-            if (empty($impediments)) {
-                $widget = $deployer->make_confirm_widget($updateinfo);
-                $box .= $this->output->render($widget);
-            } else {
-                if (isset($impediments['notwritable'])) {
-                    $box .= $this->output->help_icon('notwritable', 'core_plugin', get_string('notwritable', 'core_plugin'));
-                }
-                if (isset($impediments['notdownloadable'])) {
-                    $box .= $this->output->help_icon('notdownloadable', 'core_plugin', get_string('notdownloadable', 'core_plugin'));
-                }
+        if ($pluginman->is_remote_plugin_installable($updateinfo->component, $updateinfo->version, $reason)) {
+            $box .= $this->output->single_button(
+                new moodle_url($this->page->url, array('installupdate' => $updateinfo->component,
+                    'installupdateversion' => $updateinfo->version)),
+                get_string('updateavailableinstall', 'core_admin'),
+                'post',
+                array('class' => 'singlebutton updateavailableinstall')
+            );
+        } else {
+            $reasonhelp = $this->info_remote_plugin_not_installable($reason);
+            if ($reasonhelp) {
+                $box .= html_writer::div($reasonhelp, 'reasonhelp updateavailableinstall');
             }
         }
 
index 7059010..6b4061d 100644 (file)
@@ -348,6 +348,8 @@ class tool_installaddon_installer {
      * 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
index f75cb0e..02bbedb 100644 (file)
@@ -667,7 +667,6 @@ class core_plugin_manager {
     /**
      * Check to see if the current version of the plugin seems to be a checkout of an external repository.
      *
-     * @see \core\update\deployer::plugin_external_source()
      * @param string $component frankenstyle component name
      * @return false|string
      */
diff --git a/lib/classes/update/deployer.php b/lib/classes/update/deployer.php
deleted file mode 100644 (file)
index 25ba168..0000000
+++ /dev/null
@@ -1,574 +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/>.
-
-/**
- * Defines classes used for updates.
- *
- * @package    core
- * @copyright  2011 David Mudrak <david@moodle.com>
- * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-namespace core\update;
-
-use coding_exception, core_component, moodle_url;
-
-defined('MOODLE_INTERNAL') || die();
-
-/**
- * Implements a communication bridge to the mdeploy.php utility
- */
-class deployer {
-
-    /** @var \core\update\deployer holds the singleton instance */
-    protected static $singletoninstance;
-    /** @var moodle_url URL of a page that includes the deployer UI */
-    protected $callerurl;
-    /** @var moodle_url URL to return after the deployment */
-    protected $returnurl;
-
-    /**
-     * Direct instantiation not allowed, use the factory method {@link self::instance()}
-     */
-    protected function __construct() {
-    }
-
-    /**
-     * Sorry, this is singleton
-     */
-    protected function __clone() {
-    }
-
-    /**
-     * Factory method for this class
-     *
-     * @return \core\update\deployer the singleton instance
-     */
-    public static function instance() {
-        if (is_null(self::$singletoninstance)) {
-            self::$singletoninstance = new self();
-        }
-        return self::$singletoninstance;
-    }
-
-    /**
-     * Reset caches used by this script
-     *
-     * @param bool $phpunitreset is this called as a part of PHPUnit reset?
-     */
-    public static function reset_caches($phpunitreset = false) {
-        if ($phpunitreset) {
-            self::$singletoninstance = null;
-        }
-    }
-
-    /**
-     * Is automatic deployment enabled?
-     *
-     * @return bool
-     */
-    public function enabled() {
-        global $CFG;
-
-        if (!empty($CFG->disableupdateautodeploy)) {
-            // The feature is prohibited via config.php.
-            return false;
-        }
-
-        return get_config('updateautodeploy');
-    }
-
-    /**
-     * Sets some base properties of the class to make it usable.
-     *
-     * @param moodle_url $callerurl the base URL of a script that will handle the class'es form data
-     * @param moodle_url $returnurl the final URL to return to when the deployment is finished
-     */
-    public function initialize(moodle_url $callerurl, moodle_url $returnurl) {
-
-        if (!$this->enabled()) {
-            throw new coding_exception('Unable to initialize the deployer, the feature is not enabled.');
-        }
-
-        $this->callerurl = $callerurl;
-        $this->returnurl = $returnurl;
-    }
-
-    /**
-     * Has the deployer been initialized?
-     *
-     * Initialized deployer means that the following properties were set:
-     * callerurl, returnurl
-     *
-     * @return bool
-     */
-    public function initialized() {
-
-        if (!$this->enabled()) {
-            return false;
-        }
-
-        if (empty($this->callerurl)) {
-            return false;
-        }
-
-        if (empty($this->returnurl)) {
-            return false;
-        }
-
-        return true;
-    }
-
-    /**
-     * Returns a list of reasons why the deployment can not happen
-     *
-     * If the returned array is empty, the deployment seems to be possible. The returned
-     * structure is an associative array with keys representing individual impediments.
-     * Possible keys are: missingdownloadurl, missingdownloadmd5, notwritable.
-     *
-     * @param \core\update\info $info
-     * @return array
-     */
-    public function deployment_impediments(info $info) {
-
-        $impediments = array();
-
-        if (empty($info->download)) {
-            $impediments['missingdownloadurl'] = true;
-        }
-
-        if (empty($info->downloadmd5)) {
-            $impediments['missingdownloadmd5'] = true;
-        }
-
-        if (!empty($info->download) and !$this->update_downloadable($info->download)) {
-            $impediments['notdownloadable'] = true;
-        }
-
-        if (!$this->component_writable($info->component)) {
-            $impediments['notwritable'] = true;
-        }
-
-        return $impediments;
-    }
-
-    /**
-     * Check to see if the current version of the plugin seems to be a checkout of an external repository.
-     *
-     * @see core_plugin_manager::plugin_external_source()
-     * @param \core\update\info $info
-     * @return false|string
-     */
-    public function plugin_external_source(info $info) {
-
-        $paths = core_component::get_plugin_types();
-        list($plugintype, $pluginname) = core_component::normalize_component($info->component);
-        $pluginroot = $paths[$plugintype].'/'.$pluginname;
-
-        if (is_dir($pluginroot.'/.git')) {
-            return 'git';
-        }
-
-        if (is_file($pluginroot.'/.git')) {
-            return 'git-submodule';
-        }
-
-        if (is_dir($pluginroot.'/CVS')) {
-            return 'cvs';
-        }
-
-        if (is_dir($pluginroot.'/.svn')) {
-            return 'svn';
-        }
-
-        if (is_dir($pluginroot.'/.hg')) {
-            return 'mercurial';
-        }
-
-        return false;
-    }
-
-    /**
-     * Prepares a renderable widget to confirm installation of an available update.
-     *
-     * @param \core\update\info $info component version to deploy
-     * @return \renderable
-     */
-    public function make_confirm_widget(info $info) {
-
-        if (!$this->initialized()) {
-            throw new coding_exception('Illegal method call - deployer not initialized.');
-        }
-
-        $params = array(
-            'updateaddon' => $info->component,
-            'version' =>$info->version,
-            'sesskey' => sesskey(),
-        );
-
-        // Append some our own data.
-        if (!empty($this->callerurl)) {
-            $params['callerurl'] = $this->callerurl->out(false);
-        }
-        if (!empty($this->returnurl)) {
-            $params['returnurl'] = $this->returnurl->out(false);
-        }
-
-        $widget = new \single_button(
-            new moodle_url($this->callerurl, $params),
-            get_string('updateavailableinstall', 'core_admin'),
-            'post'
-        );
-
-        return $widget;
-    }
-
-    /**
-     * Prepares a renderable widget to execute installation of an available update.
-     *
-     * @param \core\update\info $info component version to deploy
-     * @param moodle_url $returnurl URL to return after the installation execution
-     * @return \renderable
-     */
-    public function make_execution_widget(info $info, moodle_url $returnurl = null) {
-        global $CFG;
-
-        if (!$this->initialized()) {
-            throw new coding_exception('Illegal method call - deployer not initialized.');
-        }
-
-        $pluginrootpaths = core_component::get_plugin_types();
-
-        list($plugintype, $pluginname) = core_component::normalize_component($info->component);
-
-        if (empty($pluginrootpaths[$plugintype])) {
-            throw new coding_exception('Unknown plugin type root location', $plugintype);
-        }
-
-        list($passfile, $password) = $this->prepare_authorization();
-
-        if (is_null($returnurl)) {
-            $returnurl = new moodle_url('/admin');
-        } else {
-            $returnurl = $returnurl;
-        }
-
-        $params = array(
-            'upgrade' => true,
-            'type' => $plugintype,
-            'name' => $pluginname,
-            'typeroot' => $pluginrootpaths[$plugintype],
-            'package' => $info->download,
-            'md5' => $info->downloadmd5,
-            'dataroot' => $CFG->dataroot,
-            'dirroot' => $CFG->dirroot,
-            'passfile' => $passfile,
-            'password' => $password,
-            'returnurl' => $returnurl->out(false),
-        );
-
-        if (!empty($CFG->proxyhost)) {
-            // MDL-36973 - Beware - we should call just !is_proxybypass() here. But currently, our
-            // cURL wrapper class does not do it. So, to have consistent behaviour, we pass proxy
-            // setting regardless the $CFG->proxybypass setting. Once the {@link curl} class is
-            // fixed, the condition should be amended.
-            if (true or !is_proxybypass($info->download)) {
-                if (empty($CFG->proxyport)) {
-                    $params['proxy'] = $CFG->proxyhost;
-                } else {
-                    $params['proxy'] = $CFG->proxyhost.':'.$CFG->proxyport;
-                }
-
-                if (!empty($CFG->proxyuser) and !empty($CFG->proxypassword)) {
-                    $params['proxyuserpwd'] = $CFG->proxyuser.':'.$CFG->proxypassword;
-                }
-
-                if (!empty($CFG->proxytype)) {
-                    $params['proxytype'] = $CFG->proxytype;
-                }
-            }
-        }
-
-        $widget = new \single_button(
-            new moodle_url('/mdeploy.php', $params),
-            get_string('updateavailableinstall', 'core_admin'),
-            'post'
-        );
-
-        return $widget;
-    }
-
-    /**
-     * Returns array of data objects passed to this tool.
-     *
-     * @return array
-     */
-    public function submitted_data() {
-        $component = optional_param('updateaddon', '', PARAM_COMPONENT);
-        $version = optional_param('version', '', PARAM_RAW);
-        if (!$component or !$version) {
-            return false;
-        }
-
-        $plugininfo = \core_plugin_manager::instance()->get_plugin_info($component);
-        if (!$plugininfo) {
-            return false;
-        }
-
-        if ($plugininfo->is_standard()) {
-            return false;
-        }
-
-        if (!$updates = $plugininfo->available_updates()) {
-            return false;
-        }
-
-        $info = null;
-        foreach ($updates as $update) {
-            if ($update->version == $version) {
-                $info = $update;
-                break;
-            }
-        }
-        if (!$info) {
-            return false;
-        }
-
-        $data = array(
-            'updateaddon' => $component,
-            'updateinfo'  => $info,
-            'callerurl'   => optional_param('callerurl', null, PARAM_URL),
-            'returnurl'   => optional_param('returnurl', null, PARAM_URL),
-        );
-        if ($data['callerurl']) {
-            $data['callerurl'] = new moodle_url($data['callerurl']);
-        }
-        if ($data['callerurl']) {
-            $data['returnurl'] = new moodle_url($data['returnurl']);
-        }
-
-        return $data;
-    }
-
-    /**
-     * Handles magic getters and setters for protected properties.
-     *
-     * @param string $name method name, e.g. set_returnurl()
-     * @param array $arguments arguments to be passed to the array
-     */
-    public function __call($name, array $arguments = array()) {
-
-        if (substr($name, 0, 4) === 'set_') {
-            $property = substr($name, 4);
-            if (empty($property)) {
-                throw new coding_exception('Invalid property name (empty)');
-            }
-            if (empty($arguments)) {
-                $arguments = array(true); // Default value for flag-like properties.
-            }
-            // Make sure it is a protected property.
-            $isprotected = false;
-            $reflection = new \ReflectionObject($this);
-            foreach ($reflection->getProperties(\ReflectionProperty::IS_PROTECTED) as $reflectionproperty) {
-                if ($reflectionproperty->getName() === $property) {
-                    $isprotected = true;
-                    break;
-                }
-            }
-            if (!$isprotected) {
-                throw new coding_exception('Unable to set property - it does not exist or it is not protected');
-            }
-            $value = reset($arguments);
-            $this->$property = $value;
-            return;
-        }
-
-        if (substr($name, 0, 4) === 'get_') {
-            $property = substr($name, 4);
-            if (empty($property)) {
-                throw new coding_exception('Invalid property name (empty)');
-            }
-            if (!empty($arguments)) {
-                throw new coding_exception('No parameter expected');
-            }
-            // Make sure it is a protected property.
-            $isprotected = false;
-            $reflection = new \ReflectionObject($this);
-            foreach ($reflection->getProperties(\ReflectionProperty::IS_PROTECTED) as $reflectionproperty) {
-                if ($reflectionproperty->getName() === $property) {
-                    $isprotected = true;
-                    break;
-                }
-            }
-            if (!$isprotected) {
-                throw new coding_exception('Unable to get property - it does not exist or it is not protected');
-            }
-            return $this->$property;
-        }
-    }
-
-    /**
-     * Generates a random token and stores it in a file in moodledata directory.
-     *
-     * @return array of the (string)filename and (string)password in this order
-     */
-    public function prepare_authorization() {
-        global $CFG;
-
-        make_upload_directory('mdeploy/auth/');
-
-        $attempts = 0;
-        $success = false;
-
-        while (!$success and $attempts < 5) {
-            $attempts++;
-
-            $passfile = $this->generate_passfile();
-            $password = $this->generate_password();
-            $now = time();
-
-            $filepath = $CFG->dataroot.'/mdeploy/auth/'.$passfile;
-
-            if (!file_exists($filepath)) {
-                $success = file_put_contents($filepath, $password . PHP_EOL . $now . PHP_EOL, LOCK_EX);
-                chmod($filepath, $CFG->filepermissions);
-            }
-        }
-
-        if ($success) {
-            return array($passfile, $password);
-
-        } else {
-            throw new \moodle_exception('unable_prepare_authorization', 'core_plugin');
-        }
-    }
-
-    /* === End of external API === */
-
-    /**
-     * Returns a random string to be used as a filename of the password storage.
-     *
-     * @return string
-     */
-    protected function generate_passfile() {
-        return clean_param(uniqid('mdeploy_', true), PARAM_FILE);
-    }
-
-    /**
-     * Returns a random string to be used as the authorization token
-     *
-     * @return string
-     */
-    protected function generate_password() {
-        return complex_random_string();
-    }
-
-    /**
-     * Checks if the given component's directory is writable
-     *
-     * For the purpose of the deployment, the web server process has to have
-     * write access to all files in the component's directory (recursively) and for the
-     * directory itself.
-     *
-     * @see worker::move_directory_source_precheck()
-     * @param string $component normalized component name
-     * @return boolean
-     */
-    protected function component_writable($component) {
-
-        list($plugintype, $pluginname) = core_component::normalize_component($component);
-
-        $directory = core_component::get_plugin_directory($plugintype, $pluginname);
-
-        if (is_null($directory)) {
-            // Plugin unknown, most probably deleted or missing during upgrade,
-            // look at the parent directory instead because they might want to install it.
-            $plugintypes = core_component::get_plugin_types();
-            if (!isset($plugintypes[$plugintype])) {
-                throw new coding_exception('Unknown component location', $component);
-            }
-            $directory = $plugintypes[$plugintype];
-        }
-
-        return $this->directory_writable($directory);
-    }
-
-    /**
-     * Checks if the mdeploy.php will be able to fetch the ZIP from the given URL
-     *
-     * This is mainly supposed to check if the transmission over HTTPS would
-     * work. That is, if the CA certificates are present at the server.
-     *
-     * @param string $downloadurl the URL of the ZIP package to download
-     * @return bool
-     */
-    protected function update_downloadable($downloadurl) {
-        global $CFG;
-
-        $curloptions = array(
-            'CURLOPT_SSL_VERIFYHOST' => 2,      // This is the default in {@link curl} class but just in case.
-            'CURLOPT_SSL_VERIFYPEER' => true,
-        );
-
-        $curl = new \curl(array('proxy' => true));
-        $result = $curl->head($downloadurl, $curloptions);
-        $errno = $curl->get_errno();
-        if (empty($errno)) {
-            return true;
-        } else {
-            return false;
-        }
-    }
-
-    /**
-     * Checks if the directory and all its contents (recursively) is writable
-     *
-     * @param string $path full path to a directory
-     * @return boolean
-     */
-    private function directory_writable($path) {
-
-        if (!is_writable($path)) {
-            return false;
-        }
-
-        if (is_dir($path)) {
-            $handle = opendir($path);
-        } else {
-            return false;
-        }
-
-        $result = true;
-
-        while ($filename = readdir($handle)) {
-            $filepath = $path.'/'.$filename;
-
-            if ($filename === '.' or $filename === '..') {
-                continue;
-            }
-
-            if (is_dir($filepath)) {
-                $result = $result && $this->directory_writable($filepath);
-
-            } else {
-                $result = $result && is_writable($filepath);
-            }
-        }
-
-        closedir($handle);
-
-        return $result;
-    }
-}
index cc1c64a..a3d7233 100644 (file)
@@ -240,9 +240,6 @@ class phpunit_util extends testing_util {
         if (class_exists('\core\update\checker')) {
             \core\update\checker::reset_caches(true);
         }
-        if (class_exists('\core\update\deployer')) {
-            \core\update\deployer::reset_caches(true);
-        }
 
         // Clear static cache within restore.
         if (class_exists('restore_section_structure_step')) {
diff --git a/mdeploy.php b/mdeploy.php
deleted file mode 100644 (file)
index 26c5caa..0000000
+++ /dev/null
@@ -1,1574 +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/>.
-
-/**
- * Moodle deployment utility
- *
- * This script looks after deploying new add-ons and available updates for them
- * to the local Moodle site. It can operate via both HTTP and CLI mode.
- * Moodle itself calls this utility via the HTTP mode when the admin is about to
- * install or update an add-on. You can use the CLI mode in your custom deployment
- * shell scripts.
- *
- * CLI usage example:
- *
- *  $ sudo -u apache php mdeploy.php --install \
- *                                   --package=https://moodle.org/plugins/download.php/...zip \
- *                                   --typeroot=/var/www/moodle/htdocs/blocks
- *                                   --name=loancalc
- *                                   --md5=...
- *
- *  $ sudo -u apache php mdeploy.php --upgrade \
- *                                   --package=https://moodle.org/plugins/download.php/...zip \
- *                                   --typeroot=/var/www/moodle/htdocs/blocks
- *                                   --name=loancalc
- *                                   --md5=...
- *
- * When called via HTTP, additional parameters returnurl, passfile and password must be
- * provided. Optional proxy configuration can be passed using parameters proxy, proxytype
- * and proxyuserpwd.
- *
- * Changes
- *
- * 1.1 - Added support to install a new plugin from the Moodle Plugins directory.
- * 1.0 - Initial version used in Moodle 2.4 to deploy available updates.
- *
- * @package     core
- * @subpackage  mdeploy
- * @version     1.1
- * @copyright   2012 David Mudrak <david@moodle.com>
- * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-if (defined('MOODLE_INTERNAL')) {
-    die('This is a standalone utility that should not be included by any other Moodle code.');
-}
-
-// This stops immediately at the beginning of lib/setup.php.
-define('ABORT_AFTER_CONFIG', true);
-if (PHP_SAPI === 'cli') {
-    // Called from the CLI - we need to set CLI_SCRIPT to ensure that appropriate CLI checks are made in setup.php.
-    define('CLI_SCRIPT', true);
-}
-require(__DIR__ . '/config.php');
-
-// Exceptions //////////////////////////////////////////////////////////////////
-
-class invalid_coding_exception extends Exception {}
-class missing_option_exception extends Exception {}
-class invalid_option_exception extends Exception {}
-class unauthorized_access_exception extends Exception {}
-class download_file_exception extends Exception {}
-class backup_folder_exception extends Exception {}
-class zip_exception extends Exception {}
-class filesystem_exception extends Exception {}
-class checksum_exception extends Exception {}
-class invalid_setting_exception extends Exception {}
-
-
-// Various support classes /////////////////////////////////////////////////////
-
-/**
- * Base class implementing the singleton pattern using late static binding feature.
- *
- * @copyright 2012 David Mudrak <david@moodle.com>
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-abstract class singleton_pattern {
-
-    /** @var array singleton_pattern instances */
-    protected static $singletoninstances = array();
-
-    /**
-     * Factory method returning the singleton instance.
-     *
-     * Subclasses may want to override the {@link self::initialize()} method that is
-     * called right after their instantiation.
-     *
-     * @return mixed the singleton instance
-     */
-    final public static function instance() {
-        $class = get_called_class();
-        if (!isset(static::$singletoninstances[$class])) {
-            static::$singletoninstances[$class] = new static();
-            static::$singletoninstances[$class]->initialize();
-        }
-        return static::$singletoninstances[$class];
-    }
-
-    /**
-     * Optional post-instantiation code.
-     */
-    protected function initialize() {
-        // Do nothing in this base class.
-    }
-
-    /**
-     * Direct instantiation not allowed, use the factory method {@link instance()}
-     */
-    final protected function __construct() {
-    }
-
-    /**
-     * Sorry, this is singleton.
-     */
-    final protected function __clone() {
-    }
-}
-
-
-// User input handling /////////////////////////////////////////////////////////
-
-/**
- * Provides access to the script options.
- *
- * Implements the delegate pattern by dispatching the calls to appropriate
- * helper class (CLI or HTTP).
- *
- * @copyright 2012 David Mudrak <david@moodle.com>
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class input_manager extends singleton_pattern {
-
-    const TYPE_FILE         = 'file';   // File name
-    const TYPE_FLAG         = 'flag';   // No value, just a flag (switch)
-    const TYPE_INT          = 'int';    // Integer
-    const TYPE_PATH         = 'path';   // Full path to a file or a directory
-    const TYPE_RAW          = 'raw';    // Raw value, keep as is
-    const TYPE_URL          = 'url';    // URL to a file
-    const TYPE_PLUGIN       = 'plugin'; // Plugin name
-    const TYPE_MD5          = 'md5';    // MD5 hash
-
-    /** @var input_cli_provider|input_http_provider the provider of the input */
-    protected $inputprovider = null;
-
-    /**
-     * Returns the value of an option passed to the script.
-     *
-     * If the caller passes just the $name, the requested argument is considered
-     * required. The caller may specify the second argument which then
-     * makes the argument optional with the given default value.
-     *
-     * If the type of the $name option is TYPE_FLAG (switch), this method returns
-     * true if the flag has been passed or false if it was not. Specifying the
-     * default value makes no sense in this case and leads to invalid coding exception.
-     *
-     * The array options are not supported.
-     *
-     * @example $filename = $input->get_option('f');
-     * @example $filename = $input->get_option('filename');
-     * @example if ($input->get_option('verbose')) { ... }
-     * @param string $name
-     * @return mixed
-     */
-    public function get_option($name, $default = 'provide_default_value_explicitly') {
-
-        $this->validate_option_name($name);
-
-        $info = $this->get_option_info($name);
-
-        if ($info->type === input_manager::TYPE_FLAG) {
-            return $this->inputprovider->has_option($name);
-        }
-
-        if (func_num_args() == 1) {
-            return $this->get_required_option($name);
-        } else {
-            return $this->get_optional_option($name, $default);
-        }
-    }
-
-    /**
-     * Returns the meta-information about the given option.
-     *
-     * @param string|null $name short or long option name, defaults to returning the list of all
-     * @return array|object|false array with all, object with the specific option meta-information or false of no such an option
-     */
-    public function get_option_info($name=null) {
-
-        $supportedoptions = array(
-            array('', 'passfile', input_manager::TYPE_FILE, 'File name of the passphrase file (HTTP access only)'),
-            array('', 'password', input_manager::TYPE_RAW, 'Session passphrase (HTTP access only)'),
-            array('', 'proxy', input_manager::TYPE_RAW, 'HTTP proxy host and port (e.g. \'our.proxy.edu:8888\')'),
-            array('', 'proxytype', input_manager::TYPE_RAW, 'Proxy type (HTTP or SOCKS5)'),
-            array('', 'proxyuserpwd', input_manager::TYPE_RAW, 'Proxy username and password (e.g. \'username:password\')'),
-            array('', 'returnurl', input_manager::TYPE_URL, 'Return URL (HTTP access only)'),
-            array('h', 'help', input_manager::TYPE_FLAG, 'Prints usage information'),
-            array('i', 'install', input_manager::TYPE_FLAG, 'Installation mode'),
-            array('m', 'md5', input_manager::TYPE_MD5, 'Expected MD5 hash of the ZIP package to deploy'),
-            array('n', 'name', input_manager::TYPE_PLUGIN, 'Plugin name (the name of its folder)'),
-            array('p', 'package', input_manager::TYPE_URL, 'URL to the ZIP package to deploy'),
-            array('r', 'typeroot', input_manager::TYPE_PATH, 'Full path of the container for this plugin type'),
-            array('u', 'upgrade', input_manager::TYPE_FLAG, 'Upgrade mode'),
-        );
-
-        if (is_null($name)) {
-            $all = array();
-            foreach ($supportedoptions as $optioninfo) {
-                $info = new stdClass();
-                $info->shortname = $optioninfo[0];
-                $info->longname = $optioninfo[1];
-                $info->type = $optioninfo[2];
-                $info->desc = $optioninfo[3];
-                $all[] = $info;
-            }
-            return $all;
-        }
-
-        $found = false;
-
-        foreach ($supportedoptions as $optioninfo) {
-            if (strlen($name) == 1) {
-                // Search by the short option name
-                if ($optioninfo[0] === $name) {
-                    $found = $optioninfo;
-                    break;
-                }
-            } else {
-                // Search by the long option name
-                if ($optioninfo[1] === $name) {
-                    $found = $optioninfo;
-                    break;
-                }
-            }
-        }
-
-        if (!$found) {
-            return false;
-        }
-
-        $info = new stdClass();
-        $info->shortname = $found[0];
-        $info->longname = $found[1];
-        $info->type = $found[2];
-        $info->desc = $found[3];
-
-        return $info;
-    }
-
-    /**
-     * Casts the value to the given type.
-     *
-     * @param mixed $raw the raw value
-     * @param string $type the expected value type, e.g. {@link input_manager::TYPE_INT}
-     * @return mixed
-     */
-    public function cast_value($raw, $type) {
-
-        if (is_array($raw)) {
-            throw new invalid_coding_exception('Unsupported array option.');
-        } else if (is_object($raw)) {
-            throw new invalid_coding_exception('Unsupported object option.');
-        }
-
-        switch ($type) {
-
-            case input_manager::TYPE_FILE:
-                $raw = preg_replace('~[[:cntrl:]]|[&<>"`\|\':\\\\/]~u', '', $raw);
-                $raw = preg_replace('~\.\.+~', '', $raw);
-                if ($raw === '.') {
-                    $raw = '';
-                }
-                return $raw;
-
-            case input_manager::TYPE_FLAG:
-                return true;
-
-            case input_manager::TYPE_INT:
-                return (int)$raw;
-
-            case input_manager::TYPE_PATH:
-                if (strpos($raw, '~') !== false) {
-                    throw new invalid_option_exception('Using the tilde (~) character in paths is not supported');
-                }
-                $colonpos = strpos($raw, ':');
-                if ($colonpos !== false) {
-                    if ($colonpos !== 1 or strrpos($raw, ':') !== 1) {
-                        throw new invalid_option_exception('Using the colon (:) character in paths is supported for Windows drive labels only.');
-                    }
-                    if (preg_match('/^[a-zA-Z]:/', $raw) !== 1) {
-                        throw new invalid_option_exception('Using the colon (:) character in paths is supported for Windows drive labels only.');
-                    }
-                }
-                $raw = str_replace('\\', '/', $raw);
-                $raw = preg_replace('~[[:cntrl:]]|[&<>"`\|\']~u', '', $raw);
-                $raw = preg_replace('~\.\.+~', '', $raw);
-                $raw = preg_replace('~//+~', '/', $raw);
-                $raw = preg_replace('~/(\./)+~', '/', $raw);
-                return $raw;
-
-            case input_manager::TYPE_RAW:
-                return $raw;
-
-            case input_manager::TYPE_URL:
-                $regex  = '^(https?|ftp)\:\/\/'; // protocol
-                $regex .= '([a-z0-9+!*(),;?&=\$_.-]+(\:[a-z0-9+!*(),;?&=\$_.-]+)?@)?'; // optional user and password
-                $regex .= '[a-z0-9+\$_-]+(\.[a-z0-9+\$_-]+)*'; // hostname or IP (one word like http://localhost/ allowed)
-                $regex .= '(\:[0-9]{2,5})?'; // port (optional)
-                $regex .= '(\/([a-z0-9+\$_-]\.?)+)*\/?'; // path to the file
-                $regex .= '(\?[a-z+&\$_.-][a-z0-9;:@/&%=+\$_.-]*)?'; // HTTP params
-
-                if (preg_match('#'.$regex.'#i', $raw)) {
-                    return $raw;
-                } else {
-                    throw new invalid_option_exception('Not a valid URL');
-                }
-
-            case input_manager::TYPE_PLUGIN:
-                if (!preg_match('/^[a-z][a-z0-9_]*[a-z0-9]$/', $raw)) {
-                    throw new invalid_option_exception('Invalid plugin name');
-                }
-                if (strpos($raw, '__') !== false) {
-                    throw new invalid_option_exception('Invalid plugin name');
-                }
-                return $raw;
-
-            case input_manager::TYPE_MD5:
-                if (!preg_match('/^[a-f0-9]{32}$/', $raw)) {
-                    throw new invalid_option_exception('Invalid MD5 hash format');
-                }
-                return $raw;
-
-            default:
-                throw new invalid_coding_exception('Unknown option type.');
-
-        }
-    }
-
-    /**
-     * Picks the appropriate helper class to delegate calls to.
-     */
-    protected function initialize() {
-        if (PHP_SAPI === 'cli') {
-            $this->inputprovider = input_cli_provider::instance();
-        } else {
-            $this->inputprovider = input_http_provider::instance();
-        }
-    }
-
-    // End of external API
-
-    /**
-     * Validates the parameter name.
-     *
-     * @param string $name
-     * @throws invalid_coding_exception
-     */
-    protected function validate_option_name($name) {
-
-        if (empty($name)) {
-            throw new invalid_coding_exception('Invalid empty option name.');
-        }
-
-        $meta = $this->get_option_info($name);
-        if (empty($meta)) {
-            throw new invalid_coding_exception('Invalid option name: '.$name);
-        }
-    }
-
-    /**
-     * Returns cleaned option value or throws exception.
-     *
-     * @param string $name the name of the parameter
-     * @param string $type the parameter type, e.g. {@link input_manager::TYPE_INT}
-     * @return mixed
-     */
-    protected function get_required_option($name) {
-        if ($this->inputprovider->has_option($name)) {
-            return $this->inputprovider->get_option($name);
-        } else {
-            throw new missing_option_exception('Missing required option: '.$name);
-        }
-    }
-
-    /**
-     * Returns cleaned option value or the default value
-     *
-     * @param string $name the name of the parameter
-     * @param string $type the parameter type, e.g. {@link input_manager::TYPE_INT}
-     * @param mixed $default the default value.
-     * @return mixed
-     */
-    protected function get_optional_option($name, $default) {
-        if ($this->inputprovider->has_option($name)) {
-            return $this->inputprovider->get_option($name);
-        } else {
-            return $default;
-        }
-    }
-}
-
-
-/**
- * Base class for input providers.
- *
- * @copyright 2012 David Mudrak <david@moodle.com>
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-abstract class input_provider extends singleton_pattern {
-
-    /** @var array list of all passed valid options */
-    protected $options = array();
-
-    /**
-     * Returns the casted value of the option.
-     *
-     * @param string $name option name
-     * @throws invalid_coding_exception if the option has not been passed
-     * @return mixed casted value of the option
-     */
-    public function get_option($name) {
-
-        if (!$this->has_option($name)) {
-            throw new invalid_coding_exception('Option not passed: '.$name);
-        }
-
-        return $this->options[$name];
-    }
-
-    /**
-     * Was the given option passed?
-     *
-     * @param string $name optionname
-     * @return bool
-     */
-    public function has_option($name) {
-        return array_key_exists($name, $this->options);
-    }
-
-    /**
-     * Initializes the input provider.
-     */
-    protected function initialize() {
-        $this->populate_options();
-    }
-
-    // End of external API
-
-    /**
-     * Parses and validates all supported options passed to the script.
-     */
-    protected function populate_options() {
-
-        $input = input_manager::instance();
-        $raw = $this->parse_raw_options();
-        $cooked = array();
-
-        foreach ($raw as $k => $v) {
-            if (is_array($v) or is_object($v)) {
-                // Not supported.
-            }
-
-            $info = $input->get_option_info($k);
-            if (!$info) {
-                continue;
-            }
-
-            $casted = $input->cast_value($v, $info->type);
-
-            if (!empty($info->shortname)) {
-                $cooked[$info->shortname] = $casted;
-            }
-
-            if (!empty($info->longname)) {
-                $cooked[$info->longname] = $casted;
-            }
-        }
-
-        // Store the options.
-        $this->options = $cooked;
-    }
-}
-
-
-/**
- * Provides access to the script options passed via CLI.
- *
- * @copyright 2012 David Mudrak <david@moodle.com>
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class input_cli_provider extends input_provider {
-
-    /**
-     * Parses raw options passed to the script.
-     *
-     * @return array as returned by getopt()
-     */
-    protected function parse_raw_options() {
-
-        $input = input_manager::instance();
-
-        // Signatures of some in-built PHP functions are just crazy, aren't they.
-        $short = '';
-        $long = array();
-
-        foreach ($input->get_option_info() as $option) {
-            if ($option->type === input_manager::TYPE_FLAG) {
-                // No value expected for this option.
-                $short .= $option->shortname;
-                $long[] = $option->longname;
-            } else {
-                // A value expected for the option, all considered as optional.
-                $short .= empty($option->shortname) ? '' : $option->shortname.'::';
-                $long[] = empty($option->longname) ? '' : $option->longname.'::';
-            }
-        }
-
-        return getopt($short, $long);
-    }
-}
-
-
-/**
- * Provides access to the script options passed via HTTP request.
- *
- * @copyright 2012 David Mudrak <david@moodle.com>
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class input_http_provider extends input_provider {
-
-    /**
-     * Parses raw options passed to the script.
-     *
-     * @return array of raw values passed via HTTP request
-     */
-    protected function parse_raw_options() {
-        return $_POST;
-    }
-}
-
-
-// Output handling /////////////////////////////////////////////////////////////
-
-/**
- * Provides output operations.
- *
- * @copyright 2012 David Mudrak <david@moodle.com>
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class output_manager extends singleton_pattern {
-
-    /** @var output_cli_provider|output_http_provider the provider of the output functionality */
-    protected $outputprovider = null;
-
-    /**
-     * Magic method triggered when invoking an inaccessible method.
-     *
-     * @param string $name method name
-     * @param array $arguments method arguments
-     */
-    public function __call($name, array $arguments = array()) {
-        call_user_func_array(array($this->outputprovider, $name), $arguments);
-    }
-
-    /**
-     * Picks the appropriate helper class to delegate calls to.
-     */
-    protected function initialize() {
-        if (PHP_SAPI === 'cli') {
-            $this->outputprovider = output_cli_provider::instance();
-        } else {
-            $this->outputprovider = output_http_provider::instance();
-        }
-    }
-}
-
-
-/**
- * Base class for all output providers.
- *
- * @copyright 2012 David Mudrak <david@moodle.com>
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-abstract class output_provider extends singleton_pattern {
-}
-
-/**
- * Provides output to the command line.
- *
- * @copyright 2012 David Mudrak <david@moodle.com>
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class output_cli_provider extends output_provider {
-
-    /**
-     * Prints help information in CLI mode.
-     */
-    public function help() {
-
-        $this->outln('mdeploy.php - Moodle (http://moodle.org) deployment utility');
-        $this->outln();
-        $this->outln('Usage: $ sudo -u apache php mdeploy.php [options]');
-        $this->outln();
-        $input = input_manager::instance();
-        foreach($input->get_option_info() as $info) {
-            $option = array();
-            if (!empty($info->shortname)) {
-                $option[] = '-'.$info->shortname;
-            }
-            if (!empty($info->longname)) {
-                $option[] = '--'.$info->longname;
-            }
-            $this->outln(sprintf('%-20s %s', implode(', ', $option), $info->desc));
-        }
-    }
-
-    // End of external API
-
-    /**
-     * Writes a text to the STDOUT followed by a new line character.
-     *
-     * @param string $text text to print
-     */
-    protected function outln($text='') {
-        fputs(STDOUT, $text.PHP_EOL);
-    }
-}
-
-
-/**
- * Provides HTML output as a part of HTTP response.
- *
- * @copyright 2012 David Mudrak <david@moodle.com>
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class output_http_provider extends output_provider {
-
-    /**
-     * Prints help on the script usage.
-     */
-    public function help() {
-        // No help available via HTTP
-    }
-
-    /**
-     * Display the information about uncaught exception
-     *
-     * @param Exception $e uncaught exception
-     */
-    public function exception(Exception $e) {
-
-        $docslink = 'http://docs.moodle.org/en/admin/mdeploy/'.get_class($e);
-        $this->start_output();
-        echo('<h1>Oops! It did it again</h1>');
-        echo('<p><strong>Moodle deployment utility had a trouble with your request.
-            See <a href="'.$docslink.'">the docs page</a> and the debugging information for more details.</strong></p>');
-        echo('<pre>');
-        echo exception_handlers::format_exception_info($e);
-        echo('</pre>');
-        $this->end_output();
-    }
-
-    // End of external API
-
-    /**
-     * Produce the HTML page header
-     */
-    protected function start_output() {
-        echo '<!doctype html>
-<html lang="en">
-<head>
-  <meta charset="utf-8">
-  <style type="text/css">
-    body {background-color:#666;font-family:"DejaVu Sans","Liberation Sans",Freesans,sans-serif;}
-    h1 {text-align:center;}
-    pre {white-space: pre-wrap;}
-    #page {background-color:#eee;width:1024px;margin:5em auto;border:3px solid #333;border-radius: 15px;padding:1em;}
-  </style>
-</head>
-<body>
-<div id="page">';
-    }
-
-    /**
-     * Produce the HTML page footer
-     */
-    protected function end_output() {
-        echo '</div></body></html>';
-    }
-}
-
-// The main class providing all the functionality //////////////////////////////
-
-/**
- * The actual worker class implementing the main functionality of the script.
- *
- * @copyright 2012 David Mudrak <david@moodle.com>
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class worker extends singleton_pattern {
-
-    const EXIT_OK                       = 0;    // Success exit code.
-    const EXIT_HELP                     = 1;    // Explicit help required.
-    const EXIT_UNKNOWN_ACTION           = 127;  // Neither -i nor -u provided.
-
-    /** @var input_manager */
-    protected $input = null;
-
-    /** @var output_manager */
-    protected $output = null;
-
-    /** @var int the most recent cURL error number, zero for no error */
-    private $curlerrno = null;
-
-    /** @var string the most recent cURL error message, empty string for no error */
-    private $curlerror = null;
-
-    /** @var array|false the most recent cURL request info, if it was successful */
-    private $curlinfo = null;
-
-    /** @var string the full path to the log file */
-    private $logfile = null;
-
-    /** @var array the whitelisted config options which can be queried. */
-    private $validconfigoptions = array(
-        'dirroot'       => true,
-        'dataroot'      => true,
-    );
-
-    /**
-     * Main - the one that actually does something
-     */
-    public function execute() {
-
-        $this->log('=== MDEPLOY EXECUTION START ===');
-
-        // Authorize access. None in CLI. Passphrase in HTTP.
-        $this->authorize();
-
-        // Asking for help in the CLI mode.
-        if ($this->input->get_option('help')) {
-            $this->output->help();
-            $this->done(self::EXIT_HELP);
-        }
-
-        if ($this->input->get_option('upgrade')) {
-            $this->log('Plugin upgrade requested');
-
-            // Fetch the ZIP file into a temporary location.
-            $source = $this->input->get_option('package');
-            $target = $this->target_location($source);
-            $this->log('Downloading package '.$source);
-
-            if ($this->download_file($source, $target)) {
-                $this->log('Package downloaded into '.$target);
-            } else {
-                $this->log('cURL error ' . $this->curlerrno . ' ' . $this->curlerror);
-                $this->log('Unable to download the file from ' . $source . ' into ' . $target);
-                throw new download_file_exception('Unable to download the package');
-            }
-
-            // Compare MD5 checksum of the ZIP file
-            $md5remote = $this->input->get_option('md5');
-            $md5local = md5_file($target);
-
-            if ($md5local !== $md5remote) {
-                $this->log('MD5 checksum failed. Expected: '.$md5remote.' Got: '.$md5local);
-                throw new checksum_exception('MD5 checksum failed');
-            }
-            $this->log('MD5 checksum ok');
-
-            // Check that the specified typeroot is within the current site's dirroot.
-            $plugintyperoot = $this->input->get_option('typeroot');
-            if (strpos(realpath($plugintyperoot), realpath($this->get_env('dirroot'))) !== 0) {
-                throw new backup_folder_exception('Unable to backup the current version of the plugin (typeroot is invalid)');
-            }
-
-            // Backup the current version of the plugin
-            $pluginname = $this->input->get_option('name');
-            $sourcelocation = $plugintyperoot.'/'.$pluginname;
-            $backuplocation = $this->backup_location($sourcelocation);
-
-            $this->log('Current plugin code location: '.$sourcelocation);
-            $this->log('Moving the current code into archive: '.$backuplocation);
-
-            if (file_exists($sourcelocation)) {
-                // We don't want to touch files unless we are pretty sure it would be all ok.
-                if (!$this->move_directory_source_precheck($sourcelocation)) {
-                    throw new backup_folder_exception('Unable to backup the current version of the plugin (source precheck failed)');
-                }
-                if (!$this->move_directory_target_precheck($backuplocation)) {
-                    throw new backup_folder_exception('Unable to backup the current version of the plugin (backup precheck failed)');
-                }
-
-                // Looking good, let's try it.
-                if (!$this->move_directory($sourcelocation, $backuplocation, true)) {
-                    throw new backup_folder_exception('Unable to backup the current version of the plugin (moving failed)');
-                }
-
-            } else {
-                // Upgrading missing plugin - this happens often during upgrades.
-                if (!$this->create_directory_precheck($sourcelocation)) {
-                    throw new filesystem_exception('Unable to prepare the plugin location (cannot create new directory)');
-                }
-            }
-
-            // Unzip the plugin package file into the target location.
-            $this->unzip_plugin($target, $plugintyperoot, $sourcelocation, $backuplocation);
-            $this->log('Package successfully extracted');
-
-            // Redirect to the given URL (in HTTP) or exit (in CLI).
-            $this->done();
-
-        } else if ($this->input->get_option('install')) {
-            $this->log('Plugin installation requested');
-
-            $plugintyperoot = $this->input->get_option('typeroot');
-            $pluginname     = $this->input->get_option('name');
-            $source         = $this->input->get_option('package');
-            $md5remote      = $this->input->get_option('md5');
-
-            if (strpos(realpath($plugintyperoot), realpath($this->get_env('dirroot'))) !== 0) {
-                throw new backup_folder_exception('Unable to prepare the plugin location (typeroot is invalid)');
-            }
-
-            // Check if the plugin location if available for us.
-            $pluginlocation = $plugintyperoot.'/'.$pluginname;
-
-            $this->log('New plugin code location: '.$pluginlocation);
-
-            if (file_exists($pluginlocation)) {
-                throw new filesystem_exception('Unable to prepare the plugin location (directory already exists)');
-            }
-
-            if (!$this->create_directory_precheck($pluginlocation)) {
-                throw new filesystem_exception('Unable to prepare the plugin location (cannot create new directory)');
-            }
-
-            // Fetch the ZIP file into a temporary location.
-            $target = $this->target_location($source);
-            $this->log('Downloading package '.$source);
-
-            if ($this->download_file($source, $target)) {
-                $this->log('Package downloaded into '.$target);
-            } else {
-                $this->log('cURL error ' . $this->curlerrno . ' ' . $this->curlerror);
-                $this->log('Unable to download the file');
-                throw new download_file_exception('Unable to download the package');
-            }
-
-            // Compare MD5 checksum of the ZIP file
-            $md5local = md5_file($target);
-
-            if ($md5local !== $md5remote) {
-                $this->log('MD5 checksum failed. Expected: '.$md5remote.' Got: '.$md5local);
-                throw new checksum_exception('MD5 checksum failed');
-            }
-            $this->log('MD5 checksum ok');
-
-            // Unzip the plugin package file into the plugin location.
-            $this->unzip_plugin($target, $plugintyperoot, $pluginlocation, false);
-            $this->log('Package successfully extracted');
-
-            // Redirect to the given URL (in HTTP) or exit (in CLI).
-            $this->done();
-        }
-
-        // Print help in CLI by default.
-        $this->output->help();
-        $this->done(self::EXIT_UNKNOWN_ACTION);
-    }
-
-    /**
-     * Attempts to log a thrown exception
-     *
-     * @param Exception $e uncaught exception
-     */
-    public function log_exception(Exception $e) {
-        $this->log($e->__toString());
-    }
-
-    /**
-     * Initialize the worker class.
-     */
-    protected function initialize() {
-        $this->input = input_manager::instance();
-        $this->output = output_manager::instance();
-    }
-
-    // End of external API
-
-    /**
-     * Finish this script execution.
-     *
-     * @param int $exitcode
-     */
-    protected function done($exitcode = self::EXIT_OK) {
-
-        if (PHP_SAPI === 'cli') {
-            exit($exitcode);
-
-        } else {
-            $returnurl = $this->input->get_option('returnurl');
-            $this->redirect($returnurl);
-            exit($exitcode);
-        }
-    }
-
-    /**
-     * Authorize access to the script.
-     *
-     * In CLI mode, the access is automatically authorized. In HTTP mode, the
-     * passphrase submitted via the request params must match the contents of the
-     * file, the name of which is passed in another parameter.
-     *
-     * @throws unauthorized_access_exception
-     */
-    protected function authorize() {
-        if (PHP_SAPI === 'cli') {
-            $this->log('Successfully authorized using the CLI SAPI');
-            return;
-        }
-
-        $passfile = $this->input->get_option('passfile');
-        $password = $this->input->get_option('password');
-
-        $passpath = $this->get_env('dataroot') . '/mdeploy/auth/' . $passfile;
-
-        if (!is_readable($passpath)) {
-            throw new unauthorized_access_exception('Unable to read the passphrase file.');
-        }
-
-        $stored = file($passpath, FILE_IGNORE_NEW_LINES);
-
-        // "This message will self-destruct in five seconds." -- Mission Commander Swanbeck, Mission: Impossible II
-        unlink($passpath);
-
-        if (is_readable($passpath)) {
-            throw new unauthorized_access_exception('Unable to remove the passphrase file.');
-        }
-
-        if (count($stored) < 2) {
-            throw new unauthorized_access_exception('Invalid format of the passphrase file.');
-        }
-
-        if (time() - (int)$stored[1] > 30 * 60) {
-            throw new unauthorized_access_exception('Passphrase timeout.');
-        }
-
-        if (strlen($stored[0]) < 24) {
-            throw new unauthorized_access_exception('Session passphrase not long enough.');
-        }
-
-        if ($password !== $stored[0]) {
-            throw new unauthorized_access_exception('Session passphrase does not match the stored one.');
-        }
-
-        $this->log('Successfully authorized using the passphrase file');
-    }
-
-    /**
-     * Returns the full path to the log file.
-     *
-     * @return string
-     */
-    protected function log_location() {
-        if (!is_null($this->logfile)) {
-            return $this->logfile;
-        }
-
-        $dataroot = $this->get_env('dataroot');
-
-        if (empty($dataroot)) {
-            $this->logfile = false;
-            return $this->logfile;
-        }
-
-        $myroot = $dataroot.'/mdeploy';
-
-        if (!is_dir($myroot)) {
-            mkdir($myroot, 02777, true);
-        }
-
-        $this->logfile = $myroot.'/mdeploy.log';
-        return $this->logfile;
-    }
-
-    /**
-     * Choose the target location for the given ZIP's URL.
-     *
-     * @param string $source URL
-     * @return string
-     */
-    protected function target_location($source) {
-        $dataroot = $this->get_env('dataroot');
-        $pool = $dataroot.'/mdeploy/var';
-
-        if (!is_dir($pool)) {
-            mkdir($pool, 02777, true);
-        }
-
-        $target = $pool.'/'.md5($source);
-
-        $suffix = 0;
-        while (file_exists($target.'.'.$suffix.'.zip')) {
-            $suffix++;
-        }
-
-        return $target.'.'.$suffix.'.zip';
-    }
-
-    /**
-     * Choose the location of the current plugin folder backup
-     *
-     * @param string $path full path to the current folder
-     * @return string
-     */
-    protected function backup_location($path) {
-        $dataroot = $this->get_env('dataroot');
-        $pool = $dataroot.'/mdeploy/archive';
-
-        if (!is_dir($pool)) {
-            mkdir($pool, 02777, true);
-        }
-
-        $target = $pool.'/'.basename($path).'_'.time();
-
-        $suffix = 0;
-        while (file_exists($target.'.'.$suffix)) {
-            $suffix++;
-        }
-
-        return $target.'.'.$suffix;
-    }
-
-    /**
-     * Downloads 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.
-     *
-     * @param string $source file url starting with http(s)://
-     * @param string $target store the downloaded content to this file (full path)
-     * @return bool true on success, false otherwise
-     * @throws download_file_exception
-     */
-    protected function download_file($source, $target) {
-
-        $newlines = array("\r", "\n");
-        $source = str_replace($newlines, '', $source);
-        if (!preg_match('|^https?://|i', $source)) {
-            throw new download_file_exception('Unsupported transport protocol.');
-        }
-        if (!$ch = curl_init($source)) {
-            $this->log('Unable to init cURL.');
-            return false;
-        }
-
-        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); // verify the peer's certificate
-        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); // check the existence of a common name and also verify that it matches the hostname provided
-        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // return the transfer as a string
-        curl_setopt($ch, CURLOPT_HEADER, false); // don't include the header in the output
-        curl_setopt($ch, CURLOPT_TIMEOUT, 3600);
-        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 20); // nah, moodle.org is never unavailable! :-p
-        curl_setopt($ch, CURLOPT_URL, $source);
-        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // Allow redirection, we trust in ssl.
-        curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
-
-        if ($cacertfile = $this->get_cacert()) {
-            // Do not use CA certs provided by the operating system. Instead,
-            // use this CA cert to verify the ZIP provider.
-            $this->log('Using custom CA certificate '.$cacertfile);
-            curl_setopt($ch, CURLOPT_CAINFO, $cacertfile);
-        } else {
-            $this->log('Using operating system CA certificates.');
-        }
-
-        $proxy = $this->input->get_option('proxy', false);
-        if (!empty($proxy)) {
-            curl_setopt($ch, CURLOPT_PROXY, $proxy);
-
-            $proxytype = $this->input->get_option('proxytype', false);
-            if (strtoupper($proxytype) === 'SOCKS5') {
-                $this->log('Using SOCKS5 proxy');
-                curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
-            } else if (!empty($proxytype)) {
-                $this->log('Using HTTP proxy');
-                curl_setopt($ch, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
-                curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, false);
-            }
-
-            $proxyuserpwd = $this->input->get_option('proxyuserpwd', false);
-            if (!empty($proxyuserpwd)) {
-                curl_setopt($ch, CURLOPT_PROXYUSERPWD, $proxyuserpwd);
-                curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_BASIC | CURLAUTH_NTLM);
-            }
-        }
-
-        $targetfile = fopen($target, 'w');
-
-        if (!$targetfile) {
-            throw new download_file_exception('Unable to create local file '.$target);
-        }
-
-        curl_setopt($ch, CURLOPT_FILE, $targetfile);
-
-        $result = curl_exec($ch);
-
-        // try to detect encoding problems
-        if ((curl_errno($ch) == 23 or curl_errno($ch) == 61) and defined('CURLOPT_ENCODING')) {
-            curl_setopt($ch, CURLOPT_ENCODING, 'none');
-            $result = curl_exec($ch);
-        }
-
-        fclose($targetfile);
-
-        $this->curlerrno = curl_errno($ch);
-        $this->curlerror = curl_error($ch);
-        $this->curlinfo = curl_getinfo($ch);
-
-        if (!$result or $this->curlerrno) {
-            $this->log('Curl Error.');
-            return false;
-
-        } else if (is_array($this->curlinfo) and (empty($this->curlinfo['http_code']) or ($this->curlinfo['http_code'] != 200))) {
-            $this->log('Curl remote error.');
-            $this->log(print_r($this->curlinfo,true));
-            return false;
-        }
-
-        return true;
-    }
-
-    /**
-     * Fetch environment settings.
-     *
-     * @param string $key The key to fetch
-     * @return mixed The value of the key if found.
-     * @throws invalid_setting_exception if the option is not set, or is invalid.
-     */
-    protected function get_env($key) {
-        global $CFG;
-
-        if (array_key_exists($key, $this->validconfigoptions)) {
-            if (isset($CFG->$key)) {
-                return $CFG->$key;
-            }
-            throw new invalid_setting_exception("Requested environment setting '{$key}' is not currently set.");
-        } else {
-            throw new invalid_setting_exception("Requested environment setting '{$key}' is invalid.");
-        }
-    }
-
-    /**
-     * Get the location of ca certificates.
-     * @return string absolute file path or empty if default used
-     */
-    protected function get_cacert() {
-        $dataroot = $this->get_env('dataroot');
-
-        // Bundle in dataroot always wins.
-        if (is_readable($dataroot.'/moodleorgca.crt')) {
-            return realpath($dataroot.'/moodleorgca.crt');
-        }
-
-        // Next comes the default from php.ini
-        $cacert = ini_get('curl.cainfo');
-        if (!empty($cacert) and is_readable($cacert)) {
-            return realpath($cacert);
-        }
-
-        // Windows PHP does not have any certs, we need to use something.
-        if (stristr(PHP_OS, 'win') && !stristr(PHP_OS, 'darwin')) {
-            if (is_readable(__DIR__.'/lib/cacert.pem')) {
-                return realpath(__DIR__.'/lib/cacert.pem');
-            }
-        }
-
-        // Use default, this should work fine on all properly configured *nix systems.
-        return null;
-    }
-
-    /**
-     * Log a message
-     *
-     * @param string $message
-     */
-    protected function log($message) {
-
-        $logpath = $this->log_location();
-
-        if (empty($logpath)) {
-            // no logging available
-            return;
-        }
-
-        $f = fopen($logpath, 'ab');
-
-        if ($f === false) {
-            throw new filesystem_exception('Unable to open the log file for appending');
-        }
-
-        $message = $this->format_log_message($message);
-
-        fwrite($f, $message);
-
-        fclose($f);
-    }
-
-    /**
-     * Prepares the log message for writing into the file
-     *
-     * @param string $msg
-     * @return string
-     */
-    protected function format_log_message($msg) {
-
-        $msg = trim($msg);
-        $timestamp = date("Y-m-d H:i:s");
-
-        return $timestamp . ' '. $msg . PHP_EOL;
-    }
-
-    /**
-     * Checks to see if the given source could be safely moved into a new location
-     *
-     * @param string $source full path to the existing directory
-     * @return bool
-     */
-    protected function move_directory_source_precheck($source) {
-
-        if (!is_writable($source)) {
-            return false;
-        }
-
-        if (is_dir($source)) {
-            $handle = opendir($source);
-        } else {
-            return false;
-        }
-
-        $result = true;
-
-        while ($filename = readdir($handle)) {
-            $sourcepath = $source.'/'.$filename;
-
-            if ($filename === '.' or $filename === '..') {
-                continue;
-            }
-
-            if (is_dir($sourcepath)) {
-                $result = $result && $this->move_directory_source_precheck($sourcepath);
-
-            } else {
-                $result = $result && is_writable($sourcepath);
-            }
-        }
-
-        closedir($handle);
-
-        return $result;
-    }
-
-    /**
-     * Checks to see if a source folder could be safely moved into the given new location
-     *
-     * @param string $destination full path to the new expected location of a folder
-     * @return bool
-     */
-    protected function move_directory_target_precheck($target) {
-
-        // Check if the target folder does not exist yet, can be created
-        // and removed again.
-        $result = $this->create_directory_precheck($target);
-
-        // At the moment, it seems to be enough to check. We may want to add
-        // more steps in the future.
-
-        return $result;
-    }
-
-    /**
-     * Make sure the given directory can be created (and removed)
-     *
-     * @param string $path full path to the folder
-     * @return bool
-     */
-    protected function create_directory_precheck($path) {
-
-        if (file_exists($path)) {
-            return false;
-        }
-
-        $result = mkdir($path, 02777) && rmdir($path);
-
-        return $result;
-    }
-
-    /**
-     * Moves the given source into a new location recursively
-     *
-     * The target location can not exist.
-     *
-     * @param string $source full path to the existing directory
-     * @param string $destination full path to the new location of the folder
-     * @param bool $keepsourceroot should the root of the $source be kept or removed at the end
-     * @return bool
-     */
-    protected function move_directory($source, $target, $keepsourceroot = false) {
-
-        if (file_exists($target)) {
-            throw new filesystem_exception('Unable to move the directory - target location already exists');
-        }
-
-        return $this->move_directory_into($source, $target, $keepsourceroot);
-    }
-
-    /**
-     * Moves the given source into a new location recursively
-     *
-     * If the target already exists, files are moved into it. The target is created otherwise.
-     *
-     * @param string $source full path to the existing directory
-     * @param string $destination full path to the new location of the folder
-     * @param bool $keepsourceroot should the root of the $source be kept or removed at the end
-     * @return bool
-     */
-    protected function move_directory_into($source, $target, $keepsourceroot = false) {
-
-        if (is_dir($source)) {
-            $handle = opendir($source);
-        } else {
-            throw new filesystem_exception('Source location is not a directory');
-        }
-
-        if (is_dir($target)) {
-            $result = true;
-        } else {
-            $result = mkdir($target, 02777);
-        }
-
-        while ($filename = readdir($handle)) {
-            $sourcepath = $source.'/'.$filename;
-            $targetpath = $target.'/'.$filename;
-
-            if ($filename === '.' or $filename === '..') {
-                continue;
-            }
-
-            if (is_dir($sourcepath)) {
-                $result = $result && $this->move_directory($sourcepath, $targetpath, false);
-
-            } else {
-                $result = $result && rename($sourcepath, $targetpath);
-            }
-        }
-
-        closedir($handle);
-
-        if (!$keepsourceroot) {
-            $result = $result && rmdir($source);
-        }
-
-        clearstatcache();
-
-        return $result;
-    }
-
-    /**
-     * Deletes the given directory recursively
-     *
-     * @param string $path full path to the directory
-     * @param bool $keeppathroot should the root of the $path be kept (i.e. remove the content only) or removed too
-     * @return bool
-     */
-    protected function remove_directory($path, $keeppathroot = false) {
-
-        $result = true;
-
-        if (!file_exists($path)) {
-            return $result;
-        }
-
-        if (is_dir($path)) {
-            $handle = opendir($path);
-        } else {
-            throw new filesystem_exception('Given path is not a directory');
-        }
-
-        while ($filename = readdir($handle)) {
-            $filepath = $path.'/'.$filename;
-
-            if ($filename === '.' or $filename === '..') {
-                continue;
-            }
-
-            if (is_dir($filepath)) {
-                $result = $result && $this->remove_directory($filepath, false);
-
-            } else {
-                $result = $result && unlink($filepath);
-            }
-        }
-
-        closedir($handle);
-
-        if (!$keeppathroot) {
-            $result = $result && rmdir($path);
-        }
-
-        clearstatcache();
-
-        return $result;
-    }
-
-    /**
-     * Unzip the file obtained from the Plugins directory to this site
-     *
-     * @param string $ziplocation full path to the ZIP file
-     * @param string $plugintyperoot full path to the plugin's type location
-     * @param string $expectedlocation expected full path to the plugin after it is extracted
-     * @param string|bool $backuplocation location of the previous version of the plugin or false for no backup
-     */
-    protected function unzip_plugin($ziplocation, $plugintyperoot, $expectedlocation, $backuplocation) {
-
-        $zip = new ZipArchive();
-        $result = $zip->open($ziplocation);
-
-        if ($result !== true) {
-            if ($backuplocation !== false) {
-                $this->move_directory($backuplocation, $expectedlocation);
-            }
-            throw new zip_exception('Unable to open the zip package');
-        }
-
-        // Make sure that the ZIP has expected structure
-        $pluginname = basename($expectedlocation);
-        for ($i = 0; $i < $zip->numFiles; $i++) {
-            $stat = $zip->statIndex($i);
-            $filename = $stat['name'];
-            $filename = explode('/', $filename);
-            if ($filename[0] !== $pluginname) {
-                $zip->close();
-                throw new zip_exception('Invalid structure of the zip package');
-            }
-        }
-
-        if (!$zip->extractTo($plugintyperoot)) {
-            $zip->close();
-            $this->remove_directory($expectedlocation, true); // just in case something was created
-            if ($backuplocation !== false) {
-                $this->move_directory_into($backuplocation, $expectedlocation);
-            }
-            throw new zip_exception('Unable to extract the zip package');
-        }
-
-        $zip->close();
-        unlink($ziplocation);
-    }
-
-    /**
-     * Redirect the browser
-     *
-     * @todo check if there has been some output yet
-     * @param string $url
-     */
-    protected function redirect($url) {
-        header('Location: '.$url);
-    }
-}
-
-
-/**
- * Provides exception handlers for this script
- */
-class exception_handlers {
-
-    /**
-     * Sets the exception handler
-     *
-     *
-     * @param string $handler name
-     */
-    public static function set_handler($handler) {
-
-        if (PHP_SAPI === 'cli') {
-            // No custom handler available for CLI mode.
-            set_exception_handler(null);
-            return;
-        }
-
-        set_exception_handler('exception_handlers::'.$handler.'_exception_handler');
-    }
-
-    /**
-     * Returns the text describing the thrown exception
-     *
-     * By default, PHP displays full path to scripts when the exception is thrown. In order to prevent
-     * sensitive information leak (and yes, the path to scripts at a web server _is_ sensitive information)
-     * the path to scripts is removed from the message.
-     *
-     * @param Exception $e thrown exception
-     * @return string
-     */
-    public static function format_exception_info(Exception $e) {
-
-        $mydir = dirname(__FILE__).'/';
-        $text = $e->__toString();
-        $text = str_replace($mydir, '', $text);
-        return $text;
-    }
-
-    /**
-     * Very basic exception handler
-     *
-     * @param Exception $e uncaught exception
-     */
-    public static function bootstrap_exception_handler(Exception $e) {
-        echo('<h1>Oops! It did it again</h1>');
-        echo('<p><strong>Moodle deployment utility had a trouble with your request. See the debugging information for more details.</strong></p>');
-        echo('<pre>');
-        echo self::format_exception_info($e);
-        echo('</pre>');
-    }
-
-    /**
-     * Default exception handler
-     *
-     * When this handler is used, input_manager and output_manager singleton instances already
-     * exist in the memory and can be used.
-     *
-     * @param Exception $e uncaught exception
-     */
-    public static function default_exception_handler(Exception $e) {
-
-        $worker = worker::instance();
-        $worker->log_exception($e);
-
-        $output = output_manager::instance();
-        $output->exception($e);
-    }
-}
-
-////////////////////////////////////////////////////////////////////////////////
-
-// Check if the script is actually executed or if it was just included by someone
-// else - typically by the PHPUnit. This is a PHP alternative to the Python's
-// if __name__ == '__main__'
-if (!debug_backtrace()) {
-    // We are executed by the SAPI.
-    exception_handlers::set_handler('bootstrap');
-    // Initialize the worker class to actually make the job.
-    $worker = worker::instance();
-    exception_handlers::set_handler('default');
-
-    // Lights, Camera, Action!
-    $worker->execute();
-
-} else {
-    // We are included - probably by some unit testing framework. Do nothing.
-}
diff --git a/mdeploytest.php b/mdeploytest.php
deleted file mode 100644 (file)
index dcd2dfd..0000000
+++ /dev/null
@@ -1,370 +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/>.
-
-/**
- * PHPUnit tests for the mdeploy.php utility
- *
- * Because the mdeploy.php can't be part of the Moodle code itself, this tests must be
- * executed using something like:
- *
- *  $ phpunit --no-configuration mdeploytest
- *
- * @package     core
- * @copyright   2012 David Mudrak <david@moodle.com>
- * @license     http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-require(__DIR__.'/mdeploy.php');
-
-/**
- * Provides testable input options.
- *
- * @copyright 2012 David Mudrak <david@moodle.com>
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class input_fake_provider extends input_provider {
-
-    /** @var array */
-    protected $fakeoptions = array();
-
-    /**
-     * Sets fake raw options.
-     *
-     * @param array $options
-     */
-    public function set_fake_options(array $options) {
-        $this->fakeoptions = $options;
-        $this->populate_options();
-    }
-
-    /**
-     * Returns the explicitly set fake options.
-     *
-     * @return array
-     */
-    protected function parse_raw_options() {
-        return $this->fakeoptions;
-    }
-}
-
-/**
- * Testable subclass.
- *
- * @copyright 2012 David Mudrak <david@moodle.com>
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class testable_input_manager extends input_manager {
-
-    /**
-     * Provides access to the protected method so we can test it explicitly.
-     */
-    public function cast_value($raw, $type) {
-        return parent::cast_value($raw, $type);
-    }
-
-    /**
-     * Sets the fake input provider.
-     */
-    protected function initialize() {
-        $this->inputprovider = input_fake_provider::instance();
-    }
-}
-
-
-/**
- * Testable subclass
- *
- * @copyright 2012 David Mudrak <david@moodle.com>
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class testable_worker extends worker {
-
-    /**
-     * Provides access to the protected method.
-     */
-    public function move_directory($source, $target, $keepsourceroot = false) {
-        return parent::move_directory($source, $target, $keepsourceroot);
-    }
-
-    /**
-     * Provides access to the protected method.
-     */
-    public function remove_directory($path, $keeppathroot = false) {
-        return parent::remove_directory($path, $keeppathroot);
-    }
-
-    /**
-     * Provides access to the protected method.
-     */
-    public function create_directory_precheck($path) {
-        return parent::create_directory_precheck($path);
-    }
-
-    public function get_env($key) {
-        return parent::get_env($key);
-    }
-}
-
-
-/**
- * Test cases for the mdeploy utility
- *
- * @copyright 2012 David Mudrak <david@moodle.com>
- * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class mdeploytest extends PHPUnit_Framework_TestCase {
-
-    public function test_same_singletons() {
-        $a = input_manager::instance();
-        $b = input_manager::instance();
-        $this->assertSame($a, $b);
-    }
-
-    /**
-     * @dataProvider data_for_cast_value
-     */
-    public function test_cast_value($raw, $type, $result) {
-        $input = testable_input_manager::instance();
-        $this->assertSame($input->cast_value($raw, $type), $result);
-    }
-
-    public function data_for_cast_value() {
-        return array(
-            array('3', input_manager::TYPE_INT, 3),
-            array(4, input_manager::TYPE_INT, 4),
-            array('', input_manager::TYPE_INT, 0),
-
-            array(true, input_manager::TYPE_FLAG, true),
-            array(false, input_manager::TYPE_FLAG, true),
-            array(0, input_manager::TYPE_FLAG, true),
-            array('1', input_manager::TYPE_FLAG, true),
-            array('0', input_manager::TYPE_FLAG, true),
-            array('muhehe', input_manager::TYPE_FLAG, true),
-
-            array('C:\\WINDOWS\\user.dat', input_manager::TYPE_PATH, 'C:/WINDOWS/user.dat'),
-            array('D:\xampp\htdocs\24_integration/mdeploy.php', input_manager::TYPE_PATH, 'D:/xampp/htdocs/24_integration/mdeploy.php'),
-            array('d:/xampp/htdocs/24_integration/mdeploy.php', input_manager::TYPE_PATH, 'd:/xampp/htdocs/24_integration/mdeploy.php'),
-            array('../../../etc/passwd', input_manager::TYPE_PATH, '/etc/passwd'),
-            array('///////.././public_html/test.php', input_manager::TYPE_PATH, '/public_html/test.php'),
-
-            array("!@#$%|/etc/qwerty\n\n\t\n\r", input_manager::TYPE_RAW, "!@#$%|/etc/qwerty\n\n\t\n\r"),
-
-            array("\nrock'n'roll.mp3\t.exe", input_manager::TYPE_FILE, 'rocknroll.mp3.exe'),
-
-            array('http://localhost/moodle/dev/plugin.zip', input_manager::TYPE_URL, 'http://localhost/moodle/dev/plugin.zip'),
-            array(
-                'https://moodle.org/plugins/download.php/1292/mod_stampcoll_moodle23_2012062201.zip',
-                input_manager::TYPE_URL,
-                'https://moodle.org/plugins/download.php/1292/mod_stampcoll_moodle23_2012062201.zip'
-            ),
-
-            array('5e8d2ea4f50d154730100b1645fbad67', input_manager::TYPE_MD5, '5e8d2ea4f50d154730100b1645fbad67'),
-        );
-    }
-
-    /**
-     * @expectedException invalid_option_exception
-     */
-    public function test_input_type_path_multiple_colons() {
-        $input = testable_input_manager::instance();
-        $input->cast_value('C:\apache\log:file', input_manager::TYPE_PATH); // must throw exception
-    }
-
-    /**
-     * @expectedException invalid_option_exception
-     */
-    public function test_input_type_path_invalid_drive_label() {
-        $input = testable_input_manager::instance();
-        $input->cast_value('0:/srv/moodledata', input_manager::TYPE_PATH); // must throw exception
-    }
-
-    /**
-     * @expectedException invalid_option_exception
-     */
-    public function test_input_type_path_invalid_colon() {
-        $input = testable_input_manager::instance();
-        $input->cast_value('/var/www/moodle:2.5', input_manager::TYPE_PATH); // must throw exception
-    }
-
-    /**
-     * @expectedException invalid_coding_exception
-     */
-    public function test_cast_array_argument() {
-        $input = testable_input_manager::instance();
-        $input->cast_value(array(1, 2, 3), input_manager::TYPE_INT); // must throw exception
-    }
-
-    /**
-     * @expectedException invalid_coding_exception
-     */
-    public function test_cast_object_argument() {
-        $input = testable_input_manager::instance();
-        $o = new stdClass();
-        $input->cast_value($o, input_manager::TYPE_INT); // must throw exception
-    }
-
-    /**
-     * @expectedException invalid_option_exception
-     */
-    public function test_cast_invalid_url_value() {
-        $input = testable_input_manager::instance();
-        $invalid = 'file:///etc/passwd';
-        $input->cast_value($invalid, input_manager::TYPE_URL); // must throw exception
-    }
-
-    /**
-     * @expectedException invalid_option_exception
-     */
-    public function test_cast_invalid_md5_value() {
-        $input = testable_input_manager::instance();
-        $invalid = 'this is not a valid md5 hash';
-        $input->cast_value($invalid, input_manager::TYPE_MD5); // must throw exception
-    }
-
-    /**
-     * @expectedException invalid_option_exception
-     */
-    public function test_cast_tilde_in_path() {
-        $input = testable_input_manager::instance();
-        $invalid = '~/public_html/moodle_dev';
-        $input->cast_value($invalid, input_manager::TYPE_PATH); // must throw exception
-    }
-
-    public function test_has_option() {
-        $provider = input_fake_provider::instance();
-
-        $provider->set_fake_options(array());
-        $this->assertFalse($provider->has_option('foo')); // foo not passed
-
-        $provider->set_fake_options(array('foo' => 1));
-        $this->assertFalse($provider->has_option('foo')); // foo passed but not a known option
-
-        $provider->set_fake_options(array('foo' => 1, 'help' => false));
-        $this->assertTrue($provider->has_option('help')); // help passed and it is a flag (value ignored)
-        $this->assertTrue($provider->has_option('h')); // 'h' is a shortname for 'help'
-    }
-
-    public function test_get_option() {
-        $input = testable_input_manager::instance();
-        $provider = input_fake_provider::instance();
-
-        $provider->set_fake_options(array('help' => false, 'passfile' => '_mdeploy.123456'));
-        $this->assertTrue($input->get_option('h'));
-        $this->assertEquals($input->get_option('passfile'), '_mdeploy.123456');
-        $this->assertEquals($input->get_option('password', 'admin123'), 'admin123');
-        try {
-            $this->assertEquals($input->get_option('password'), 'admin123'); // must throw exception (not passed but required)
-            $this->assertTrue(false);
-        } catch (missing_option_exception $e) {
-            $this->assertTrue(true);
-        }
-    }
-
-    public function test_moving_and_removing_directories() {
-        $worker = testable_worker::instance();
-
-        $root = sys_get_temp_dir().'/'.uniqid('mdeploytest', true);
-        mkdir($root.'/a', 0777, true);
-        touch($root.'/a/a.txt');
-
-        $this->assertTrue(file_exists($root.'/a/a.txt'));
-        $this->assertFalse(file_exists($root.'/b/a.txt'));
-        $this->assertTrue($worker->move_directory($root.'/a', $root.'/b'));
-        $this->assertFalse(is_dir($root.'/a'));
-        $this->assertTrue(file_exists($root.'/b/a.txt'));
-        $this->assertTrue($worker->move_directory($root.'/b', $root.'/c', true));
-        $this->assertTrue(file_exists($root.'/c/a.txt'));
-        $this->assertFalse(file_exists($root.'/b/a.txt'));
-        $this->assertTrue(is_dir($root.'/b'));
-        $this->assertTrue($worker->remove_directory($root.'/c', true));
-        $this->assertFalse(file_exists($root.'/c/a.txt'));
-        $this->assertTrue($worker->remove_directory($root.'/c'));
-        $this->assertFalse(is_dir($root.'/c'));
-    }
-
-    public function test_create_directory_precheck() {
-        $worker = testable_worker::instance();
-
-        $root = sys_get_temp_dir().'/'.uniqid('mdeploytest', true);
-        $this->assertFalse(file_exists($root));
-        $this->assertTrue($worker->create_directory_precheck($root));
-        $this->assertFalse(file_exists($root)); // The precheck is supposed to remove it again.
-    }
-
-    /**
-     * Test that an invalid setting throws an exception.
-     *
-     * @dataProvider get_env_unlisted_provider
-     * @expectedException invalid_setting_exception
-     * @expectedExceptionMessageRegExp /^Requested environment setting '[^']*' is invalid.$/
-     */
-    public function test_get_env_unlisted($key) {
-        $worker = testable_worker::instance();
-        $worker->get_env($key);
-    }
-
-    public function get_env_unlisted_provider() {
-        return array(
-            // Completely invalid environment variables.
-            array('example'),
-            array('invalid'),
-
-            // Valid ones which have not been whitelisted.
-            array('noemailever'),
-            array('dbname'),
-        );
-    }
-
-    /**
-     * Test that a valid, but unset setting throws an exception.
-     *
-     * @dataProvider get_env_valid_provider
-     * @expectedException invalid_setting_exception
-     * @expectedExceptionMessageRegExp /^Requested environment setting '[^']*' is not current set.$/
-     */
-    public function test_get_env_unset($key) {
-        // Ensure that the setting is currently unset.
-        global $CFG;
-        $CFG->$key = null;
-
-        $worker = testable_worker::instance();
-        $worker->get_env($key);
-    }
-
-    /**
-     * Test that a valid setting with data returns that data.
-     *
-     * @dataProvider get_env_valid_provider
-     */
-    public function test_get_env_valid($key) {
-        // Ensure that the setting is currently unset.
-        global $CFG;
-        $CFG->$key = rand(0, 1000);
-
-        $worker = testable_worker::instance();
-        $value = $worker->get_env($key);
-
-        $this->assertEquals($CFG->$key, $value);
-    }
-
-    public function get_env_valid_provider() {
-        return array(
-            array('dataroot'),
-            array('dirroot'),
-        );
-    }
-}