MDL-49329 admin: Get rid of mdeploy and \core\update\deployer class
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.
This commit is contained in:
+16
-75
@@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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 <[email protected]>
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
@@ -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')) {
|
||||
|
||||
-1574
File diff suppressed because it is too large
Load Diff
-370
@@ -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 <[email protected]>
|
||||
* @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 <[email protected]>
|
||||
* @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 <[email protected]>
|
||||
* @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 <[email protected]>
|
||||
* @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 <[email protected]>
|
||||
* @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'),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user