Merge branch 'MDL-35238-deployment' of git://github.com/mudrd8mz/moodle
This commit is contained in:
+45
-1
@@ -261,6 +261,18 @@ if ($version > $CFG->version) { // upgrade
|
||||
}
|
||||
|
||||
$output = $PAGE->get_renderer('core', 'admin');
|
||||
|
||||
$deployer = available_update_deployer::instance();
|
||||
if ($deployer->enabled()) {
|
||||
$deployer->initialize($reloadurl, $reloadurl);
|
||||
|
||||
$deploydata = $deployer->submitted_data();
|
||||
if (!empty($deploydata)) {
|
||||
echo $output->upgrade_plugin_confirm_deploy_page($deployer, $deploydata);
|
||||
die();
|
||||
}
|
||||
}
|
||||
|
||||
echo $output->upgrade_plugin_check_page(plugin_manager::instance(), available_update_checker::instance(),
|
||||
$version, $showallplugins, $reloadurl,
|
||||
new moodle_url('/admin/index.php', array('confirmupgrade'=>1, 'confirmrelease'=>1, 'confirmplugincheck'=>1)));
|
||||
@@ -305,6 +317,17 @@ if (moodle_needs_upgrading()) {
|
||||
|
||||
$output = $PAGE->get_renderer('core', 'admin');
|
||||
|
||||
$deployer = available_update_deployer::instance();
|
||||
if ($deployer->enabled()) {
|
||||
$deployer->initialize($PAGE->url, $PAGE->url);
|
||||
|
||||
$deploydata = $deployer->submitted_data();
|
||||
if (!empty($deploydata)) {
|
||||
echo $output->upgrade_plugin_confirm_deploy_page($deployer, $deploydata);
|
||||
die();
|
||||
}
|
||||
}
|
||||
|
||||
// check plugin dependencies first
|
||||
$failed = array();
|
||||
if (!plugin_manager::instance()->all_plugins_ok($version, $failed)) {
|
||||
@@ -416,9 +439,30 @@ $cronoverdue = ($lastcron < time() - 3600 * 24);
|
||||
$dbproblems = $DB->diagnose();
|
||||
$maintenancemode = !empty($CFG->maintenance_enabled);
|
||||
|
||||
// Available updates for Moodle core
|
||||
$updateschecker = available_update_checker::instance();
|
||||
$availableupdates = $updateschecker->get_update_info('core',
|
||||
$availableupdates = array();
|
||||
$availableupdates['core'] = $updateschecker->get_update_info('core',
|
||||
array('minmaturity' => $CFG->updateminmaturity, 'notifybuilds' => $CFG->updatenotifybuilds));
|
||||
|
||||
// Available updates for contributed plugins
|
||||
$pluginman = plugin_manager::instance();
|
||||
foreach ($pluginman->get_plugins() as $plugintype => $plugintypeinstances) {
|
||||
foreach ($plugintypeinstances as $pluginname => $plugininfo) {
|
||||
if (!empty($plugininfo->availableupdates)) {
|
||||
foreach ($plugininfo->availableupdates as $pluginavailableupdate) {
|
||||
if ($pluginavailableupdate->version > $plugininfo->versiondisk) {
|
||||
if (!isset($availableupdates[$plugintype.'_'.$pluginname])) {
|
||||
$availableupdates[$plugintype.'_'.$pluginname] = array();
|
||||
}
|
||||
$availableupdates[$plugintype.'_'.$pluginname][] = $pluginavailableupdate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The timestamp of the most recent check for available updates
|
||||
$availableupdatesfetch = $updateschecker->get_last_timefetched();
|
||||
|
||||
$buggyiconvnomb = (!function_exists('mb_convert_encoding') and @iconv('UTF-8', 'UTF-8//IGNORE', '100'.chr(130).'€') !== '100€');
|
||||
|
||||
+23
-2
@@ -32,15 +32,36 @@ require_capability('moodle/site:config', context_system::instance());
|
||||
admin_externalpage_setup('pluginsoverview');
|
||||
|
||||
$fetchremote = optional_param('fetchremote', false, PARAM_BOOL);
|
||||
$updatesonly = optional_param('updatesonly', false, PARAM_BOOL);
|
||||
$contribonly = optional_param('contribonly', false, PARAM_BOOL);
|
||||
|
||||
$pluginman = plugin_manager::instance();
|
||||
$checker = available_update_checker::instance();
|
||||
|
||||
// Filtering options.
|
||||
$options = array(
|
||||
'updatesonly' => $updatesonly,
|
||||
'contribonly' => $contribonly,
|
||||
);
|
||||
|
||||
if ($fetchremote) {
|
||||
require_sesskey();
|
||||
$checker->fetch();
|
||||
redirect($PAGE->url);
|
||||
redirect(new moodle_url($PAGE->url, $options));
|
||||
}
|
||||
|
||||
$output = $PAGE->get_renderer('core', 'admin');
|
||||
echo $output->plugin_management_page($pluginman, $checker);
|
||||
|
||||
$deployer = available_update_deployer::instance();
|
||||
if ($deployer->enabled()) {
|
||||
$myurl = new moodle_url($PAGE->url, array('updatesonly' => $updatesonly, 'contribonly' => $contribonly));
|
||||
$deployer->initialize($myurl, $myurl);
|
||||
|
||||
$deploydata = $deployer->submitted_data();
|
||||
if (!empty($deploydata)) {
|
||||
echo $output->upgrade_plugin_confirm_deploy_page($deployer, $deploydata);
|
||||
die();
|
||||
}
|
||||
}
|
||||
|
||||
echo $output->plugin_management_page($pluginman, $checker, $options);
|
||||
|
||||
+175
-17
@@ -234,6 +234,62 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a page with a summary of plugin deployment to be confirmed.
|
||||
*
|
||||
* @param available_update_deployer $deployer
|
||||
* @param array $data deployer's data package as returned by {@link available_update_deployer::submitted_data()}
|
||||
* @return string
|
||||
*/
|
||||
public function upgrade_plugin_confirm_deploy_page(available_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']);
|
||||
$output .= $this->output->render($widget);
|
||||
|
||||
$output .= $this->output->single_button($data['returnurl'], get_string('cancel', 'core'), 'get');
|
||||
|
||||
$output .= $this->container_end();
|
||||
$output .= $this->footer();
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the admin notifications page.
|
||||
* @param int $maturity
|
||||
@@ -278,22 +334,30 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
/**
|
||||
* Display the plugin management page (admin/plugins.php).
|
||||
*
|
||||
* The filtering options array may contain following items:
|
||||
* bool contribonly - show only contributed extensions
|
||||
* bool updatesonly - show only plugins with an available update
|
||||
*
|
||||
* @param plugin_manager $pluginman
|
||||
* @param available_update_checker $checker
|
||||
* @param array $options filtering options
|
||||
* @return string HTML to output.
|
||||
*/
|
||||
public function plugin_management_page(plugin_manager $pluginman, available_update_checker $checker) {
|
||||
public function plugin_management_page(plugin_manager $pluginman, available_update_checker $checker, array $options = array()) {
|
||||
global $CFG;
|
||||
|
||||
$output = '';
|
||||
|
||||
$output .= $this->header();
|
||||
$output .= $this->heading(get_string('pluginsoverview', 'core_admin'));
|
||||
$output .= $this->plugins_overview_panel($pluginman);
|
||||
$output .= $this->plugins_overview_panel($pluginman, $options);
|
||||
|
||||
if (empty($CFG->disableupdatenotifications)) {
|
||||
$output .= $this->container_start('checkforupdates');
|
||||
$output .= $this->single_button(new moodle_url($this->page->url, array('fetchremote' => 1)), get_string('checkforupdates', 'core_plugin'));
|
||||
$output .= $this->single_button(
|
||||
new moodle_url($this->page->url, array_merge($options, array('fetchremote' => 1))),
|
||||
get_string('checkforupdates', 'core_plugin')
|
||||
);
|
||||
if ($timefetched = $checker->get_last_timefetched()) {
|
||||
$output .= $this->container(get_string('checkforupdateslast', 'core_plugin',
|
||||
userdate($timefetched, get_string('strftimedatetime', 'core_langconfig'))));
|
||||
@@ -301,7 +365,7 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
$output .= $this->container_end();
|
||||
}
|
||||
|
||||
$output .= $this->box($this->plugins_control_panel($pluginman), 'generalbox');
|
||||
$output .= $this->box($this->plugins_control_panel($pluginman, $options), 'generalbox');
|
||||
$output .= $this->footer();
|
||||
|
||||
return $output;
|
||||
@@ -495,21 +559,39 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays the info about available Moodle updates
|
||||
* Displays the info about available Moodle core and plugin updates
|
||||
*
|
||||
* @param array|null $updates array of available_update_info objects or null
|
||||
* The structure of the $updates param has changed since 2.4. It contains not only updates
|
||||
* for the core itself, but also for all other installed plugins.
|
||||
*
|
||||
* @param array|null $updates array of (string)component => array of available_update_info objects or null
|
||||
* @param int|null $fetch timestamp of the most recent updates fetch or null (unknown)
|
||||
* @return string
|
||||
*/
|
||||
protected function available_updates($updates, $fetch) {
|
||||
|
||||
$updateinfo = $this->box_start('generalbox adminwarning availableupdatesinfo');
|
||||
$someupdateavailable = false;
|
||||
if (is_array($updates)) {
|
||||
$updateinfo .= $this->heading(get_string('updateavailable', 'core_admin'), 3);
|
||||
foreach ($updates as $update) {
|
||||
$updateinfo .= $this->moodle_available_update_info($update);
|
||||
if (is_array($updates['core'])) {
|
||||
$someupdateavailable = true;
|
||||
$updateinfo .= $this->heading(get_string('updateavailable', 'core_admin'), 3);
|
||||
foreach ($updates['core'] as $update) {
|
||||
$updateinfo .= $this->moodle_available_update_info($update);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unset($updates['core']);
|
||||
// If something has left in the $updates array now, it is updates for plugins.
|
||||
if (!empty($updates)) {
|
||||
$someupdateavailable = true;
|
||||
$updateinfo .= $this->heading(get_string('updateavailableforplugin', 'core_admin'), 3);
|
||||
$pluginsoverviewurl = new moodle_url('/admin/plugins.php', array('updatesonly' => 1));
|
||||
$updateinfo .= $this->container(get_string('pluginsoverviewsee', 'core_admin',
|
||||
array('url' => $pluginsoverviewurl->out())));
|
||||
}
|
||||
}
|
||||
|
||||
if (!$someupdateavailable) {
|
||||
$now = time();
|
||||
if ($fetch and ($fetch <= $now) and ($now - $fetch < HOURSECS)) {
|
||||
$updateinfo .= $this->heading(get_string('updateavailablenot', 'core_admin'), 3);
|
||||
@@ -852,9 +934,10 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
* Prints an overview about the plugins - number of installed, number of extensions etc.
|
||||
*
|
||||
* @param plugin_manager $pluginman provides information about the plugins
|
||||
* @param array $options filtering options
|
||||
* @return string as usually
|
||||
*/
|
||||
public function plugins_overview_panel(plugin_manager $pluginman) {
|
||||
public function plugins_overview_panel(plugin_manager $pluginman, array $options = array()) {
|
||||
global $CFG;
|
||||
|
||||
$plugininfo = $pluginman->get_plugins();
|
||||
@@ -880,14 +963,49 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
}
|
||||
|
||||
$info = array();
|
||||
$filter = array();
|
||||
$somefilteractive = false;
|
||||
$info[] = html_writer::tag('span', get_string('numtotal', 'core_plugin', $numtotal), array('class' => 'info total'));
|
||||
$info[] = html_writer::tag('span', get_string('numdisabled', 'core_plugin', $numdisabled), array('class' => 'info disabled'));
|
||||
$info[] = html_writer::tag('span', get_string('numextension', 'core_plugin', $numextension), array('class' => 'info extension'));
|
||||
if ($numextension > 0) {
|
||||
if (empty($options['contribonly'])) {
|
||||
$filter[] = html_writer::link(
|
||||
new moodle_url($this->page->url, array('contribonly' => 1)),
|
||||
get_string('filtercontribonly', 'core_plugin'),
|
||||
array('class' => 'filter-item show-contribonly')
|
||||
);
|
||||
} else {
|
||||
$filter[] = html_writer::tag('span', get_string('filtercontribonlyactive', 'core_plugin'),
|
||||
array('class' => 'filter-item active show-contribonly'));
|
||||
$somefilteractive = true;
|
||||
}
|
||||
}
|
||||
if ($numupdatable > 0) {
|
||||
$info[] = html_writer::tag('span', get_string('numupdatable', 'core_plugin', $numupdatable), array('class' => 'info updatable'));
|
||||
if (empty($options['updatesonly'])) {
|
||||
$filter[] = html_writer::link(
|
||||
new moodle_url($this->page->url, array('updatesonly' => 1)),
|
||||
get_string('filterupdatesonly', 'core_plugin'),
|
||||
array('class' => 'filter-item show-updatesonly')
|
||||
);
|
||||
} else {
|
||||
$filter[] = html_writer::tag('span', get_string('filterupdatesonlyactive', 'core_plugin'),
|
||||
array('class' => 'filter-item active show-updatesonly'));
|
||||
$somefilteractive = true;
|
||||
}
|
||||
}
|
||||
if ($somefilteractive) {
|
||||
$filter[] = html_writer::link($this->page->url, get_string('filterall', 'core_plugin'), array('class' => 'filter-item show-all'));
|
||||
}
|
||||
|
||||
return $this->output->box(implode(html_writer::tag('span', ' ', array('class' => 'separator')), $info), '', 'plugins-overview-panel');
|
||||
$output = $this->output->box(implode(html_writer::tag('span', ' ', array('class' => 'separator')), $info), '', 'plugins-overview-panel');
|
||||
|
||||
if (!empty($filter)) {
|
||||
$output .= $this->output->box(implode(html_writer::tag('span', ' ', array('class' => 'separator')), $filter), '', 'plugins-overview-filter');
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -896,13 +1014,43 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
* This default implementation renders all plugins into one big table.
|
||||
*
|
||||
* @param plugin_manager $pluginman provides information about the plugins.
|
||||
* @param array $options filtering options
|
||||
* @return string HTML code
|
||||
*/
|
||||
public function plugins_control_panel(plugin_manager $pluginman) {
|
||||
public function plugins_control_panel(plugin_manager $pluginman, array $options = array()) {
|
||||
global $CFG;
|
||||
|
||||
$plugininfo = $pluginman->get_plugins();
|
||||
|
||||
// Filter the list of plugins according the options.
|
||||
if (!empty($options['updatesonly'])) {
|
||||
$updateable = array();
|
||||
foreach ($plugininfo as $plugintype => $pluginnames) {
|
||||
foreach ($pluginnames as $pluginname => $pluginfo) {
|
||||
if (!empty($pluginfo->availableupdates)) {
|
||||
foreach ($pluginfo->availableupdates as $pluginavailableupdate) {
|
||||
if ($pluginavailableupdate->version > $pluginfo->versiondisk) {
|
||||
$updateable[$plugintype][$pluginname] = $pluginfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$plugininfo = $updateable;
|
||||
}
|
||||
|
||||
if (!empty($options['contribonly'])) {
|
||||
$contribs = array();
|
||||
foreach ($plugininfo as $plugintype => $pluginnames) {
|
||||
foreach ($pluginnames as $pluginname => $pluginfo) {
|
||||
if (!$pluginfo->is_standard()) {
|
||||
$contribs[$plugintype][$pluginname] = $pluginfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
$plugininfo = $contribs;
|
||||
}
|
||||
|
||||
if (empty($plugininfo)) {
|
||||
return '';
|
||||
}
|
||||
@@ -973,12 +1121,10 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
$availability = new html_table_cell('');
|
||||
} else if ($isenabled) {
|
||||
$row->attributes['class'] .= ' enabled';
|
||||
$icon = $this->output->pix_icon('i/hide', get_string('pluginenabled', 'core_plugin'));
|
||||
$availability = new html_table_cell($icon . ' ' . get_string('pluginenabled', 'core_plugin'));
|
||||
$availability = new html_table_cell(get_string('pluginenabled', 'core_plugin'));
|
||||
} else {
|
||||
$row->attributes['class'] .= ' disabled';
|
||||
$icon = $this->output->pix_icon('i/show', get_string('plugindisabled', 'core_plugin'));
|
||||
$availability = new html_table_cell($icon . ' ' . get_string('plugindisabled', 'core_plugin'));
|
||||
$availability = new html_table_cell(get_string('plugindisabled', 'core_plugin'));
|
||||
}
|
||||
|
||||
$actions = array();
|
||||
@@ -1058,6 +1204,18 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
$box = $this->output->box_start($boxclasses);
|
||||
$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 = available_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'));
|
||||
}
|
||||
}
|
||||
|
||||
$box .= $this->output->box_end();
|
||||
|
||||
return $box;
|
||||
|
||||
@@ -228,6 +228,10 @@ if (empty($CFG->disableupdatenotifications)) {
|
||||
$temp = new admin_settingpage('updatenotifications', new lang_string('updatenotifications', 'core_admin'));
|
||||
$temp->add(new admin_setting_configcheckbox('updateautocheck', new lang_string('updateautocheck', 'core_admin'),
|
||||
new lang_string('updateautocheck_desc', 'core_admin'), 1));
|
||||
if (empty($CFG->disableupdateautodeploy)) {
|
||||
$temp->add(new admin_setting_configcheckbox('updateautodeploy', new lang_string('updateautodeploy', 'core_admin'),
|
||||
new lang_string('updateautodeploy_desc', 'core_admin'), 0));
|
||||
}
|
||||
$temp->add(new admin_setting_configselect('updateminmaturity', new lang_string('updateminmaturity', 'core_admin'),
|
||||
new lang_string('updateminmaturity_desc', 'core_admin'), MATURITY_STABLE,
|
||||
array(
|
||||
|
||||
@@ -774,6 +774,7 @@ $string['pluginscheck'] = 'Plugin dependencies check';
|
||||
$string['pluginscheckfailed'] = 'Dependencies check failed for {$a->pluginslist}';
|
||||
$string['pluginschecktodo'] = 'You must solve all the plugin requirements before proceeding to install this Moodle version!';
|
||||
$string['pluginsoverview'] = 'Plugins overview';
|
||||
$string['pluginsoverviewsee'] = 'See <a href="{$a->url}">plugins overview</a> page for more details.';
|
||||
$string['profilecategory'] = 'Category';
|
||||
$string['profilecategoryname'] = 'Category name (must be unique)';
|
||||
$string['profilecategorynamenotunique'] = 'This category name is already in use';
|
||||
@@ -1004,12 +1005,15 @@ $string['updateavailableforplugin'] = 'There is a newer version for some of your
|
||||
$string['updateavailable_moreinfo'] = 'More info...';
|
||||
$string['updateavailable_release'] = 'Moodle {$a}';
|
||||
$string['updateavailable_version'] = 'Version {$a}';
|
||||
$string['updateavailableinstall'] = 'Install this update';
|
||||
$string['updateavailablenot'] = 'Your Moodle code is up-to-date!';
|
||||
$string['updatenotifications'] = 'Update notifications';
|
||||
$string['updatenotificationfooter'] = 'Your Moodle site {$a->siteurl} is configured to automatically check for available updates. You are receiving this message as the administrator of the site. You can disable automatic checks for available updates in the Site administration section of the Settings block. You can customize the delivery of this message via your personal Messaging setting in the My profile settings section.';
|
||||
$string['updatenotificationsubject'] = 'Moodle updates are available ({$a->siteurl})';
|
||||
$string['updateautocheck'] = 'Automatically check for available updates';
|
||||
$string['updateautocheck_desc'] = 'If enabled, your site will automatically check for available updates for both Moodle code and all additional plugins. If there is a new update available, a notification will be sent to site admins.';
|
||||
$string['updateautodeploy'] = 'Enable updates deployment';
|
||||
$string['updateautodeploy_desc'] = 'If enabled, you will be able to download and install available updates directly from Moodle administration pages. Note that your web server process has to have write access into folders with Moodle installation to make this work. That can be seen as a potential security risk.';
|
||||
$string['updateminmaturity'] = 'Required code maturity';
|
||||
$string['updateminmaturity_desc'] = 'Notify about available updates only if the available code has the selected maturity level at least. Updates for plugins that do not declare their code maturity level are always reported regardless this setting.';
|
||||
$string['updatenotifybuilds'] = 'Notify about new builds';
|
||||
|
||||
@@ -30,11 +30,21 @@ $string['availability'] = 'Availability';
|
||||
$string['checkforupdates'] = 'Check for available updates';
|
||||
$string['checkforupdateslast'] = 'Last check done on {$a}';
|
||||
$string['displayname'] = 'Plugin name';
|
||||
$string['err_response_format_version'] = 'Unexpected version of the response format. Please try to re-check for available updates.';
|
||||
$string['filterall'] = 'Show all';
|
||||
$string['filtercontribonly'] = 'Show contributions only';
|
||||
$string['filtercontribonlyactive'] = 'Showing contributions only';
|
||||
$string['filterupdatesonly'] = 'Show updateable only';
|
||||
$string['filterupdatesonlyactive'] = 'Showing updateable only';
|
||||
$string['moodleversion'] = 'Moodle {$a}';
|
||||
$string['nonehighlighted'] = 'No plugins require your attention now';
|
||||
$string['nonehighlightedinfo'] = 'Display the list of all installed plugins anyway';
|
||||
$string['noneinstalled'] = 'No plugins of this type are installed';
|
||||
$string['notes'] = 'Notes';
|
||||
$string['notwritable'] = 'Plugin files not writable';
|
||||
$string['notwritable_help'] = 'You have enabled automatic updates deployment and there is available update for this plugin. However, the plugin files are not writable by the web server so the update can not be installed at the moment.
|
||||
|
||||
Make the plugin folder and all its contents writable to be able to install the available update automatically.';
|
||||
$string['numtotal'] = 'Installed: {$a}';
|
||||
$string['numdisabled'] = 'Disabled: {$a}';
|
||||
$string['numextension'] = 'Contributions: {$a}';
|
||||
@@ -126,6 +136,10 @@ $string['type_webservice_plural'] = 'Webservice protocols';
|
||||
$string['updateavailable'] = 'There is a new version {$a} available!';
|
||||
$string['updateavailable_moreinfo'] = 'More info...';
|
||||
$string['updateavailable_release'] = 'Release {$a}';
|
||||
$string['updatepluginconfirm'] = 'Plugin update confirmation';
|
||||
$string['updatepluginconfirminfo'] = 'You are about to install a new version of the plugin <strong>{$a->name}</strong>. A zip package with version {$a->version} of the plugin will be downloaded from <a href="{$a->url}">{$a->url}</a> and extracted to your Moodle installation so it can upgrade your installation.';
|
||||
$string['updatepluginconfirmexternal'] = 'It appears that the current version of the plugin has been obtained via source code management system ({$a}) checkout. If you install this update, you will no longer be able to obtain plugin updates from the source code management system. Please ensure that you definitely want to update the plugin before continuing.';
|
||||
$string['updatepluginconfirmwarning'] = 'Please note that Moodle will not automatically make a backup of your database before the upgrade. We strongly recommend that you make a full snapshot backup now, to cope with the rare case that the new code has bugs that make your site unavailable or even corrupts your database. Proceed at your own risk.';
|
||||
$string['uninstall'] = 'Uninstall';
|
||||
$string['version'] = 'Version';
|
||||
$string['versiondb'] = 'Current version';
|
||||
|
||||
@@ -655,6 +655,9 @@ class phpunit_util {
|
||||
if (class_exists('available_update_checker')) {
|
||||
available_update_checker::reset_caches(true);
|
||||
}
|
||||
if (class_exists('available_update_deployer')) {
|
||||
available_update_deployer::reset_caches(true);
|
||||
}
|
||||
|
||||
// purge dataroot directory
|
||||
self::reset_dataroot();
|
||||
|
||||
+505
-4
@@ -852,7 +852,7 @@ class available_update_checker {
|
||||
throw new available_update_checker_exception('err_response_status', $response['status']);
|
||||
}
|
||||
|
||||
if (empty($response['apiver']) or $response['apiver'] !== '1.0') {
|
||||
if (empty($response['apiver']) or $response['apiver'] !== '1.1') {
|
||||
throw new available_update_checker_exception('err_response_format_version', $response['apiver']);
|
||||
}
|
||||
|
||||
@@ -987,10 +987,10 @@ class available_update_checker {
|
||||
protected function prepare_request_url() {
|
||||
global $CFG;
|
||||
|
||||
if (!empty($CFG->alternativeupdateproviderurl)) {
|
||||
return $CFG->alternativeupdateproviderurl;
|
||||
if (!empty($CFG->config_php_settings['alternativeupdateproviderurl'])) {
|
||||
return $CFG->config_php_settings['alternativeupdateproviderurl'];
|
||||
} else {
|
||||
return 'http://download.moodle.org/api/1.0/updates.php';
|
||||
return 'http://download.moodle.org/api/1.1/updates.php';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1407,6 +1407,8 @@ class available_update_info {
|
||||
public $url = null;
|
||||
/** @var string|null optional URL of a ZIP package that can be downloaded and installed */
|
||||
public $download = null;
|
||||
/** @var string|null of self::download is set, then this must be the MD5 hash of the ZIP */
|
||||
public $downloadmd5 = null;
|
||||
|
||||
/**
|
||||
* Creates new instance of the class
|
||||
@@ -1428,6 +1430,505 @@ class available_update_info {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Implements a communication bridge to the mdeploy.php utility
|
||||
*/
|
||||
class available_update_deployer {
|
||||
|
||||
const HTTP_PARAM_PREFIX = 'updteautodpldata_'; // Hey, even Google has not heard of such a prefix! So it MUST be safe :-p
|
||||
const HTTP_PARAM_CHECKER = 'datapackagesize'; // Name of the parameter that holds the number of items in the received data items
|
||||
|
||||
/** @var available_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 available_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 available_update_info $info
|
||||
* @return array
|
||||
*/
|
||||
public function deployment_impediments(available_update_info $info) {
|
||||
|
||||
$impediments = array();
|
||||
|
||||
if (empty($info->download)) {
|
||||
$impediments['missingdownloadurl'] = true;
|
||||
}
|
||||
|
||||
if (empty($info->downloadmd5)) {
|
||||
$impediments['missingdownloadmd5'] = 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.
|
||||
*
|
||||
* @param available_update_info $info
|
||||
* @return false|string
|
||||
*/
|
||||
public function plugin_external_source(available_update_info $info) {
|
||||
|
||||
$paths = get_plugin_types(true);
|
||||
list($plugintype, $pluginname) = normalize_component($info->component);
|
||||
$pluginroot = $paths[$plugintype].'/'.$pluginname;
|
||||
|
||||
if (is_dir($pluginroot.'/.git')) {
|
||||
return 'git';
|
||||
}
|
||||
|
||||
if (is_dir($pluginroot.'/CVS')) {
|
||||
return 'cvs';
|
||||
}
|
||||
|
||||
if (is_dir($pluginroot.'/.svn')) {
|
||||
return 'svn';
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a renderable widget to confirm installation of an available update.
|
||||
*
|
||||
* @param available_update_info $info component version to deploy
|
||||
* @return renderable
|
||||
*/
|
||||
public function make_confirm_widget(available_update_info $info) {
|
||||
|
||||
if (!$this->initialized()) {
|
||||
throw new coding_exception('Illegal method call - deployer not initialized.');
|
||||
}
|
||||
|
||||
$params = $this->data_to_params(array(
|
||||
'updateinfo' => (array)$info, // see http://www.php.net/manual/en/language.types.array.php#language.types.array.casting
|
||||
));
|
||||
|
||||
$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 available_update_info $info component version to deploy
|
||||
* @return renderable
|
||||
*/
|
||||
public function make_execution_widget(available_update_info $info) {
|
||||
global $CFG;
|
||||
|
||||
if (!$this->initialized()) {
|
||||
throw new coding_exception('Illegal method call - deployer not initialized.');
|
||||
}
|
||||
|
||||
$pluginrootpaths = get_plugin_types(true);
|
||||
|
||||
list($plugintype, $pluginname) = normalize_component($info->component);
|
||||
|
||||
if (empty($pluginrootpaths[$plugintype])) {
|
||||
throw new coding_exception('Unknown plugin type root location', $plugintype);
|
||||
}
|
||||
|
||||
list($passfile, $password) = $this->prepare_authorization();
|
||||
|
||||
$upgradeurl = new moodle_url('/admin');
|
||||
|
||||
$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' => $upgradeurl->out(true),
|
||||
);
|
||||
|
||||
$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() {
|
||||
|
||||
$data = $this->params_to_data($_POST);
|
||||
|
||||
if (empty($data) or empty($data[self::HTTP_PARAM_CHECKER])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!empty($data['updateinfo']) and is_object($data['updateinfo'])) {
|
||||
$updateinfo = $data['updateinfo'];
|
||||
if (!empty($updateinfo->component) and !empty($updateinfo->version)) {
|
||||
$data['updateinfo'] = new available_update_info($updateinfo->component, (array)$updateinfo);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($data['callerurl'])) {
|
||||
$data['callerurl'] = new moodle_url($data['callerurl']);
|
||||
}
|
||||
|
||||
if (!empty($data['returnurl'])) {
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
||||
if ($success) {
|
||||
return array($passfile, $password);
|
||||
|
||||
} else {
|
||||
throw new moodle_exception('unable_prepare_authorization', 'core_plugin');
|
||||
}
|
||||
}
|
||||
|
||||
// End of external API
|
||||
|
||||
/**
|
||||
* Prepares an array of HTTP parameters that can be passed to another page.
|
||||
*
|
||||
* @param array|object $data associative array or an object holding the data, data JSON-able
|
||||
* @return array suitable as a param for moodle_url
|
||||
*/
|
||||
protected function data_to_params($data) {
|
||||
|
||||
// Append some our own data
|
||||
if (!empty($this->callerurl)) {
|
||||
$data['callerurl'] = $this->callerurl->out(false);
|
||||
}
|
||||
if (!empty($this->callerurl)) {
|
||||
$data['returnurl'] = $this->returnurl->out(false);
|
||||
}
|
||||
|
||||
// Finally append the count of items in the package.
|
||||
$data[self::HTTP_PARAM_CHECKER] = count($data);
|
||||
|
||||
// Generate params
|
||||
$params = array();
|
||||
foreach ($data as $name => $value) {
|
||||
$transname = self::HTTP_PARAM_PREFIX.$name;
|
||||
$transvalue = json_encode($value);
|
||||
$params[$transname] = $transvalue;
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts HTTP parameters passed to the script into native PHP data
|
||||
*
|
||||
* @param array $params such as $_REQUEST or $_POST
|
||||
* @return array data passed for this class
|
||||
*/
|
||||
protected function params_to_data(array $params) {
|
||||
|
||||
if (empty($params)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$data = array();
|
||||
foreach ($params as $name => $value) {
|
||||
if (strpos($name, self::HTTP_PARAM_PREFIX) === 0) {
|
||||
$realname = substr($name, strlen(self::HTTP_PARAM_PREFIX));
|
||||
$realvalue = json_decode($value);
|
||||
$data[$realname] = $realvalue;
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) = normalize_component($component);
|
||||
|
||||
$directory = get_plugin_directory($plugintype, $pluginname);
|
||||
|
||||
if (is_null($directory)) {
|
||||
throw new coding_exception('Unknown component location', $component);
|
||||
}
|
||||
|
||||
return $this->directory_writable($directory);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Factory class producing required subclasses of {@link plugininfo_base}
|
||||
*/
|
||||
|
||||
@@ -543,3 +543,39 @@ class testable_available_update_checker extends available_update_checker {
|
||||
class testable_available_update_checker_cron_executed extends Exception {
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Modified {@link available_update_deployer} suitable for testing purposes
|
||||
*/
|
||||
class testable_available_update_deployer extends available_update_deployer {
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Test cases for {@link available_update_deployer} class
|
||||
*/
|
||||
class available_update_deployer_test extends advanced_testcase {
|
||||
|
||||
public function test_magic_setters() {
|
||||
$deployer = testable_available_update_deployer::instance();
|
||||
$value = new moodle_url('/');
|
||||
$deployer->set_returnurl($value);
|
||||
$this->assertSame($deployer->get_returnurl(), $value);
|
||||
}
|
||||
|
||||
public function test_prepare_authorization() {
|
||||
global $CFG;
|
||||
|
||||
$deployer = testable_available_update_deployer::instance();
|
||||
list($passfile, $password) = $deployer->prepare_authorization();
|
||||
$filename = $CFG->phpunit_dataroot.'/mdeploy/auth/'.$passfile;
|
||||
$this->assertFileExists($filename);
|
||||
$stored = file($filename, FILE_IGNORE_NEW_LINES);
|
||||
$this->assertEquals(count($stored), 2);
|
||||
$this->assertGreaterThan(23, strlen($stored[0]));
|
||||
$this->assertSame($stored[0], $password);
|
||||
$this->assertTrue(time() - (int)$stored[1] < 60);
|
||||
}
|
||||
}
|
||||
|
||||
+1318
File diff suppressed because it is too large
Load Diff
+215
@@ -0,0 +1,215 @@
|
||||
<?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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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('../../../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_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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -102,6 +102,11 @@
|
||||
#page-admin-index .adminwarning.availableupdatesinfo .moodleupdateinfo a {padding-right:1em;}
|
||||
#page-admin-index .adminwarning.availableupdatesinfo .moodleupdateinfo .separator {border-left:1px dotted #333;}
|
||||
|
||||
#page-admin-index .updateplugin div,
|
||||
#page-admin-plugins .updateplugin div {margin-bottom:0.5em;}
|
||||
#page-admin-index .updateplugin .updatepluginconfirmexternal,
|
||||
#page-admin-plugins .updateplugin .updatepluginconfirmexternal {padding:1em;background-color:#ffd3d9;border:1px solid #EEAAAA}
|
||||
|
||||
#page-admin-user-user_bulk #users .fgroup {white-space: nowrap;}
|
||||
#page-admin-report-stats-index .graph {text-align: center;margin-bottom: 1em;}
|
||||
#page-admin-report-courseoverview-index .graph {text-align: center;margin-bottom: 1em;}
|
||||
@@ -248,6 +253,11 @@
|
||||
#page-admin-plugins #plugins-overview-panel .info {padding:5px 10px;}
|
||||
#page-admin-plugins #plugins-overview-panel .separator {border-left:1px dotted #999;}
|
||||
#page-admin-plugins #plugins-overview-panel .info.updatable {margin-left:10px;background-color:#d2ebff;font-weight:bold;-moz-border-radius: 10px;-webkit-border-radius: 10px;border-radius: 10px;}
|
||||
#page-admin-plugins #plugins-overview-filter {margin:1em auto;text-align:center;}
|
||||
#page-admin-plugins #plugins-overview-filter .filter-item {padding:5px 10px;}
|
||||
#page-admin-plugins #plugins-overview-filter .filter-item.active {font-weight:bold;}
|
||||
#page-admin-plugins #plugins-overview-filter .separator {border-left:1px dotted #999;}
|
||||
#page-admin-plugins #plugins-control-panel .actions .uninstall {color:#900;}
|
||||
#page-admin-plugins #plugins-control-panel .notes .pluginupdateinfo {padding:5px 10px;margin:10px;background-color:#d2ebff;-moz-border-radius: 10px;-webkit-border-radius: 10px;border-radius: 10px;}
|
||||
#page-admin-plugins #plugins-control-panel .notes .pluginupdateinfo.maturity50 {background-color:#ffd3d9;}
|
||||
#page-admin-plugins #plugins-control-panel .notes .pluginupdateinfo.maturity100,
|
||||
|
||||
Reference in New Issue
Block a user