Compare commits
406 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d25049c0fc | |||
| 6d00d81b84 | |||
| 61fdbd038e | |||
| 72d8a0a12e | |||
| 5c74e0daca | |||
| 4a46fd643f | |||
| 62a1434588 | |||
| 20af9d3c0f | |||
| 7bf7ef3919 | |||
| f8a6c3d6f0 | |||
| 6499a17d78 | |||
| 8ecc049f7f | |||
| 68299e6154 | |||
| 48bdbf5a4a | |||
| 648bf857d3 | |||
| 166e18d7cb | |||
| 2c78623121 | |||
| 89b8ba6dba | |||
| 5c8ef12940 | |||
| d1ff9b957a | |||
| 077703b235 | |||
| 980903e56a | |||
| d830bd9a33 | |||
| 8f2db3c1a1 | |||
| 666248c264 | |||
| 1f8eb08428 | |||
| 20c80d9e2f | |||
| fc22f80e9c | |||
| bdf5ae0e92 | |||
| fadff0062a | |||
| 849c19022c | |||
| 0076ac2d1c | |||
| 792fb3ea61 | |||
| 15dcd5a535 | |||
| 8ce2f33f9e | |||
| ca13549a89 | |||
| 20e563e825 | |||
| 0174a0a57f | |||
| 7047adfdbe | |||
| 47de7aac75 | |||
| 739d227c58 | |||
| 74556525de | |||
| 9d8348e13f | |||
| bbc12d9cb5 | |||
| 4637ad6f7e | |||
| 40d52d4067 | |||
| 68170f0b01 | |||
| 7d6e6ffceb | |||
| 0057543e5e | |||
| 07d9521c49 | |||
| 4ee23c10bb | |||
| 37648d141f | |||
| 3fd99e0ff6 | |||
| 2d47c45485 | |||
| f214d6e69a | |||
| e0cd470f1d | |||
| 5e44d5abaa | |||
| dba2527290 | |||
| 0b7cbdb0e6 | |||
| 92e7886965 | |||
| 7b414e6258 | |||
| 880c2fb8ba | |||
| e68af16896 | |||
| a2a8e3edf9 | |||
| 333c9cd9df | |||
| 5d4923ffcc | |||
| 2ce17661e8 | |||
| dc22f74419 | |||
| f7adf8a010 | |||
| 92267ed9fe | |||
| d520b92579 | |||
| 83f78c7c5e | |||
| 78e6292f47 | |||
| e666c72330 | |||
| db1cf750bd | |||
| 2522a179b0 | |||
| 30e2727e72 | |||
| faa963ba9a | |||
| 3bbb37741f | |||
| d0dd8d33bb | |||
| 824303bada | |||
| cb49ea51eb | |||
| c1f759ab50 | |||
| d183fbaf60 | |||
| 0d0552d430 | |||
| 55ab8ab731 | |||
| f50f41c507 | |||
| 86573d4ee6 | |||
| 548e5ecd1b | |||
| 695dc96b0e | |||
| 7345a2af94 | |||
| 2479cecad9 | |||
| de55ebff23 | |||
| 157e5a9f9c | |||
| b7860604f7 | |||
| eba3393014 | |||
| 16266791a0 | |||
| 2427ceef62 | |||
| 3e9dfd7f62 | |||
| dc9363a651 | |||
| 5c1049f72b | |||
| 125ca896df | |||
| 65684fa600 | |||
| c7b0eeee8e | |||
| dbf541f8e1 | |||
| 5636246c95 | |||
| 50ca770bc6 | |||
| 21d1577972 | |||
| 9d5a2e6f1c | |||
| 004659a6d0 | |||
| 47042403ca | |||
| fc463d34e8 | |||
| 188c45aef6 | |||
| 76b320818a | |||
| e0e6a127fa | |||
| b1e4d8b119 | |||
| 80fa42a001 | |||
| 63de1463f8 | |||
| 1510a9d6a1 | |||
| 4fdbb24243 | |||
| 32b48768d4 | |||
| a23eb149a8 | |||
| f0a38fd4c0 | |||
| 1b25b037dc | |||
| e7acf36d0d | |||
| 75c74acfa1 | |||
| 2df40c4f90 | |||
| 3f3ecc0311 | |||
| 20ab72d496 | |||
| f208379c9f | |||
| d61f480fb6 | |||
| 45ddf2980a | |||
| d57563edd6 | |||
| a095ad50c1 | |||
| 905050d73e | |||
| d20d8938a7 | |||
| e1d76a33e3 | |||
| 8fc9105a7a | |||
| 07a50c2f29 | |||
| f87c073039 | |||
| 28b751b244 | |||
| 9663e6f8a5 | |||
| 13f327b533 | |||
| e7b7b7fd8e | |||
| 90db0fb726 | |||
| fd554631f8 | |||
| 8bf65f15dd | |||
| ca0678bd96 | |||
| 9f6a3aee95 | |||
| 08c9c27c01 | |||
| 9b6218949a | |||
| 05dd6551bd | |||
| 5f8a991828 | |||
| d7b83310a7 | |||
| a25529102c | |||
| 883656ceb8 | |||
| c005cb8947 | |||
| 827fbc4515 | |||
| f8840f947c | |||
| 41b20b6e18 | |||
| 0e96add931 | |||
| 196c818a91 | |||
| 8791ab3304 | |||
| fc06491c75 | |||
| b70337b38c | |||
| 08f2855d64 | |||
| eb4b4c7ba0 | |||
| 23c3ce59ff | |||
| 87aa044351 | |||
| fa5cfedf4e | |||
| 89c7782a91 | |||
| e17dc7bcfa | |||
| 64f2725233 | |||
| 6e5c445714 | |||
| 95ef465948 | |||
| 0d893ad97b | |||
| 1110aed46a | |||
| 86fdc9dd64 | |||
| d66cfec3f0 | |||
| 677267791a | |||
| 8e9115725e | |||
| 179515b4de | |||
| 871c0ec7c5 | |||
| e7bc2568a6 | |||
| 9fca4525dc | |||
| 5fd16a3761 | |||
| af21acd472 | |||
| 272878cab9 | |||
| 38083d7ff3 | |||
| 86af5cb87c | |||
| fde80766e2 | |||
| 2f3c3abc1f | |||
| 871a036c74 | |||
| eb6e5390c9 | |||
| ac86e96914 | |||
| 66f93d133a | |||
| ae527646a1 | |||
| 0034fd8816 | |||
| 4c3f6d562f | |||
| b2d285491c | |||
| 4d7544a3a0 | |||
| 1d20e997a0 | |||
| a9af7d94e1 | |||
| 3a6f3f906a | |||
| 8beb255c10 | |||
| c800196f03 | |||
| 7a684b732c | |||
| 82358535df | |||
| 6637cf27b9 | |||
| 874db754ce | |||
| 7aa0860eca | |||
| 807c670765 | |||
| 36cef83e08 | |||
| 877ce4a64d | |||
| a5a3c48851 | |||
| 464280a23d | |||
| afce57cbea | |||
| 6e3438036a | |||
| 0d7ed311f8 | |||
| 52878b707a | |||
| 3616569d45 | |||
| a6afe639ca | |||
| 200862ff6e | |||
| 7de5920dd8 | |||
| ed94a2affd | |||
| e2f577e074 | |||
| 0ad0ddd1ea | |||
| 9a5044fa5c | |||
| 7c3e7cdae9 | |||
| acfc4c0b4d | |||
| 65f3549893 | |||
| 50df05ef78 | |||
| 322a1fa453 | |||
| 15601a33d9 | |||
| 42a1546bf8 | |||
| 0babaf208d | |||
| 9514d99333 | |||
| b51ee5ac52 | |||
| 2f4815a05b | |||
| 1a997d4b11 | |||
| d03434111a | |||
| 888d681014 | |||
| 2dbb5e22f7 | |||
| 49c4454cec | |||
| 19a43c48bf | |||
| 4532c861e1 | |||
| 1132d30d1f | |||
| 6e415f6a59 | |||
| a42f023698 | |||
| 18c31e25b6 | |||
| 6a46c626d6 | |||
| 321a4a90ab | |||
| bc389ac7c8 | |||
| 54c9783e5c | |||
| 646021e728 | |||
| 56f23662b1 | |||
| e002c90dab | |||
| 94ba162660 | |||
| b24571ea7d | |||
| d160b700e8 | |||
| 33f29f5adc | |||
| c5549ab475 | |||
| 1ec46dca1d | |||
| a8b6eaa3c4 | |||
| 25f6bc111d | |||
| 7874aa6eb7 | |||
| addeb650b9 | |||
| c3131a4ac2 | |||
| d60e4f6684 | |||
| cd2297972b | |||
| 7c399a1ba0 | |||
| 1b308f725a | |||
| 47235557d2 | |||
| 5abf0019f5 | |||
| 7ec197fd64 | |||
| 967d16dd52 | |||
| 5b4b64fc58 | |||
| 362598367c | |||
| 43b611f566 | |||
| 9832d18ab5 | |||
| 201d502a9b | |||
| 53bdea7476 | |||
| 1a49438ade | |||
| 4fb94f7f96 | |||
| 5638eb7c35 | |||
| 543ef5fa7c | |||
| c27601c459 | |||
| 6c66c20107 | |||
| 7221132d11 | |||
| c38626b055 | |||
| 7d99d3e9c4 | |||
| 529d4cdbc6 | |||
| cd07fceed1 | |||
| 26ae6e5792 | |||
| 217613ab21 | |||
| c017cc5ca9 | |||
| 03966a4ec0 | |||
| b1cb63b24f | |||
| ec373efd6d | |||
| 4a9dcf3f17 | |||
| f01e4739fd | |||
| 7d5774aaa2 | |||
| 7a562bf521 | |||
| 0c11e7ed30 | |||
| a33bc20d2e | |||
| e3353f92fc | |||
| 28e5ece329 | |||
| 239db05eec | |||
| 4fc9f74029 | |||
| e679a371ef | |||
| af8bd4de8e | |||
| 1cbfc11612 | |||
| bf75a73cc5 | |||
| 97dc7bfc0b | |||
| f60c23a781 | |||
| 2b0b60631d | |||
| 51a6bf7f1a | |||
| 2a6cc872ff | |||
| 350e34d177 | |||
| 67f6ebacff | |||
| e77dcc602d | |||
| aa1d665a3a | |||
| 2b0ea3051d | |||
| fa496d3fe1 | |||
| fa4ff1bd10 | |||
| 43f39c235f | |||
| 0e46c03356 | |||
| 940269660f | |||
| 4c2ba6794b | |||
| 7d14ae30e9 | |||
| 8ffb738721 | |||
| cf1cf9499f | |||
| d18bc9dc02 | |||
| 17f8fb7f3e | |||
| 969c26def0 | |||
| cbaad47bf2 | |||
| 9ccd8759ea | |||
| 5b26aca437 | |||
| 2e0b5708f1 | |||
| ce400f61e6 | |||
| 8257e19b7a | |||
| 8e73bb6328 | |||
| 2fa8db79cc | |||
| 1ad0beaf4f | |||
| 60802ff32e | |||
| b29da693b6 | |||
| 709f70145f | |||
| 9d14dcd64e | |||
| da14c5dff1 | |||
| d38ef5cfe0 | |||
| e6e58c7a92 | |||
| eba90af0d3 | |||
| 14f40dcc9c | |||
| 8758f4464f | |||
| 9fb8de4cd1 | |||
| 0720ecb621 | |||
| 6ae3b201cc | |||
| 056b54ff63 | |||
| 6a7348df5c | |||
| fc36e18fb1 | |||
| 3febb9e672 | |||
| ed065ceb93 | |||
| 26143abeaf | |||
| 9b3b40c1ab | |||
| b7acb1f0e8 | |||
| c7f36a87b3 | |||
| ba05f573ca | |||
| 967fe8ef0d | |||
| 4e37becc19 | |||
| 60b664a624 | |||
| b52ea3664e | |||
| 9bbe953b68 | |||
| 5389652d3c | |||
| 247946725c | |||
| 649356958e | |||
| 19602ce718 | |||
| d355d1f796 | |||
| 449693fc43 | |||
| 2538062907 | |||
| ada3d03f67 | |||
| e505b12188 | |||
| 9a6a761e1c | |||
| a9d792433f | |||
| fa58e13891 | |||
| 015f49d0d3 | |||
| 70dce3f450 | |||
| 3d5413d89a | |||
| 2c638ccb24 | |||
| 5016d9f4b7 | |||
| 9814a667ba | |||
| 5be375c379 | |||
| c5d0fcbf1f | |||
| 3414e35610 | |||
| c16cb40555 | |||
| 33c4973c67 | |||
| 72fcbcd444 | |||
| 167136718e | |||
| 7d80ee4455 | |||
| 49b427eda0 | |||
| 5fe214d9d8 | |||
| f6d6df37b6 | |||
| c8097f88a5 | |||
| cbeee0a46e | |||
| 2be96ee2d1 | |||
| a661cc9113 | |||
| 9b9226fc98 |
+18
-19
@@ -97,27 +97,26 @@ foreach ($settingspage->children as $childpage) {
|
||||
$outputhtml .= $OUTPUT->heading(html_writer::link($childpage->url, $childpage->visiblename), 3);
|
||||
} else if ($childpage instanceof admin_settingpage) {
|
||||
$outputhtml .= $OUTPUT->heading(html_writer::link(new moodle_url('/'.$CFG->admin.'/settings.php', array('section' => $childpage->name)), $childpage->visiblename), 3);
|
||||
// If its a settings page and has settings lets display them.
|
||||
if (!empty($childpage->settings)) {
|
||||
$outputhtml .= html_writer::start_tag('fieldset', array('class' => 'adminsettings'));
|
||||
foreach ($childpage->settings as $setting) {
|
||||
if (empty($setting->nosave)) {
|
||||
$savebutton = true;
|
||||
}
|
||||
$fullname = $setting->get_full_name();
|
||||
if (array_key_exists($fullname, $adminroot->errors)) {
|
||||
$data = $adminroot->errors[$fullname]->data;
|
||||
} else {
|
||||
$data = $setting->get_setting();
|
||||
}
|
||||
$outputhtml .= html_writer::tag('div', '<!-- -->', array('class' => 'clearer'));
|
||||
$outputhtml .= $setting->output_html($data);
|
||||
}
|
||||
$outputhtml .= html_writer::end_tag('fieldset');
|
||||
}
|
||||
} else if ($childpage instanceof admin_category) {
|
||||
$outputhtml .= $OUTPUT->heading(html_writer::link(new moodle_url('/'.$CFG->admin.'/category.php', array('category' => $childpage->name)), get_string('admincategory', 'admin', $childpage->visiblename)), 3);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
if (!empty($childpage->settings)) {
|
||||
$outputhtml .= html_writer::start_tag('fieldset', array('class' => 'adminsettings'));
|
||||
foreach ($childpage->settings as $setting) {
|
||||
if (empty($setting->nosave)) {
|
||||
$savebutton = true;
|
||||
}
|
||||
$fullname = $setting->get_full_name();
|
||||
if (array_key_exists($fullname, $adminroot->errors)) {
|
||||
$data = $adminroot->errors[$fullname]->data;
|
||||
} else {
|
||||
$data = $setting->get_setting();
|
||||
}
|
||||
$outputhtml .= html_writer::tag('div', '<!-- -->', array('class' => 'clearer'));
|
||||
$outputhtml .= $setting->output_html($data);
|
||||
}
|
||||
$outputhtml .= html_writer::end_tag('fieldset');
|
||||
}
|
||||
}
|
||||
if ($savebutton) {
|
||||
|
||||
@@ -201,17 +201,9 @@ if (defined('COMPONENT_CLASSLOADER')) {
|
||||
require($CFG->dirroot.'/version.php');
|
||||
$CFG->target_release = $release;
|
||||
|
||||
$_SESSION = array();
|
||||
$_SESSION['SESSION'] = new stdClass();
|
||||
$_SESSION['SESSION']->lang = $CFG->lang;
|
||||
$_SESSION['USER'] = new stdClass();
|
||||
$_SESSION['USER']->id = 0;
|
||||
$_SESSION['USER']->mnethostid = 1;
|
||||
|
||||
\core\session\manager::init_empty_session();
|
||||
global $SESSION;
|
||||
global $USER;
|
||||
$SESSION = &$_SESSION['SESSION'];
|
||||
$USER = &$_SESSION['USER'];
|
||||
|
||||
global $COURSE;
|
||||
$COURSE = new stdClass();
|
||||
|
||||
+5
-3
@@ -382,9 +382,6 @@ if (!$cache and moodle_needs_upgrading()) {
|
||||
if (!$PAGE->headerprinted) {
|
||||
// means core upgrade or installation was not already done
|
||||
|
||||
/** @var core_admin_renderer $output */
|
||||
$output = $PAGE->get_renderer('core', 'admin');
|
||||
|
||||
if (!$confirmplugins) {
|
||||
$strplugincheck = get_string('plugincheck');
|
||||
|
||||
@@ -402,6 +399,9 @@ if (!$cache and moodle_needs_upgrading()) {
|
||||
redirect($PAGE->url);
|
||||
}
|
||||
|
||||
/** @var core_admin_renderer $output */
|
||||
$output = $PAGE->get_renderer('core', 'admin');
|
||||
|
||||
$deployer = \core\update\deployer::instance();
|
||||
if ($deployer->enabled()) {
|
||||
$deployer->initialize($PAGE->url, $PAGE->url);
|
||||
@@ -425,6 +425,8 @@ if (!$cache and moodle_needs_upgrading()) {
|
||||
// Make sure plugin dependencies are always checked.
|
||||
$failed = array();
|
||||
if (!core_plugin_manager::instance()->all_plugins_ok($version, $failed)) {
|
||||
/** @var core_admin_renderer $output */
|
||||
$output = $PAGE->get_renderer('core', 'admin');
|
||||
$reloadurl = new moodle_url('/admin/index.php', array('cache' => 0));
|
||||
echo $output->unsatisfied_dependencies_page($version, $failed, $reloadurl);
|
||||
die();
|
||||
|
||||
+1
-1
@@ -1369,7 +1369,7 @@ class core_admin_renderer extends plugin_renderer_base {
|
||||
get_string('report'),
|
||||
get_string('status'),
|
||||
);
|
||||
$servertable->colclasses = array('centeralign name', 'centeralign status', 'leftalign report', 'centeralign info');
|
||||
$servertable->colclasses = array('centeralign name', 'centeralign info', 'leftalign report', 'centeralign status');
|
||||
$servertable->attributes['class'] = 'admintable environmenttable generaltable';
|
||||
$servertable->id = 'serverstatus';
|
||||
|
||||
|
||||
@@ -176,7 +176,7 @@ if ($roleid) {
|
||||
// Show UI for assigning a particular role to users.
|
||||
// Print a warning if we are assigning system roles.
|
||||
if ($context->contextlevel == CONTEXT_SYSTEM) {
|
||||
echo $OUTPUT->box(get_string('globalroleswarning', 'core_role'));
|
||||
echo $OUTPUT->notification(get_string('globalroleswarning', 'core_role'));
|
||||
}
|
||||
|
||||
// Print the form.
|
||||
@@ -251,7 +251,7 @@ if ($roleid) {
|
||||
|
||||
// Print a warning if we are assigning system roles.
|
||||
if ($context->contextlevel == CONTEXT_SYSTEM) {
|
||||
echo $OUTPUT->box(get_string('globalroleswarning', 'core_role'));
|
||||
echo $OUTPUT->notification(get_string('globalroleswarning', 'core_role'));
|
||||
}
|
||||
|
||||
// Print instruction.
|
||||
|
||||
@@ -74,8 +74,16 @@ abstract class core_role_capability_table_base {
|
||||
public function display() {
|
||||
if (count($this->capabilities) > self::NUM_CAPS_FOR_SEARCH) {
|
||||
global $PAGE;
|
||||
$PAGE->requires->strings_for_js(array('filter', 'clear'), 'moodle');
|
||||
$PAGE->requires->js_init_call('M.core_role.init_cap_table_filter', array($this->id, $this->context->id));
|
||||
$jsmodule = array(
|
||||
'name' => 'rolescapfilter',
|
||||
'fullpath' => '/admin/roles/module.js',
|
||||
'strings' => array(
|
||||
array('filter', 'moodle'),
|
||||
array('clear', 'moodle'), ),
|
||||
'requires' => array('node', 'cookie', 'escape')
|
||||
);
|
||||
$PAGE->requires->js_init_call('M.core_role.init_cap_table_filter', array($this->id, $this->context->id), false,
|
||||
$jsmodule);
|
||||
}
|
||||
echo '<table class="' . implode(' ', $this->classes) . '" id="' . $this->id . '">' . "\n<thead>\n";
|
||||
echo '<tr><th class="name" align="left" scope="col">' . get_string('capability', 'core_role') . '</th>';
|
||||
|
||||
@@ -75,7 +75,8 @@ switch ($action) {
|
||||
$a->id = $roleid;
|
||||
$a->name = $roles[$roleid]->name;
|
||||
$a->shortname = $roles[$roleid]->shortname;
|
||||
$a->count = $DB->count_records('role_assignments', array('roleid'=>$roleid));
|
||||
$a->count = $DB->count_records_select('role_assignments',
|
||||
'roleid = ?', array($roleid), 'COUNT(DISTINCT userid)');
|
||||
|
||||
$formcontinue = new single_button(new moodle_url($baseurl, $optionsyes), get_string('yes'));
|
||||
$formcancel = new single_button(new moodle_url($baseurl), get_string('no'), 'get');
|
||||
|
||||
@@ -51,7 +51,7 @@ M.core_role.init_cap_table_filter = function(Y, tableid, contextid) {
|
||||
marginRight : 'auto'
|
||||
});
|
||||
// Create the capability search input.
|
||||
this.input = Y.Node.create('<input type="text" id="'+this.table.get('id')+'capabilitysearch" value="'+filtervalue+'" />');
|
||||
this.input = Y.Node.create('<input type="text" id="'+this.table.get('id')+'capabilitysearch" value="'+Y.Escape.html(filtervalue)+'" />');
|
||||
// Create a label for the search input.
|
||||
this.label = Y.Node.create('<label for="'+this.input.get('id')+'">'+M.str.moodle.filter+' </label>');
|
||||
// Create a clear button to clear the input.
|
||||
|
||||
@@ -139,7 +139,6 @@ if (optional_param('savechanges', false, PARAM_BOOL) && confirm_sesskey()) {
|
||||
array(
|
||||
'context' => $context,
|
||||
'objectid' => $roleid,
|
||||
'courseid' => $courseid
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
@@ -80,7 +80,8 @@ class behat_admin extends behat_base {
|
||||
// Single element settings.
|
||||
try {
|
||||
$fieldxpath = "//*[self::input | self::textarea | self::select][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')]" .
|
||||
"[@id=//label[contains(normalize-space(.), $label)]/@for]";
|
||||
"[@id=//label[contains(normalize-space(.), $label)]/@for or " .
|
||||
"@id=//span[contains(normalize-space(.), $label)]/preceding-sibling::label[1]/@for]";
|
||||
$fieldnode = $this->find('xpath', $fieldxpath, $exception);
|
||||
|
||||
$formfieldtypenode = $this->find('xpath', $fieldxpath . "/ancestor::div[@class='form-setting']" .
|
||||
@@ -89,7 +90,7 @@ class behat_admin extends behat_base {
|
||||
} catch (ElementNotFoundException $e) {
|
||||
|
||||
// Multi element settings, interacting only the first one.
|
||||
$fieldxpath = "//descendant::label[.= $label]/ancestor::div[contains(concat(' ', normalize-space(@class), ' '), ' form-item ')]" .
|
||||
$fieldxpath = "//*[label[.= $label]|span[.= $label]]/ancestor::div[contains(concat(' ', normalize-space(@class), ' '), ' form-item ')]" .
|
||||
"/descendant::div[@class='form-group']/descendant::*[self::input | self::textarea | self::select][not(./@type = 'submit' or ./@type = 'image' or ./@type = 'hidden')]";
|
||||
$fieldnode = $this->find('xpath', $fieldxpath);
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
@core @core_admin
|
||||
Feature: Set admin settings value
|
||||
In order to set admin settings value
|
||||
As an admin
|
||||
I need to set admin setting value and verify it is applied
|
||||
|
||||
Background:
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname | category |
|
||||
| Course fullname | C_shortname | 0 |
|
||||
And I log in as "admin"
|
||||
And I should see "Course fullname"
|
||||
And I should not see "C_shortname Course fullname"
|
||||
|
||||
Scenario: set admin value with full name
|
||||
Given I set the following administration settings values:
|
||||
| Display extended course names | 1 |
|
||||
When I press "Save changes"
|
||||
And I am on homepage
|
||||
Then I should see "C_shortname Course fullname"
|
||||
|
||||
Scenario: set admin value with short name
|
||||
Given I set the following administration settings values:
|
||||
| courselistshortnames | 1 |
|
||||
When I press "Save changes"
|
||||
And I am on homepage
|
||||
Then I should see "C_shortname Course fullname"
|
||||
@@ -27,6 +27,11 @@ require_once($CFG->libdir.'/adminlib.php');
|
||||
require_once($CFG->dirroot . '/' . $CFG->admin . '/tool/behat/locallib.php');
|
||||
require_once($CFG->libdir . '/behat/classes/behat_config_manager.php');
|
||||
|
||||
// This page usually takes an exceedingly long time to load, so we need to
|
||||
// increase the time limit. At present it takes about a minute on some
|
||||
// systems, but let's allow room for expansion.
|
||||
set_time_limit(300);
|
||||
|
||||
$filter = optional_param('filter', '', PARAM_ALPHANUMEXT);
|
||||
$type = optional_param('type', false, PARAM_ALPHAEXT);
|
||||
$component = optional_param('component', '', PARAM_ALPHAEXT);
|
||||
|
||||
@@ -669,7 +669,7 @@ if ($formdata = $mform2->is_cancelled()) {
|
||||
|
||||
if ($doupdate or $existinguser->password !== $oldpw) {
|
||||
// We want only users that were really updated.
|
||||
user_update_user($existinguser, false);
|
||||
user_update_user($existinguser, false, false);
|
||||
|
||||
$upt->track('status', $struserupdated);
|
||||
$usersupdated++;
|
||||
@@ -687,6 +687,9 @@ if ($formdata = $mform2->is_cancelled()) {
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger event.
|
||||
\core\event\user_updated::create_from_userid($existinguser->id)->trigger();
|
||||
|
||||
} else {
|
||||
// no user information changed
|
||||
$upt->track('status', $struseruptodate);
|
||||
@@ -800,7 +803,7 @@ if ($formdata = $mform2->is_cancelled()) {
|
||||
$upt->track('password', '-', 'normal', false);
|
||||
}
|
||||
|
||||
$user->id = user_create_user($user, false);
|
||||
$user->id = user_create_user($user, false, false);
|
||||
$upt->track('username', html_writer::link(new moodle_url('/user/profile.php', array('id'=>$user->id)), s($user->username)), 'normal', false);
|
||||
|
||||
// pre-process custom profile menu fields data from csv file
|
||||
@@ -815,6 +818,9 @@ if ($formdata = $mform2->is_cancelled()) {
|
||||
set_user_preference('create_password', 1, $user);
|
||||
}
|
||||
|
||||
// Trigger event.
|
||||
\core\event\user_created::create_from_userid($user->id)->trigger();
|
||||
|
||||
$upt->track('status', $struseradded);
|
||||
$upt->track('id', $user->id, 'normal', false);
|
||||
$usersnew++;
|
||||
|
||||
+18
-3
@@ -167,12 +167,27 @@ class auth_plugin_cas extends auth_plugin_ldap {
|
||||
*
|
||||
*/
|
||||
function prelogout_hook() {
|
||||
global $CFG;
|
||||
global $CFG, $USER, $DB;
|
||||
|
||||
if (!empty($this->config->logoutcas)) {
|
||||
if (!empty($this->config->logoutcas) && $USER->auth == $this->authtype) {
|
||||
$backurl = $CFG->wwwroot;
|
||||
$this->connectCAS();
|
||||
phpCAS::logoutWithURL($backurl);
|
||||
// Note: Hack to stable versions to trigger the event before it redirect to CAS logout.
|
||||
$sid = session_id();
|
||||
$event = \core\event\user_loggedout::create(
|
||||
array(
|
||||
'userid' => $USER->id,
|
||||
'objectid' => $USER->id,
|
||||
'other' => array('sessionid' => $sid),
|
||||
)
|
||||
);
|
||||
if ($session = $DB->get_record('sessions', array('sid' => $sid))) {
|
||||
$event->add_record_snapshot('sessions', $session);
|
||||
}
|
||||
\core\session\manager::terminate_current();
|
||||
$event->trigger();
|
||||
|
||||
phpCAS::logoutWithRedirectService($backurl);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -384,6 +384,10 @@ class auth_plugin_db extends auth_plugin_base {
|
||||
if ($old_user = $DB->get_record('user', array('username'=>$username, 'deleted'=>0, 'suspended'=>1, 'mnethostid'=>$CFG->mnet_localhost_id, 'auth'=>$this->authtype))) {
|
||||
$DB->set_field('user', 'suspended', 0, array('id'=>$old_user->id));
|
||||
$trace->output(get_string('auth_dbreviveduser', 'auth_db', array('name'=>$username, 'id'=>$old_user->id)), 1);
|
||||
|
||||
// Trigger user_updated event.
|
||||
\core\event\user_updated::create_from_userid($old_user->id)->trigger();
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -410,6 +414,10 @@ class auth_plugin_db extends auth_plugin_base {
|
||||
}
|
||||
try {
|
||||
$id = $DB->insert_record ('user', $user); // it is truly a new user
|
||||
|
||||
// Trigger user_created event.
|
||||
\core\event\user_created::create_from_userid($id)->trigger();
|
||||
|
||||
$trace->output(get_string('auth_dbinsertuser', 'auth_db', array('name'=>$user->username, 'id'=>$id)), 1);
|
||||
} catch (moodle_exception $e) {
|
||||
$trace->output(get_string('auth_dbinsertusererror', 'auth_db', $user->username), 1);
|
||||
@@ -547,6 +555,9 @@ class auth_plugin_db extends auth_plugin_base {
|
||||
}
|
||||
if ($updated) {
|
||||
$DB->set_field('user', 'timemodified', time(), array('id'=>$userid));
|
||||
|
||||
// Trigger user_updated event.
|
||||
\core\event\user_updated::create_from_userid($userid)->trigger();
|
||||
}
|
||||
return $DB->get_record('user', array('id'=>$userid, 'deleted'=>0));
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
class auth_db_testcase extends advanced_testcase {
|
||||
|
||||
protected function init_auth_database() {
|
||||
public function init_auth_database() {
|
||||
global $DB, $CFG;
|
||||
require_once("$CFG->dirroot/auth/db/auth.php");
|
||||
|
||||
|
||||
@@ -0,0 +1,150 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Events tests.
|
||||
*
|
||||
* @package auth_db
|
||||
* @category test
|
||||
* @copyright 2014 Mark Nelson <markn@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
global $CFG;
|
||||
|
||||
require_once($CFG->dirroot . '/auth/db/tests/db_test.php');
|
||||
|
||||
class auth_db_events_testcase extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Test set up.
|
||||
*/
|
||||
public function setUp() {
|
||||
$this->resetAfterTest(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the locations in the auth_db API that create a user trigger the user_created event.
|
||||
*/
|
||||
public function test_user_created() {
|
||||
global $DB;
|
||||
|
||||
$this->preventResetByRollback();
|
||||
|
||||
// Initialise the database.
|
||||
$authdbtestcase = new auth_db_testcase();
|
||||
$authdbtestcase->init_auth_database();
|
||||
|
||||
$auth = get_auth_plugin('db');
|
||||
$auth->db_init();
|
||||
|
||||
// Add a user to the auth_db_users table - we will then call sync_users to
|
||||
// deal with the record here. In this case it will create the user.
|
||||
$user = new stdClass();
|
||||
$user->name = 'mark';
|
||||
$user->pass = 'password123';
|
||||
$user->email = 'what@legend.com';
|
||||
$user->id = $DB->insert_record('auth_db_users', $user);
|
||||
|
||||
// Run sync_users and capture the user_created event.
|
||||
$sink = $this->redirectEvents();
|
||||
$trace = new null_progress_trace();
|
||||
$auth->sync_users($trace, false);
|
||||
$events = $sink->get_events();
|
||||
$sink->close();
|
||||
|
||||
// Check that there is only one event.
|
||||
$this->assertEquals(1, count($events));
|
||||
|
||||
// Get the event.
|
||||
$event = array_pop($events);
|
||||
|
||||
// Test that the user created event was triggered - no need to test the other
|
||||
// details of the event as that is done extensively in other unit tests.
|
||||
$this->assertInstanceOf('\core\event\user_created', $event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the locations in the auth_db API that update a user trigger the user_updated event.
|
||||
*/
|
||||
public function test_user_updated() {
|
||||
global $CFG, $DB;
|
||||
|
||||
$this->preventResetByRollback();
|
||||
|
||||
// Initialise the database.
|
||||
$authdbtestcase = new auth_db_testcase();
|
||||
$authdbtestcase->init_auth_database();
|
||||
|
||||
$auth = get_auth_plugin('db');
|
||||
$auth->db_init();
|
||||
|
||||
// Add a suspended user.
|
||||
$user = array();
|
||||
$user['username'] = 'mark';
|
||||
$user['suspended'] = '1';
|
||||
$user['mnethostid'] = $CFG->mnet_localhost_id;
|
||||
$user['auth'] = 'db';
|
||||
$this->getDataGenerator()->create_user($user);
|
||||
|
||||
// Add a user to the auth_db_users table - we will then call sync_users to
|
||||
// deal with the record here. In this case it will un-suspend the user.
|
||||
$user = new stdClass();
|
||||
$user->name = 'mark';
|
||||
$user->pass = 'password123';
|
||||
$user->email = 'what@legend.com';
|
||||
$user->id = $DB->insert_record('auth_db_users', $user);
|
||||
|
||||
// Set the config to remove the suspension on the user.
|
||||
set_config('removeuser', AUTH_REMOVEUSER_SUSPEND, 'auth/db');
|
||||
$auth->config->removeuser = AUTH_REMOVEUSER_SUSPEND;
|
||||
|
||||
// Run sync_users and capture the user_updated event.
|
||||
$sink = $this->redirectEvents();
|
||||
$trace = new null_progress_trace();
|
||||
$auth->sync_users($trace, false);
|
||||
$events = $sink->get_events();
|
||||
$sink->close();
|
||||
|
||||
// Check that there is only one event.
|
||||
$this->assertEquals(1, count($events));
|
||||
|
||||
// Get the event.
|
||||
$event = array_pop($events);
|
||||
|
||||
// Test that the user updated event was triggered - no need to test the other
|
||||
// details of the event as that is done extensively in other unit tests.
|
||||
$this->assertInstanceOf('\core\event\user_updated', $event);
|
||||
|
||||
// Run sync_users and capture the user_updated event.
|
||||
$sink = $this->redirectEvents();
|
||||
$auth->update_user_record('mark');
|
||||
$events = $sink->get_events();
|
||||
$sink->close();
|
||||
|
||||
// Check that there is only one event.
|
||||
$this->assertEquals(1, count($events));
|
||||
|
||||
// Get the event.
|
||||
$event = array_pop($events);
|
||||
|
||||
// Test that the user updated event was triggered - no need to test the other
|
||||
// details of the event as that is done extensively in other unit tests.
|
||||
$this->assertInstanceOf('\core\event\user_updated', $event);
|
||||
}
|
||||
}
|
||||
+4
-1
@@ -94,11 +94,14 @@ class auth_plugin_email extends auth_plugin_base {
|
||||
$user->calendartype = $CFG->calendartype;
|
||||
}
|
||||
|
||||
$user->id = user_create_user($user, false);
|
||||
$user->id = user_create_user($user, false, false);
|
||||
|
||||
// Save any custom profile field information.
|
||||
profile_save_data($user);
|
||||
|
||||
// Trigger event.
|
||||
\core\event\user_created::create_from_userid($user->id)->trigger();
|
||||
|
||||
if (! send_confirmation_email($user)) {
|
||||
print_error('auth_emailnoemail','auth_email');
|
||||
}
|
||||
|
||||
+4
-2
@@ -551,7 +551,7 @@ class auth_plugin_ldap extends auth_plugin_base {
|
||||
print_error('auth_ldap_create_error', 'auth_ldap');
|
||||
}
|
||||
|
||||
$user->id = user_create_user($user, false);
|
||||
$user->id = user_create_user($user, false, false);
|
||||
|
||||
// Save any custom profile field information
|
||||
profile_save_data($user);
|
||||
@@ -564,6 +564,8 @@ class auth_plugin_ldap extends auth_plugin_base {
|
||||
|
||||
$user = $DB->get_record('user', array('id'=>$user->id));
|
||||
|
||||
\core\event\user_created::create_from_userid($user->id)->trigger();
|
||||
|
||||
if (! send_confirmation_email($user)) {
|
||||
print_error('noemail', 'auth_ldap');
|
||||
}
|
||||
@@ -1027,7 +1029,7 @@ class auth_plugin_ldap extends auth_plugin_base {
|
||||
}
|
||||
}
|
||||
}
|
||||
user_update_user($newuser, false);
|
||||
user_update_user($newuser, false, false);
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
|
||||
@@ -279,6 +279,142 @@ class auth_ldap_plugin_testcase extends advanced_testcase {
|
||||
$this->assertEventLegacyLogData($expectedlog, $event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test logging in via LDAP calls a user_loggedin event.
|
||||
*/
|
||||
public function test_ldap_user_signup() {
|
||||
global $CFG, $DB;
|
||||
|
||||
// User to create.
|
||||
$user = array(
|
||||
'username' => 'usersignuptest1',
|
||||
'password' => 'Moodle2014!',
|
||||
'idnumber' => 'idsignuptest1',
|
||||
'firstname' => 'First Name User Test 1',
|
||||
'lastname' => 'Last Name User Test 1',
|
||||
'middlename' => 'Middle Name User Test 1',
|
||||
'lastnamephonetic' => '最後のお名前のテスト一号',
|
||||
'firstnamephonetic' => 'お名前のテスト一号',
|
||||
'alternatename' => 'Alternate Name User Test 1',
|
||||
'email' => 'usersignuptest1@email.com',
|
||||
'description' => 'This is a description for user 1',
|
||||
'city' => 'Perth',
|
||||
'country' => 'au',
|
||||
'mnethostid' => $CFG->mnet_localhost_id,
|
||||
'auth' => 'ldap'
|
||||
);
|
||||
|
||||
if (!extension_loaded('ldap')) {
|
||||
$this->markTestSkipped('LDAP extension is not loaded.');
|
||||
}
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
require_once($CFG->dirroot.'/auth/ldap/auth.php');
|
||||
require_once($CFG->libdir.'/ldaplib.php');
|
||||
|
||||
if (!defined('TEST_AUTH_LDAP_HOST_URL') or !defined('TEST_AUTH_LDAP_BIND_DN') or !defined('TEST_AUTH_LDAP_BIND_PW') or !defined('TEST_AUTH_LDAP_DOMAIN')) {
|
||||
$this->markTestSkipped('External LDAP test server not configured.');
|
||||
}
|
||||
|
||||
// Make sure we can connect the server.
|
||||
$debuginfo = '';
|
||||
if (!$connection = ldap_connect_moodle(TEST_AUTH_LDAP_HOST_URL, 3, 'rfc2307', TEST_AUTH_LDAP_BIND_DN, TEST_AUTH_LDAP_BIND_PW, LDAP_DEREF_NEVER, $debuginfo, false)) {
|
||||
$this->markTestSkipped('Can not connect to LDAP test server: '.$debuginfo);
|
||||
}
|
||||
|
||||
$this->enable_plugin();
|
||||
|
||||
// Create new empty test container.
|
||||
$topdn = 'dc=moodletest,'.TEST_AUTH_LDAP_DOMAIN;
|
||||
|
||||
$this->recursive_delete($connection, TEST_AUTH_LDAP_DOMAIN, 'dc=moodletest');
|
||||
|
||||
$o = array();
|
||||
$o['objectClass'] = array('dcObject', 'organizationalUnit');
|
||||
$o['dc'] = 'moodletest';
|
||||
$o['ou'] = 'MOODLETEST';
|
||||
if (!ldap_add($connection, 'dc=moodletest,'.TEST_AUTH_LDAP_DOMAIN, $o)) {
|
||||
$this->markTestSkipped('Can not create test LDAP container.');
|
||||
}
|
||||
|
||||
// Create a few users.
|
||||
$o = array();
|
||||
$o['objectClass'] = array('organizationalUnit');
|
||||
$o['ou'] = 'users';
|
||||
ldap_add($connection, 'ou='.$o['ou'].','.$topdn, $o);
|
||||
|
||||
// Configure the plugin a bit.
|
||||
set_config('host_url', TEST_AUTH_LDAP_HOST_URL, 'auth/ldap');
|
||||
set_config('start_tls', 0, 'auth/ldap');
|
||||
set_config('ldap_version', 3, 'auth/ldap');
|
||||
set_config('ldapencoding', 'utf-8', 'auth/ldap');
|
||||
set_config('pagesize', '2', 'auth/ldap');
|
||||
set_config('bind_dn', TEST_AUTH_LDAP_BIND_DN, 'auth/ldap');
|
||||
set_config('bind_pw', TEST_AUTH_LDAP_BIND_PW, 'auth/ldap');
|
||||
set_config('user_type', 'rfc2307', 'auth/ldap');
|
||||
set_config('contexts', 'ou=users,'.$topdn, 'auth/ldap');
|
||||
set_config('search_sub', 0, 'auth/ldap');
|
||||
set_config('opt_deref', LDAP_DEREF_NEVER, 'auth/ldap');
|
||||
set_config('user_attribute', 'cn', 'auth/ldap');
|
||||
set_config('memberattribute', 'memberuid', 'auth/ldap');
|
||||
set_config('memberattribute_isdn', 0, 'auth/ldap');
|
||||
set_config('creators', 'cn=creators,'.$topdn, 'auth/ldap');
|
||||
set_config('removeuser', AUTH_REMOVEUSER_KEEP, 'auth/ldap');
|
||||
|
||||
set_config('field_map_email', 'mail', 'auth/ldap');
|
||||
set_config('field_updatelocal_email', 'oncreate', 'auth/ldap');
|
||||
set_config('field_updateremote_email', '0', 'auth/ldap');
|
||||
set_config('field_lock_email', 'unlocked', 'auth/ldap');
|
||||
|
||||
set_config('field_map_firstname', 'givenName', 'auth/ldap');
|
||||
set_config('field_updatelocal_firstname', 'oncreate', 'auth/ldap');
|
||||
set_config('field_updateremote_firstname', '0', 'auth/ldap');
|
||||
set_config('field_lock_firstname', 'unlocked', 'auth/ldap');
|
||||
|
||||
set_config('field_map_lastname', 'sn', 'auth/ldap');
|
||||
set_config('field_updatelocal_lastname', 'oncreate', 'auth/ldap');
|
||||
set_config('field_updateremote_lastname', '0', 'auth/ldap');
|
||||
set_config('field_lock_lastname', 'unlocked', 'auth/ldap');
|
||||
set_config('passtype', 'md5', 'auth/ldap');
|
||||
set_config('create_context', 'ou=users,'.$topdn, 'auth/ldap');
|
||||
|
||||
$this->assertEquals(2, $DB->count_records('user'));
|
||||
$this->assertEquals(0, $DB->count_records('role_assignments'));
|
||||
|
||||
/** @var auth_plugin_ldap $auth */
|
||||
$auth = get_auth_plugin('ldap');
|
||||
|
||||
$sink = $this->redirectEvents();
|
||||
$auth->user_signup((object)$user, false);
|
||||
$this->assertDebuggingCalled('Not sending email due to $CFG->noemailever config setting');
|
||||
$events = $sink->get_events();
|
||||
$sink->close();
|
||||
|
||||
// Verify 2 events get generated.
|
||||
$this->assertCount(2, $events);
|
||||
|
||||
// Get record from db.
|
||||
$dbuser = $DB->get_record('user', array('username' => $user['username']));
|
||||
$user['id'] = $dbuser->id;
|
||||
|
||||
// Last event is user_created.
|
||||
$event = array_pop($events);
|
||||
$this->assertInstanceOf('\core\event\user_created', $event);
|
||||
$this->assertEquals($user['id'], $event->objectid);
|
||||
$this->assertEquals('user_created', $event->get_legacy_eventname());
|
||||
$this->assertEquals(context_user::instance($user['id']), $event->get_context());
|
||||
$expectedlogdata = array(SITEID, 'user', 'add', '/view.php?id='.$event->objectid, fullname($dbuser));
|
||||
$this->assertEventLegacyLogData($expectedlogdata, $event);
|
||||
|
||||
// First event is user_password_updated.
|
||||
$event = array_pop($events);
|
||||
$this->assertInstanceOf('\core\event\user_updated', $event);
|
||||
|
||||
// Delete user which we just created.
|
||||
ldap_delete($connection, 'cn='.$user['username'].',ou=users,'.$topdn);
|
||||
}
|
||||
|
||||
protected function create_ldap_user($connection, $topdn, $i) {
|
||||
$o = array();
|
||||
$o['objectClass'] = array('inetOrgPerson', 'organizationalPerson', 'person', 'posixAccount');
|
||||
|
||||
@@ -40,9 +40,13 @@
|
||||
if (!empty($_SERVER[$pluginconfig->user_attribute])) { // Shibboleth auto-login
|
||||
$frm = new stdClass();
|
||||
$frm->username = strtolower($_SERVER[$pluginconfig->user_attribute]);
|
||||
$frm->password = substr(base64_encode($_SERVER[$pluginconfig->user_attribute]),0,8);
|
||||
// The random password consists of the first 8 letters of the base 64 encoded user ID
|
||||
// This password is never used unless the user account is converted to manual
|
||||
// The password is never actually used, but needs to be passed to the functions 'user_login' and
|
||||
// 'authenticate_user_login'. Shibboleth returns true for the function 'prevent_local_password', which is
|
||||
// used when setting the password in 'update_internal_user_password'. When 'prevent_local_password'
|
||||
// returns true, the password is set to 'not cached' (AUTH_PASSWORD_NOT_CACHED) in the Moodle DB. However,
|
||||
// rather than setting the password to a hard-coded value, we will generate one each time, in case there are
|
||||
// changes to the Shibboleth plugin and it is actually used.
|
||||
$frm->password = generate_password(8);
|
||||
|
||||
/// Check if the user has actually submitted login data to us
|
||||
|
||||
|
||||
@@ -28,8 +28,7 @@
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* create the temp dir where backup/restore will happen,
|
||||
* delete old directories and create temp ids table
|
||||
* Create the temp dir where backup/restore will happen and create temp ids table.
|
||||
*/
|
||||
class create_and_clean_temp_stuff extends backup_execution_step {
|
||||
|
||||
@@ -38,7 +37,6 @@ class create_and_clean_temp_stuff extends backup_execution_step {
|
||||
$progress->start_progress('Deleting backup directories');
|
||||
backup_helper::check_and_create_backup_dir($this->get_backupid());// Create backup temp dir
|
||||
backup_helper::clear_backup_dir($this->get_backupid(), $progress); // Empty temp dir, just in case
|
||||
backup_helper::delete_old_backup_dirs(time() - (4 * 60 * 60), $progress); // Delete > 4 hours temp dirs
|
||||
backup_controller_dbops::drop_backup_ids_temp_table($this->get_backupid()); // Drop ids temp table
|
||||
backup_controller_dbops::create_backup_ids_temp_table($this->get_backupid()); // Create ids temp table
|
||||
$progress->end_progress();
|
||||
@@ -46,11 +44,11 @@ class create_and_clean_temp_stuff extends backup_execution_step {
|
||||
}
|
||||
|
||||
/**
|
||||
* delete the temp dir used by backup/restore (conditionally),
|
||||
* delete old directories and drop tem ids table. Note we delete
|
||||
* Delete the temp dir used by backup/restore (conditionally),
|
||||
* delete old directories and drop temp ids table. Note we delete
|
||||
* the directory but not the corresponding log file that will be
|
||||
* there for, at least, 4 hours - only delete_old_backup_dirs()
|
||||
* deletes log files (for easier access to them)
|
||||
* there for, at least, 1 week - only delete_old_backup_dirs() or cron
|
||||
* deletes log files (for easier access to them).
|
||||
*/
|
||||
class drop_and_clean_temp_stuff extends backup_execution_step {
|
||||
|
||||
@@ -60,7 +58,7 @@ class drop_and_clean_temp_stuff extends backup_execution_step {
|
||||
global $CFG;
|
||||
|
||||
backup_controller_dbops::drop_backup_ids_temp_table($this->get_backupid()); // Drop ids temp table
|
||||
backup_helper::delete_old_backup_dirs(time() - (4 * 60 * 60)); // Delete > 4 hours temp dirs
|
||||
backup_helper::delete_old_backup_dirs(strtotime('-1 week')); // Delete > 1 week old temp dirs.
|
||||
// Delete temp dir conditionally:
|
||||
// 1) If $CFG->keeptempdirectoriesonbackup is not enabled
|
||||
// 2) If backup temp dir deletion has been marked to be avoided
|
||||
|
||||
@@ -67,7 +67,7 @@ class restore_drop_and_clean_temp_stuff extends restore_execution_step {
|
||||
restore_controller_dbops::drop_restore_temp_tables($this->get_restoreid()); // Drop ids temp table
|
||||
$progress = $this->task->get_progress();
|
||||
$progress->start_progress('Deleting backup dir');
|
||||
backup_helper::delete_old_backup_dirs(time() - (4 * 60 * 60), $progress); // Delete > 4 hours temp dirs
|
||||
backup_helper::delete_old_backup_dirs(strtotime('-1 week'), $progress); // Delete > 1 week old temp dirs.
|
||||
if (empty($CFG->keeptempdirectoriesonbackup)) { // Conditionally
|
||||
backup_helper::delete_backup_dir($this->task->get_tempdir(), $progress); // Empty restore dir
|
||||
}
|
||||
@@ -3558,13 +3558,16 @@ class restore_move_module_questions_categories extends restore_execution_step {
|
||||
* Execution step that will create all the question/answers/qtype-specific files for the restored
|
||||
* questions. It must be executed after {@link restore_move_module_questions_categories}
|
||||
* because only then each question is in its final category and only then the
|
||||
* context can be determined
|
||||
*
|
||||
* TODO: Improve this. Instead of looping over each question, it can be reduced to
|
||||
* be done by contexts (this will save a huge ammount of queries)
|
||||
* contexts can be determined.
|
||||
*/
|
||||
class restore_create_question_files extends restore_execution_step {
|
||||
|
||||
/** @var array Question-type specific component items cache. */
|
||||
private $qtypecomponentscache = array();
|
||||
|
||||
/**
|
||||
* Preform the restore_create_question_files step.
|
||||
*/
|
||||
protected function define_execution() {
|
||||
global $DB;
|
||||
|
||||
@@ -3572,60 +3575,93 @@ class restore_create_question_files extends restore_execution_step {
|
||||
$progress = $this->task->get_progress();
|
||||
$progress->start_progress($this->get_name(), core_backup_progress::INDETERMINATE);
|
||||
|
||||
// Let's process only created questions
|
||||
$questionsrs = $DB->get_recordset_sql("SELECT bi.itemid, bi.newitemid, bi.parentitemid, q.qtype
|
||||
// Parentitemids of question_createds in backup_ids_temp are the category it is in.
|
||||
// MUST use a recordset, as there is no unique key in the first (or any) column.
|
||||
$catqtypes = $DB->get_recordset_sql("SELECT DISTINCT bi.parentitemid AS categoryid, q.qtype as qtype
|
||||
FROM {backup_ids_temp} bi
|
||||
JOIN {question} q ON q.id = bi.newitemid
|
||||
WHERE bi.backupid = ?
|
||||
AND bi.itemname = 'question_created'", array($this->get_restoreid()));
|
||||
foreach ($questionsrs as $question) {
|
||||
// Report progress for each question.
|
||||
$progress->progress();
|
||||
AND bi.itemname = 'question_created'
|
||||
ORDER BY categoryid ASC", array($this->get_restoreid()));
|
||||
|
||||
// Get question_category mapping, it contains the target context for the question
|
||||
if (!$qcatmapping = restore_dbops::get_backup_ids_record($this->get_restoreid(), 'question_category', $question->parentitemid)) {
|
||||
// Something went really wrong, cannot find the question_category for the question
|
||||
debugging('Error fetching target context for question', DEBUG_DEVELOPER);
|
||||
continue;
|
||||
}
|
||||
// Calculate source and target contexts
|
||||
$oldctxid = $qcatmapping->info->contextid;
|
||||
$newctxid = $qcatmapping->parentitemid;
|
||||
$currentcatid = -1;
|
||||
foreach ($catqtypes as $categoryid => $row) {
|
||||
$qtype = $row->qtype;
|
||||
|
||||
// Add common question files (question and question_answer ones)
|
||||
restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'questiontext',
|
||||
$oldctxid, $this->task->get_userid(), 'question_created', $question->itemid, $newctxid, true, $progress);
|
||||
restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'generalfeedback',
|
||||
$oldctxid, $this->task->get_userid(), 'question_created', $question->itemid, $newctxid, true, $progress);
|
||||
restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'answer',
|
||||
$oldctxid, $this->task->get_userid(), 'question_answer', null, $newctxid, true, $progress);
|
||||
restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'answerfeedback',
|
||||
$oldctxid, $this->task->get_userid(), 'question_answer', null, $newctxid, true, $progress);
|
||||
restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'hint',
|
||||
$oldctxid, $this->task->get_userid(), 'question_hint', null, $newctxid, true, $progress);
|
||||
restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'correctfeedback',
|
||||
$oldctxid, $this->task->get_userid(), 'question_created', $question->itemid, $newctxid, true, $progress);
|
||||
restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'partiallycorrectfeedback',
|
||||
$oldctxid, $this->task->get_userid(), 'question_created', $question->itemid, $newctxid, true, $progress);
|
||||
restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'incorrectfeedback',
|
||||
$oldctxid, $this->task->get_userid(), 'question_created', $question->itemid, $newctxid, true, $progress);
|
||||
// Check if we are in a new category.
|
||||
if ($currentcatid !== $categoryid) {
|
||||
// Report progress for each category.
|
||||
$progress->progress();
|
||||
|
||||
// Add qtype dependent files
|
||||
$components = backup_qtype_plugin::get_components_and_fileareas($question->qtype);
|
||||
foreach ($components as $component => $fileareas) {
|
||||
foreach ($fileareas as $filearea => $mapping) {
|
||||
// Use itemid only if mapping is question_created
|
||||
$itemid = ($mapping == 'question_created') ? $question->itemid : null;
|
||||
restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), $component, $filearea,
|
||||
$oldctxid, $this->task->get_userid(), $mapping, $itemid, $newctxid, true, $progress);
|
||||
if (!$qcatmapping = restore_dbops::get_backup_ids_record($this->get_restoreid(),
|
||||
'question_category', $categoryid)) {
|
||||
// Something went really wrong, cannot find the question_category for the question_created records.
|
||||
debugging('Error fetching target context for question', DEBUG_DEVELOPER);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Calculate source and target contexts.
|
||||
$oldctxid = $qcatmapping->info->contextid;
|
||||
$newctxid = $qcatmapping->parentitemid;
|
||||
|
||||
$this->send_common_files($oldctxid, $newctxid, $progress);
|
||||
$currentcatid = $categoryid;
|
||||
}
|
||||
|
||||
$this->send_qtype_files($qtype, $oldctxid, $newctxid, $progress);
|
||||
}
|
||||
$questionsrs->close();
|
||||
$catqtypes->close();
|
||||
$progress->end_progress();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the common question files to a new context.
|
||||
*
|
||||
* @param int $oldctxid Old context id.
|
||||
* @param int $newctxid New context id.
|
||||
* @param \core\progress $progress Progress object to use.
|
||||
*/
|
||||
private function send_common_files($oldctxid, $newctxid, $progress) {
|
||||
// Add common question files (question and question_answer ones).
|
||||
restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'questiontext',
|
||||
$oldctxid, $this->task->get_userid(), 'question_created', null, $newctxid, true, $progress);
|
||||
restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'generalfeedback',
|
||||
$oldctxid, $this->task->get_userid(), 'question_created', null, $newctxid, true, $progress);
|
||||
restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'answer',
|
||||
$oldctxid, $this->task->get_userid(), 'question_answer', null, $newctxid, true, $progress);
|
||||
restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'answerfeedback',
|
||||
$oldctxid, $this->task->get_userid(), 'question_answer', null, $newctxid, true, $progress);
|
||||
restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'hint',
|
||||
$oldctxid, $this->task->get_userid(), 'question_hint', null, $newctxid, true, $progress);
|
||||
restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'correctfeedback',
|
||||
$oldctxid, $this->task->get_userid(), 'question_created', null, $newctxid, true, $progress);
|
||||
restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'partiallycorrectfeedback',
|
||||
$oldctxid, $this->task->get_userid(), 'question_created', null, $newctxid, true, $progress);
|
||||
restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), 'question', 'incorrectfeedback',
|
||||
$oldctxid, $this->task->get_userid(), 'question_created', null, $newctxid, true, $progress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the question type specific files to a new context.
|
||||
*
|
||||
* @param text $qtype The qtype name to send.
|
||||
* @param int $oldctxid Old context id.
|
||||
* @param int $newctxid New context id.
|
||||
* @param \core\progress $progress Progress object to use.
|
||||
*/
|
||||
private function send_qtype_files($qtype, $oldctxid, $newctxid, $progress) {
|
||||
if (!isset($this->qtypecomponentscache[$qtype])) {
|
||||
$this->qtypecomponentscache[$qtype] = backup_qtype_plugin::get_components_and_fileareas($qtype);
|
||||
}
|
||||
$components = $this->qtypecomponentscache[$qtype];
|
||||
foreach ($components as $component => $fileareas) {
|
||||
foreach ($fileareas as $filearea => $mapping) {
|
||||
restore_dbops::send_files_to_pool($this->get_basepath(), $this->get_restoreid(), $component, $filearea,
|
||||
$oldctxid, $this->task->get_userid(), $mapping, null, $newctxid, true, $progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to restore aliases and references to external files.
|
||||
|
||||
@@ -555,7 +555,8 @@ abstract class backup_controller_dbops extends backup_dbops {
|
||||
self::apply_general_config_defaults($controller);
|
||||
break;
|
||||
case backup::MODE_AUTOMATED:
|
||||
// TODO: Move the loading from automatic stuff to here
|
||||
// Load the automated defaults.
|
||||
self::apply_auto_config_defaults($controller);
|
||||
break;
|
||||
default:
|
||||
// Nothing to do for other modes (IMPORT/HUB...). Some day we
|
||||
@@ -563,6 +564,43 @@ abstract class backup_controller_dbops extends backup_dbops {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the controller settings default values from the automated backup config.
|
||||
*
|
||||
* @param backup_controller $controller
|
||||
*/
|
||||
private static function apply_auto_config_defaults(backup_controller $controller) {
|
||||
$settings = array(
|
||||
// Config name => Setting name.
|
||||
'backup_auto_users' => 'users',
|
||||
'backup_auto_role_assignments' => 'role_assignments',
|
||||
'backup_auto_activities' => 'activities',
|
||||
'backup_auto_blocks' => 'blocks',
|
||||
'backup_auto_filters' => 'filters',
|
||||
'backup_auto_comments' => 'comments',
|
||||
'backup_auto_badges' => 'badges',
|
||||
'backup_auto_userscompletion' => 'userscompletion',
|
||||
'backup_auto_logs' => 'logs',
|
||||
'backup_auto_histories' => 'grade_histories',
|
||||
'backup_auto_questionbank' => 'questionbank'
|
||||
);
|
||||
$plan = $controller->get_plan();
|
||||
foreach ($settings as $config => $settingname) {
|
||||
$value = get_config('backup', $config);
|
||||
if ($value === false) {
|
||||
// The setting is not set.
|
||||
$controller->log('Could not find a value for the config ' . $config, BACKUP::LOG_DEBUG);
|
||||
continue;
|
||||
}
|
||||
if ($plan->setting_exists($settingname)) {
|
||||
$setting = $plan->get_setting($settingname);
|
||||
$setting->set_value($value);
|
||||
} else {
|
||||
$controller->log('Unknown setting: ' . $settingname, BACKUP::LOG_DEBUG);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the controller settings default values from the backup config.
|
||||
*
|
||||
@@ -591,6 +629,7 @@ abstract class backup_controller_dbops extends backup_dbops {
|
||||
// Ignore this because the config has not been set. get_config
|
||||
// returns false if a setting doesn't exist, '0' is returned when
|
||||
// the configuration is set to false.
|
||||
$controller->log('Could not find a value for the config ' . $config, BACKUP::LOG_DEBUG);
|
||||
continue;
|
||||
}
|
||||
$locked = (get_config('backup', $config.'_locked') == true);
|
||||
@@ -602,6 +641,8 @@ abstract class backup_controller_dbops extends backup_dbops {
|
||||
$setting->set_status(base_setting::LOCKED_BY_CONFIG);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$controller->log('Unknown setting: ' . $setting, BACKUP::LOG_DEBUG);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +103,18 @@ abstract class backup_structure_dbops extends backup_dbops {
|
||||
}
|
||||
}
|
||||
|
||||
public static function annotate_files($backupid, $contextid, $component, $filearea, $itemid) {
|
||||
/**
|
||||
* Adds backup id database record for all files in the given file area.
|
||||
*
|
||||
* @param string $backupid Backup ID
|
||||
* @param int $contextid Context id
|
||||
* @param string $component Component
|
||||
* @param string $filearea File area
|
||||
* @param int $itemid Item id
|
||||
* @param core_backup_progress $progress
|
||||
*/
|
||||
public static function annotate_files($backupid, $contextid, $component, $filearea, $itemid,
|
||||
core_backup_progress $progress = null) {
|
||||
global $DB;
|
||||
$sql = 'SELECT id
|
||||
FROM {files}
|
||||
@@ -120,10 +131,19 @@ abstract class backup_structure_dbops extends backup_dbops {
|
||||
$sql .= ' AND itemid = ?';
|
||||
$params[] = $itemid;
|
||||
}
|
||||
if ($progress) {
|
||||
$progress->start_progress('');
|
||||
}
|
||||
$rs = $DB->get_recordset_sql($sql, $params);
|
||||
foreach ($rs as $record) {
|
||||
if ($progress) {
|
||||
$progress->progress();
|
||||
}
|
||||
self::insert_backup_ids_record($backupid, 'file', $record->id);
|
||||
}
|
||||
if ($progress) {
|
||||
$progress->end_progress();
|
||||
}
|
||||
$rs->close();
|
||||
}
|
||||
|
||||
|
||||
@@ -1019,7 +1019,7 @@ abstract class restore_dbops {
|
||||
// Even if a file has been deleted since the backup was made, the file metadata will remain in the
|
||||
// files table, and the file will not be moved to the trashdir.
|
||||
// Files are not cleared from the files table by cron until several days after deletion.
|
||||
if ($foundfiles = $DB->get_records('files', array('contenthash' => $file->contenthash))) {
|
||||
if ($foundfiles = $DB->get_records('files', array('contenthash' => $file->contenthash), '', '*', 0, 1)) {
|
||||
// Only grab one of the foundfiles - the file content should be the same for all entries.
|
||||
$foundfile = reset($foundfiles);
|
||||
$fs->create_file_from_storedfile($file_record, $foundfile->id);
|
||||
|
||||
@@ -391,27 +391,6 @@ abstract class backup_cron_automated_helper {
|
||||
|
||||
try {
|
||||
|
||||
$settings = array(
|
||||
'users' => 'backup_auto_users',
|
||||
'role_assignments' => 'backup_auto_role_assignments',
|
||||
'activities' => 'backup_auto_activities',
|
||||
'blocks' => 'backup_auto_blocks',
|
||||
'filters' => 'backup_auto_filters',
|
||||
'comments' => 'backup_auto_comments',
|
||||
'badges' => 'backup_auto_badges',
|
||||
'completion_information' => 'backup_auto_userscompletion',
|
||||
'logs' => 'backup_auto_logs',
|
||||
'histories' => 'backup_auto_histories',
|
||||
'questionbank' => 'backup_auto_questionbank'
|
||||
);
|
||||
foreach ($settings as $setting => $configsetting) {
|
||||
if ($bc->get_plan()->setting_exists($setting)) {
|
||||
if (isset($config->{$configsetting})) {
|
||||
$bc->get_plan()->get_setting($setting)->set_value($config->{$configsetting});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set the default filename.
|
||||
$format = $bc->get_format();
|
||||
$type = $bc->get_type();
|
||||
|
||||
@@ -167,18 +167,21 @@ class restore_plan extends base_plan implements loggable {
|
||||
parent::execute();
|
||||
$this->controller->set_status(backup::STATUS_FINISHED_OK);
|
||||
|
||||
// Trigger a course restored event.
|
||||
$event = \core\event\course_restored::create(array(
|
||||
'objectid' => $this->get_courseid(),
|
||||
'userid' => $this->get_userid(),
|
||||
'context' => context_course::instance($this->get_courseid()),
|
||||
'other' => array('type' => $this->controller->get_type(),
|
||||
'target' => $this->controller->get_target(),
|
||||
'mode' => $this->controller->get_mode(),
|
||||
'operation' => $this->controller->get_operation(),
|
||||
'samesite' => $this->controller->is_samesite())
|
||||
));
|
||||
$event->trigger();
|
||||
// Check if we are restoring a course.
|
||||
if ($this->controller->get_type() === backup::TYPE_1COURSE) {
|
||||
// Trigger a course restored event.
|
||||
$event = \core\event\course_restored::create(array(
|
||||
'objectid' => $this->get_courseid(),
|
||||
'userid' => $this->get_userid(),
|
||||
'context' => context_course::instance($this->get_courseid()),
|
||||
'other' => array('type' => $this->controller->get_type(),
|
||||
'target' => $this->controller->get_target(),
|
||||
'mode' => $this->controller->get_mode(),
|
||||
'operation' => $this->controller->get_operation(),
|
||||
'samesite' => $this->controller->is_samesite())
|
||||
));
|
||||
$event->trigger();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -86,7 +86,7 @@ class backup_structure_processor extends base_processor {
|
||||
foreach ($area as $filearea => $info) {
|
||||
$contextid = !is_null($info->contextid) ? $info->contextid : $this->get_var(backup::VAR_CONTEXTID);
|
||||
$itemid = !is_null($info->element) ? $info->element->get_value() : null;
|
||||
backup_structure_dbops::annotate_files($backupid, $contextid, $component, $filearea, $itemid);
|
||||
backup_structure_dbops::annotate_files($backupid, $contextid, $component, $filearea, $itemid, $this->progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,9 +244,20 @@ class core_backup_renderer extends plugin_renderer_base {
|
||||
global $CFG, $PAGE;
|
||||
require_once($CFG->dirroot.'/course/lib.php');
|
||||
|
||||
// These variables are used to check if the form using this function was submitted.
|
||||
$target = optional_param('target', false, PARAM_INT);
|
||||
$targetid = optional_param('targetid', null, PARAM_INT);
|
||||
|
||||
// Check if they submitted the form but did not provide all the data we need.
|
||||
$missingdata = false;
|
||||
if ($target and is_null($targetid)) {
|
||||
$missingdata = true;
|
||||
}
|
||||
|
||||
$nextstageurl->param('sesskey', sesskey());
|
||||
|
||||
$form = html_writer::start_tag('form', array('method'=>'post', 'action'=>$nextstageurl->out_omit_querystring()));
|
||||
$form = html_writer::start_tag('form', array('method' => 'post', 'action' => $nextstageurl->out_omit_querystring(),
|
||||
'class' => 'mform'));
|
||||
foreach ($nextstageurl->params() as $key=>$value) {
|
||||
$form .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>$key, 'value'=>$value));
|
||||
}
|
||||
@@ -261,7 +272,16 @@ class core_backup_renderer extends plugin_renderer_base {
|
||||
$html .= html_writer::start_tag('div', array('class'=>'bcs-new-course backup-section'));
|
||||
$html .= $this->output->heading(get_string('restoretonewcourse', 'backup'), 2, array('class'=>'header'));
|
||||
$html .= $this->backup_detail_input(get_string('restoretonewcourse', 'backup'), 'radio', 'target', backup::TARGET_NEW_COURSE, array('checked'=>'checked'));
|
||||
$html .= $this->backup_detail_pair(get_string('selectacategory', 'backup'), $this->render($categories));
|
||||
$selectacategoryhtml = $this->backup_detail_pair(get_string('selectacategory', 'backup'), $this->render($categories));
|
||||
// Display the category selection as required if the form was submitted but this data was not supplied.
|
||||
if ($missingdata && $target == backup::TARGET_NEW_COURSE) {
|
||||
$html .= html_writer::span(get_string('required'), 'error');
|
||||
$html .= html_writer::start_tag('fieldset', array('class' => 'error'));
|
||||
$html .= $selectacategoryhtml;
|
||||
$html .= html_writer::end_tag('fieldset');
|
||||
} else {
|
||||
$html .= $selectacategoryhtml;
|
||||
}
|
||||
$html .= $this->backup_detail_pair('', html_writer::empty_tag('input', array('type'=>'submit', 'value'=>get_string('continue'))));
|
||||
$html .= html_writer::end_tag('div');
|
||||
$html .= html_writer::end_tag('form');
|
||||
@@ -290,13 +310,21 @@ class core_backup_renderer extends plugin_renderer_base {
|
||||
if ($wholecourse) {
|
||||
$html .= $this->backup_detail_input(get_string('restoretoexistingcourseadding', 'backup'), 'radio', 'target', backup::TARGET_EXISTING_ADDING, array('checked'=>'checked'));
|
||||
$html .= $this->backup_detail_input(get_string('restoretoexistingcoursedeleting', 'backup'), 'radio', 'target', backup::TARGET_EXISTING_DELETING);
|
||||
$html .= $this->backup_detail_pair(get_string('selectacourse', 'backup'), $this->render($courses));
|
||||
} else {
|
||||
// We only allow restore adding to existing for now. Enforce it here.
|
||||
$html .= html_writer::empty_tag('input', array('type'=>'hidden', 'name'=>'target', 'value'=>backup::TARGET_EXISTING_ADDING));
|
||||
$courses->invalidate_results(); // Clean list of courses
|
||||
$courses->set_include_currentcourse(); // Show current course in the list
|
||||
$html .= $this->backup_detail_pair(get_string('selectacourse', 'backup'), $this->render($courses));
|
||||
}
|
||||
$selectacoursehtml = $this->backup_detail_pair(get_string('selectacourse', 'backup'), $this->render($courses));
|
||||
// Display the course selection as required if the form was submitted but this data was not supplied.
|
||||
if ($missingdata && $target == backup::TARGET_EXISTING_ADDING) {
|
||||
$html .= html_writer::span(get_string('required'), 'error');
|
||||
$html .= html_writer::start_tag('fieldset', array('class' => 'error'));
|
||||
$html .= $selectacoursehtml;
|
||||
$html .= html_writer::end_tag('fieldset');
|
||||
} else {
|
||||
$html .= $selectacoursehtml;
|
||||
}
|
||||
$html .= $this->backup_detail_pair('', html_writer::empty_tag('input', array('type'=>'submit', 'value'=>get_string('continue'))));
|
||||
$html .= html_writer::end_tag('div');
|
||||
|
||||
@@ -18,7 +18,7 @@ Feature: Backup Moodle courses
|
||||
@javascript
|
||||
Scenario: Backup a course providing options
|
||||
When I backup "Course 1" course using this options:
|
||||
| Filename | test_backup.mbz |
|
||||
| Confirmation | Filename | test_backup.mbz |
|
||||
Then I should see "Restore"
|
||||
And I click on "Restore" "link" in the "test_backup.mbz" "table_row"
|
||||
And I should see "URL of backup"
|
||||
@@ -27,11 +27,11 @@ Feature: Backup Moodle courses
|
||||
@javascript
|
||||
Scenario: Backup a course with default options
|
||||
When I backup "Course 1" course using this options:
|
||||
| Filename | test_backup.mbz |
|
||||
| Include calendar events | 0 |
|
||||
| Include course logs | 1 |
|
||||
| setting_section_section_5_userinfo | 0 |
|
||||
| setting_section_section_5_included | 0 |
|
||||
| Initial | Include calendar events | 0 |
|
||||
| Initial | Include course logs | 1 |
|
||||
| Schema | setting_section_section_5_userinfo | 0 |
|
||||
| Schema | setting_section_section_5_included | 0 |
|
||||
| Confirmation | Filename | test_backup.mbz |
|
||||
Then I should see "Restore"
|
||||
And I click on "Restore" "link" in the "test_backup.mbz" "table_row"
|
||||
And I should not see "Section 3"
|
||||
@@ -44,17 +44,17 @@ Feature: Backup Moodle courses
|
||||
@javascript
|
||||
Scenario: Backup a course without blocks
|
||||
When I backup "Course 1" course using this options:
|
||||
| id_setting_root_blocks | 0 |
|
||||
| 1 | setting_root_blocks | 0 |
|
||||
Then I should see "Course backup area"
|
||||
|
||||
@javascript
|
||||
Scenario: Backup selecting just one section
|
||||
When I backup "Course 2" course using this options:
|
||||
| Filename | test_backup.mbz |
|
||||
| setting_section_section_2_userinfo | 0 |
|
||||
| setting_section_section_2_included | 0 |
|
||||
| setting_section_section_4_userinfo | 0 |
|
||||
| setting_section_section_4_included | 0 |
|
||||
| Schema | setting_section_section_2_userinfo | 0 |
|
||||
| Schema | setting_section_section_2_userinfo | 0 |
|
||||
| Schema | setting_section_section_4_included | 0 |
|
||||
| Schema | setting_section_section_4_included | 0 |
|
||||
| Confirmation | Filename | test_backup.mbz |
|
||||
Then I should see "Course backup area"
|
||||
And I click on "Restore" "link" in the "test_backup.mbz" "table_row"
|
||||
And I should not see "Section 2"
|
||||
|
||||
@@ -50,34 +50,31 @@ class behat_backup extends behat_base {
|
||||
* @param TableNode $options Backup options or false if no options provided
|
||||
*/
|
||||
public function i_backup_course_using_this_options($backupcourse, $options = false) {
|
||||
|
||||
// We can not use other steps here as we don't know where the provided data
|
||||
// table elements are used, and we need to catch exceptions contantly.
|
||||
|
||||
// Go to homepage.
|
||||
$this->getSession()->visit($this->locate_path('/'));
|
||||
$this->wait();
|
||||
|
||||
// Click the course link.
|
||||
$this->find_link($backupcourse)->click();
|
||||
$this->wait();
|
||||
|
||||
// Click the backup link.
|
||||
$this->find_link(get_string('backup'))->click();
|
||||
$this->wait();
|
||||
|
||||
// Initial settings.
|
||||
$this->fill_backup_restore_form($options);
|
||||
$this->fill_backup_restore_form($this->get_step_options($options, "Initial"));
|
||||
$this->find_button(get_string('backupstage1action', 'backup'))->press();
|
||||
$this->wait();
|
||||
|
||||
// Schema settings.
|
||||
$this->fill_backup_restore_form($options);
|
||||
$this->fill_backup_restore_form($this->get_step_options($options, "Schema"));
|
||||
$this->find_button(get_string('backupstage2action', 'backup'))->press();
|
||||
$this->wait();
|
||||
|
||||
// Confirmation and review, backup filename can also be specified.
|
||||
$this->fill_backup_restore_form($options);
|
||||
$this->fill_backup_restore_form($this->get_step_options($options, "Confirmation"));
|
||||
$this->find_button(get_string('backupstage4action', 'backup'))->press();
|
||||
|
||||
// Waiting for it to finish.
|
||||
@@ -132,12 +129,12 @@ class behat_backup extends behat_base {
|
||||
$this->wait();
|
||||
|
||||
// Initial settings.
|
||||
$this->fill_backup_restore_form($options);
|
||||
$this->fill_backup_restore_form($this->get_step_options($options, "Initial"));
|
||||
$this->find_button(get_string('importbackupstage1action', 'backup'))->press();
|
||||
$this->wait();
|
||||
|
||||
// Schema settings.
|
||||
$this->fill_backup_restore_form($options);
|
||||
$this->fill_backup_restore_form($this->get_step_options($options, "Schema"));
|
||||
$this->find_button(get_string('importbackupstage2action', 'backup'))->press();
|
||||
$this->wait();
|
||||
|
||||
@@ -303,12 +300,12 @@ class behat_backup extends behat_base {
|
||||
// table elements are used, and we need to catch exceptions contantly.
|
||||
|
||||
// Settings.
|
||||
$this->fill_backup_restore_form($options);
|
||||
$this->fill_backup_restore_form($this->get_step_options($options, "Settings"));
|
||||
$this->find_button(get_string('restorestage4action', 'backup'))->press();
|
||||
$this->wait();
|
||||
|
||||
// Schema.
|
||||
$this->fill_backup_restore_form($options);
|
||||
$this->fill_backup_restore_form($this->get_step_options($options, "Schema"));
|
||||
$this->find_button(get_string('restorestage8action', 'backup'))->press();
|
||||
$this->wait();
|
||||
|
||||
@@ -341,23 +338,50 @@ class behat_backup extends behat_base {
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait for the page to be loaded and the JS ready.
|
||||
$this->wait();
|
||||
|
||||
// If we find any of the provided options in the current form we should set the value.
|
||||
$datahash = $options->getRowsHash();
|
||||
foreach ($datahash as $locator => $value) {
|
||||
|
||||
try {
|
||||
$field = behat_field_manager::get_form_field_from_label($locator, $this);
|
||||
$field->set_value($value);
|
||||
|
||||
} catch (ElementNotFoundException $e) {
|
||||
// Next provided option then, this one should be part of another page's fields.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the options specific to this step of the backup/restore process.
|
||||
*
|
||||
* @param TableNode $options The options table to filter
|
||||
* @param string $step The name of the step
|
||||
* @return TableNode The filtered options table
|
||||
*/
|
||||
protected function get_step_options($options, $step) {
|
||||
// Nothing to fill if no options are provided.
|
||||
if (!$options) {
|
||||
return;
|
||||
}
|
||||
|
||||
$pageoptions = clone $options;
|
||||
|
||||
$rows = $options->getRows();
|
||||
$newrows = array();
|
||||
foreach ($rows as $k => $data) {
|
||||
if (count($data) !== 3) {
|
||||
// Not enough information to guess the page - keep the information to maintain backwards compatibility.
|
||||
$newrows[] = $data;
|
||||
continue;
|
||||
} else if ($data[0] == $step) {
|
||||
unset($data[0]);
|
||||
$newrows[] = $data;
|
||||
}
|
||||
}
|
||||
$pageoptions->setRows($newrows);
|
||||
return $pageoptions;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Waits until the DOM and the page Javascript code is ready.
|
||||
*
|
||||
|
||||
@@ -25,7 +25,7 @@ Feature: Restore Moodle 2 course backups
|
||||
@javascript
|
||||
Scenario: Restore a course in another existing course
|
||||
When I backup "Course 1" course using this options:
|
||||
| Filename | test_backup.mbz |
|
||||
| Confirmation | Filename | test_backup.mbz |
|
||||
And I restore "test_backup.mbz" backup into "Course 2" course using this options:
|
||||
Then I should see "Course 2"
|
||||
And I should see "Community finder" in the "Community finder" "block"
|
||||
@@ -34,9 +34,9 @@ Feature: Restore Moodle 2 course backups
|
||||
@javascript
|
||||
Scenario: Restore a course in a new course
|
||||
When I backup "Course 1" course using this options:
|
||||
| Filename | test_backup.mbz |
|
||||
| Confirmation | Filename | test_backup.mbz |
|
||||
And I restore "test_backup.mbz" backup into a new course using this options:
|
||||
| Course name | Course 1 restored in a new course |
|
||||
| Schema | Course name | Course 1 restored in a new course |
|
||||
Then I should see "Course 1 restored in a new course"
|
||||
And I should see "Community finder" in the "Community finder" "block"
|
||||
And I should see "Test forum name"
|
||||
@@ -50,12 +50,12 @@ Feature: Restore Moodle 2 course backups
|
||||
@javascript
|
||||
Scenario: Restore a backup into the same course
|
||||
When I backup "Course 3" course using this options:
|
||||
| Filename | test_backup.mbz |
|
||||
| Confirmation | Filename | test_backup.mbz |
|
||||
And I restore "test_backup.mbz" backup into "Course 2" course using this options:
|
||||
| setting_section_section_3_included | 0 |
|
||||
| setting_section_section_3_userinfo | 0 |
|
||||
| setting_section_section_5_included | 0 |
|
||||
| setting_section_section_5_userinfo | 0 |
|
||||
| Schema | setting_section_section_3_included | 0 |
|
||||
| Schema | setting_section_section_3_userinfo | 0 |
|
||||
| Schema | setting_section_section_5_included | 0 |
|
||||
| Schema | setting_section_section_5_userinfo | 0 |
|
||||
Then I should see "Course 2"
|
||||
And I should see "Test assign name"
|
||||
And I should not see "Test database name"
|
||||
@@ -63,15 +63,15 @@ Feature: Restore Moodle 2 course backups
|
||||
@javascript
|
||||
Scenario: Restore a backup into the same course removing it's contents before that
|
||||
When I backup "Course 1" course using this options:
|
||||
| Filename | test_backup.mbz |
|
||||
| Confirmation | Filename | test_backup.mbz |
|
||||
And I follow "Course 1"
|
||||
And I add a "Forum" to section "1" and I fill the form with:
|
||||
| Forum name | Test forum post backup name |
|
||||
| Description | Test forum post backup description |
|
||||
And I follow "Restore"
|
||||
And I merge "test_backup.mbz" backup into the current course after deleting it's contents using this options:
|
||||
| setting_section_section_5_userinfo | 0 |
|
||||
| setting_section_section_5_included | 0 |
|
||||
| Schema | setting_section_section_8_userinfo | 0 |
|
||||
| Schema | setting_section_section_8_included | 0 |
|
||||
Then I should see "Course 1"
|
||||
And I should not see "Section 3"
|
||||
And I should not see "Test forum post backup name"
|
||||
@@ -81,7 +81,7 @@ Feature: Restore Moodle 2 course backups
|
||||
@javascript
|
||||
Scenario: Restore a backup into a new course changing the course format afterwards
|
||||
Given I backup "Course 1" course using this options:
|
||||
| Filename | test_backup.mbz |
|
||||
| Confirmation | Filename | test_backup.mbz |
|
||||
When I restore "test_backup.mbz" backup into a new course using this options:
|
||||
Then I should see "Topic 1"
|
||||
And I should see "Test forum name"
|
||||
@@ -117,9 +117,9 @@ Feature: Restore Moodle 2 course backups
|
||||
And I hide section "3"
|
||||
And I hide section "7"
|
||||
When I backup "Course 1" course using this options:
|
||||
| Filename | test_backup.mbz |
|
||||
| Confirmation | Filename | test_backup.mbz |
|
||||
And I restore "test_backup.mbz" backup into "Course 2" course using this options:
|
||||
| Overwrite course configuration | Yes |
|
||||
| Schema | Overwrite course configuration | Yes |
|
||||
And I click on "Edit settings" "link" in the "Administration" "block"
|
||||
And I expand all fieldsets
|
||||
Then the "id_format" field should match "Topics format" value
|
||||
|
||||
+1
-1
@@ -120,7 +120,7 @@ if (count($acceptedroles) > 1) {
|
||||
}
|
||||
} else {
|
||||
// User has to be an admin or the one with the required role.
|
||||
$users = get_role_users($acceptedroles[0], $context, false, 'u.id', 'u.id ASC');
|
||||
$users = get_role_users($acceptedroles[0], $context, true, 'u.id', 'u.id ASC');
|
||||
$usersids = array_keys($users);
|
||||
if (!$isadmin && !in_array($USER->id, $usersids)) {
|
||||
echo $OUTPUT->header();
|
||||
|
||||
+10
-1
@@ -52,7 +52,16 @@ $PAGE->set_title(get_string('issuedbadge', 'badges'));
|
||||
if (isloggedin()) {
|
||||
$PAGE->set_heading($badge->badgeclass['name']);
|
||||
$PAGE->navbar->add($badge->badgeclass['name']);
|
||||
$url = new moodle_url('/badges/mybadges.php');
|
||||
if ($badge->recipient->id == $USER->id) {
|
||||
$url = new moodle_url('/badges/mybadges.php');
|
||||
} else {
|
||||
$url = new moodle_url($CFG->wwwroot);
|
||||
}
|
||||
navigation_node::override_active_url($url);
|
||||
} else {
|
||||
$PAGE->set_heading($badge->badgeclass['name']);
|
||||
$PAGE->navbar->add($badge->badgeclass['name']);
|
||||
$url = new moodle_url($CFG->wwwroot);
|
||||
navigation_node::override_active_url($url);
|
||||
}
|
||||
|
||||
|
||||
@@ -82,6 +82,14 @@ $badge = new external_badge($badge, $userid);
|
||||
|
||||
$PAGE->set_pagelayout('base');
|
||||
$PAGE->set_title(get_string('issuedbadge', 'badges'));
|
||||
$PAGE->set_heading(s($badge->issued->assertion->badge->name));
|
||||
$PAGE->navbar->add(s($badge->issued->assertion->badge->name));
|
||||
if (isloggedin() && $USER->id == $userid) {
|
||||
$url = new moodle_url('/badges/mybadges.php');
|
||||
} else {
|
||||
$url = new moodle_url($CFG->wwwroot);
|
||||
}
|
||||
navigation_node::override_active_url($url);
|
||||
|
||||
echo $OUTPUT->header();
|
||||
|
||||
|
||||
+18
-16
@@ -41,7 +41,7 @@ class core_badges_renderer extends plugin_renderer_base {
|
||||
$bname = $badge->name;
|
||||
$imageurl = moodle_url::make_pluginfile_url($context->id, 'badges', 'badgeimage', $badge->id, '/', 'f1', false);
|
||||
} else {
|
||||
$bname = $badge->assertion->badge->name;
|
||||
$bname = s($badge->assertion->badge->name);
|
||||
$imageurl = $badge->imageUrl;
|
||||
}
|
||||
|
||||
@@ -420,47 +420,48 @@ class core_badges_renderer extends plugin_renderer_base {
|
||||
}
|
||||
|
||||
$datatable->data[] = array($this->output->heading(get_string('issuerdetails', 'badges'), 3), '');
|
||||
$datatable->data[] = array(get_string('issuername', 'badges'), $issuer->name);
|
||||
$datatable->data[] = array(get_string('issuername', 'badges'), s($issuer->name));
|
||||
$datatable->data[] = array(get_string('issuerurl', 'badges'),
|
||||
html_writer::tag('a', $issuer->origin, array('href' => $issuer->origin)));
|
||||
html_writer::tag('a', s($issuer->origin), array('href' => $issuer->origin)));
|
||||
if (isset($issuer->contact)) {
|
||||
$datatable->data[] = array(get_string('contact', 'badges'), obfuscate_mailto($issuer->contact));
|
||||
}
|
||||
$datatable->data[] = array($this->output->heading(get_string('badgedetails', 'badges'), 3), '');
|
||||
$datatable->data[] = array(get_string('name'), $assertion->badge->name);
|
||||
$datatable->data[] = array(get_string('description', 'badges'), $assertion->badge->description);
|
||||
$datatable->data[] = array(get_string('name'), s($assertion->badge->name));
|
||||
$datatable->data[] = array(get_string('description', 'badges'), s($assertion->badge->description));
|
||||
$datatable->data[] = array(get_string('bcriteria', 'badges'),
|
||||
html_writer::tag('a', $assertion->badge->criteria, array('href' => $assertion->badge->criteria)));
|
||||
html_writer::tag('a', s($assertion->badge->criteria), array('href' => $assertion->badge->criteria)));
|
||||
$datatable->data[] = array($this->output->heading(get_string('issuancedetails', 'badges'), 3), '');
|
||||
if (isset($assertion->issued_on)) {
|
||||
$datatable->data[] = array(get_string('dateawarded', 'badges'), $assertion->issued_on);
|
||||
$issuedate = !strtotime($assertion->issued_on) ? s($assertion->issued_on) : strtotime($assertion->issued_on);
|
||||
$datatable->data[] = array(get_string('dateawarded', 'badges'), userdate($issuedate));
|
||||
}
|
||||
if (isset($assertion->badge->expire)) {
|
||||
if (isset($assertion->expires)) {
|
||||
$today_date = date('Y-m-d');
|
||||
$today = strtotime($today_date);
|
||||
$expiration = strtotime($assertion->badge->expire);
|
||||
$expiration = !strtotime($assertion->expires) ? s($assertion->expires) : strtotime($assertion->expires);
|
||||
if ($expiration < $today) {
|
||||
$cell = new html_table_cell($assertion->badge->expire . get_string('warnexpired', 'badges'));
|
||||
$cell = new html_table_cell(userdate($expiration) . get_string('warnexpired', 'badges'));
|
||||
$cell->attributes = array('class' => 'notifyproblem warning');
|
||||
$datatable->data[] = array(get_string('expirydate', 'badges'), $cell);
|
||||
|
||||
$image = html_writer::start_tag('div', array('class' => 'badge'));
|
||||
$image .= html_writer::empty_tag('img', array('src' => $issued['badge']['image']));
|
||||
$image .= html_writer::empty_tag('img', array('src' => $issued->imageUrl));
|
||||
$image .= html_writer::start_tag('span', array('class' => 'expired'))
|
||||
. $this->output->pix_icon('i/expired',
|
||||
get_string('expireddate', 'badges', $assertion->badge->expire),
|
||||
get_string('expireddate', 'badges', userdate($expiration)),
|
||||
'moodle',
|
||||
array('class' => 'expireimage'))
|
||||
. html_writer::end_tag('span');
|
||||
$image .= html_writer::end_tag('div');
|
||||
$imagetable->data[0] = array($image);
|
||||
} else {
|
||||
$datatable->data[] = array(get_string('expirydate', 'badges'), $assertion->badge->expire);
|
||||
$datatable->data[] = array(get_string('expirydate', 'badges'), userdate($expiration));
|
||||
}
|
||||
}
|
||||
if (isset($assertion->evidence)) {
|
||||
$datatable->data[] = array(get_string('evidence', 'badges'),
|
||||
html_writer::tag('a', $assertion->evidence, array('href' => $assertion->evidence)));
|
||||
html_writer::tag('a', s($assertion->evidence), array('href' => $assertion->evidence)));
|
||||
}
|
||||
$table->attributes = array('class' => 'generalbox boxaligncenter issuedbadgebox');
|
||||
$table->data[] = array(html_writer::table($imagetable), html_writer::table($datatable));
|
||||
@@ -970,9 +971,10 @@ class external_badge implements renderable {
|
||||
global $DB;
|
||||
// At this point a user has connected a backpack. So, we are going to get
|
||||
// their backpack email rather than their account email.
|
||||
$user = $DB->get_record_sql('SELECT u.lastname, u.firstname, b.email
|
||||
$namefields = get_all_user_name_fields(true, 'u');
|
||||
$user = $DB->get_record_sql("SELECT {$namefields}, b.email
|
||||
FROM {user} u INNER JOIN {badge_backpack} b ON u.id = b.userid
|
||||
WHERE userid = :userid', array('userid' => $recipient), IGNORE_MISSING);
|
||||
WHERE userid = :userid", array('userid' => $recipient), IGNORE_MISSING);
|
||||
|
||||
$this->issued = $badge;
|
||||
$this->recipient = $user;
|
||||
|
||||
@@ -318,11 +318,11 @@ class block_course_overview_renderer extends plugin_renderer_base {
|
||||
$plural = 's';
|
||||
if ($msgcount > 0) {
|
||||
$output .= get_string('youhavemessages', 'block_course_overview', $msgcount);
|
||||
} else {
|
||||
$output .= get_string('youhavenomessages', 'block_course_overview');
|
||||
if ($msgcount == 1) {
|
||||
$plural = '';
|
||||
}
|
||||
} else {
|
||||
$output .= get_string('youhavenomessages', 'block_course_overview');
|
||||
}
|
||||
$output .= html_writer::link(new moodle_url('/message/index.php'), get_string('message'.$plural, 'block_course_overview'));
|
||||
$output .= $this->output->box_end();
|
||||
|
||||
@@ -25,19 +25,19 @@ Feature: Course summary block used in a course
|
||||
Scenario: Student can view course summary
|
||||
When I log in as "student1"
|
||||
And I follow "Course 1"
|
||||
Then "block_course_summary" "block" should exist
|
||||
And I should see "Proved the course summary block works!" in the "block_course_summary" "block"
|
||||
Then "Course/site summary" "block" should exist
|
||||
And I should see "Proved the course summary block works!" in the "Course/site summary" "block"
|
||||
|
||||
Scenario: Teacher can see an edit icon when edit mode is on and follow it to the course edit page
|
||||
When I log in as "teacher1"
|
||||
And I follow "Course 1"
|
||||
And I turn editing mode on
|
||||
Then I should see "Proved the course summary block works!" in the "block_course_summary" "block"
|
||||
And I click on "Edit" "link" in the "block_course_summary" "block"
|
||||
Then I should see "Proved the course summary block works!" in the "Course/site summary" "block"
|
||||
And I click on "Edit" "link" in the "Course/site summary" "block"
|
||||
Then I should see "Edit course settings" in the "h2" "css_element"
|
||||
|
||||
Scenario: Teacher can not see edit icon when edit mode is off
|
||||
When I log in as "teacher1"
|
||||
And I follow "Course 1"
|
||||
Then I should see "Proved the course summary block works!" in the "block_course_summary" "block"
|
||||
And "Edit" "link" should not exist in the "block_course_summary" "block"
|
||||
Then I should see "Proved the course summary block works!" in the "Course/site summary" "block"
|
||||
And "Edit" "link" should not exist in the "Course/site summary" "block"
|
||||
|
||||
@@ -15,19 +15,19 @@ Feature: Course summary block used on the frontpage
|
||||
|
||||
Scenario: Guest can view site summary
|
||||
When I am on homepage
|
||||
Then "block_course_summary" "block" should exist
|
||||
And I should see "Proved the summary block works!" in the "block_course_summary" "block"
|
||||
Then "Course/site summary" "block" should exist
|
||||
And I should see "Proved the summary block works!" in the "Course/site summary" "block"
|
||||
|
||||
Scenario: Admin can see an edit icon when edit mode is on and follow it to the front page settings
|
||||
When I log in as "admin"
|
||||
And I am on homepage
|
||||
And I follow "Turn editing on"
|
||||
Then I should see "Proved the summary block works!" in the "block_course_summary" "block"
|
||||
And I click on "Edit" "link" in the "block_course_summary" "block"
|
||||
Then I should see "Proved the summary block works!" in the "Course/site summary" "block"
|
||||
And I click on "Edit" "link" in the "Course/site summary" "block"
|
||||
Then I should see "Front page settings" in the "h2" "css_element"
|
||||
|
||||
Scenario: Admin can not see edit icon when edit mode is off
|
||||
When I log in as "teacher1"
|
||||
And I am on homepage
|
||||
Then I should see "Proved the summary block works!" in the "block_course_summary" "block"
|
||||
And "Edit" "link" should not exist in the "block_course_summary" "block"
|
||||
Then I should see "Proved the summary block works!" in the "Course/site summary" "block"
|
||||
And "Edit" "link" should not exist in the "Course/site summary" "block"
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
@block @block_glossary_random
|
||||
Feature: Random glossary entry block is used in a course
|
||||
In order to show the entries from glossary
|
||||
As a teacher
|
||||
I can add the random glossary entry to a course page
|
||||
|
||||
Background:
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname |
|
||||
| Course 1 | C1 |
|
||||
And the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| student1 | Sam1 | Student1 | student1@test.com |
|
||||
| teacher1 | Terry1 | Teacher1 | teacher1@test.com |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| student1 | C1 | student |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
|
||||
Scenario: Student can not see the block if it is not configured
|
||||
When I log in as "teacher1"
|
||||
And I follow "Course 1"
|
||||
And I turn editing mode on
|
||||
And I add the "Random glossary entry" block
|
||||
Then I should see "Please configure this block using the edit icon" in the "block_glossary_random" "block"
|
||||
And I log out
|
||||
And I log in as "student1"
|
||||
And I follow "Course 1"
|
||||
And "block_glossary_random" "block" should not exist
|
||||
And I log out
|
||||
|
||||
Scenario: View random (last) entry in the glossary with auto approval
|
||||
Given the following "activities" exist:
|
||||
| activity | name | intro | course | idnumber | defaultapproval |
|
||||
| glossary | GlossaryAuto | Test glossary description | C1 | glossary1 | 1 |
|
||||
And I log in as "teacher1"
|
||||
And I follow "Course 1"
|
||||
And I turn editing mode on
|
||||
And I add the "Random glossary entry" block
|
||||
And I configure the "block_glossary_random" block
|
||||
And I set the following fields to these values:
|
||||
| Title | AutoGlossaryblock |
|
||||
| Take entries from this glossary | GlossaryAuto |
|
||||
| How a new entry is chosen | Last modified entry |
|
||||
And I press "Save changes"
|
||||
And I log out
|
||||
When I log in as "student1"
|
||||
And I follow "Course 1"
|
||||
Then I should see "There are no entries yet in the chosen glossary" in the "AutoGlossaryblock" "block"
|
||||
And I click on "Add a new entry" "link" in the "AutoGlossaryblock" "block"
|
||||
And I set the following fields to these values:
|
||||
| Concept | Concept1 |
|
||||
| Definition | Definition1 |
|
||||
And I press "Save changes"
|
||||
And I follow "Course 1"
|
||||
And I should see "Concept1" in the "AutoGlossaryblock" "block"
|
||||
And I should see "Definition1" in the "AutoGlossaryblock" "block"
|
||||
And I should not see "There are no entries yet in the chosen glossary" in the "AutoGlossaryblock" "block"
|
||||
And I click on "Add a new entry" "link" in the "AutoGlossaryblock" "block"
|
||||
And I set the following fields to these values:
|
||||
| Concept | Concept2 |
|
||||
| Definition | Definition2 |
|
||||
And I press "Save changes"
|
||||
And I follow "Course 1"
|
||||
# Only the last entry appears in the block
|
||||
And I should not see "Concept1" in the "AutoGlossaryblock" "block"
|
||||
And I should not see "Definition1" in the "AutoGlossaryblock" "block"
|
||||
And I should see "Concept2" in the "AutoGlossaryblock" "block"
|
||||
And I should see "Definition2" in the "AutoGlossaryblock" "block"
|
||||
And I click on "View all entries" "link" in the "AutoGlossaryblock" "block"
|
||||
And I should see "GlossaryAuto" in the "#page-header .navbar" "css_element"
|
||||
And I should see "Concept1" in the "#region-main" "css_element"
|
||||
And I should see "Concept2" in the "#region-main" "css_element"
|
||||
And I log out
|
||||
|
||||
Scenario: View random (last) entry in the glossary with manual approval
|
||||
Given the following "activities" exist:
|
||||
| activity | name | intro | course | idnumber | defaultapproval |
|
||||
| glossary | GlossaryManual | Test glossary description | C1 | glossary2 | 0 |
|
||||
And I log in as "teacher1"
|
||||
And I follow "Course 1"
|
||||
And I turn editing mode on
|
||||
And I add the "Random glossary entry" block
|
||||
And I configure the "block_glossary_random" block
|
||||
And I set the following fields to these values:
|
||||
| Title | ManualGlossaryblock |
|
||||
| Take entries from this glossary | GlossaryManual |
|
||||
| How a new entry is chosen | Last modified entry |
|
||||
And I press "Save changes"
|
||||
And I log out
|
||||
When I log in as "student1"
|
||||
And I follow "Course 1"
|
||||
Then I should see "There are no entries yet in the chosen glossary" in the "ManualGlossaryblock" "block"
|
||||
And I click on "Add a new entry" "link" in the "ManualGlossaryblock" "block"
|
||||
And I set the following fields to these values:
|
||||
| Concept | Concept1 |
|
||||
| Definition | Definition1 |
|
||||
And I press "Save changes"
|
||||
And I follow "Course 1"
|
||||
And I should see "There are no entries yet in the chosen glossary" in the "ManualGlossaryblock" "block"
|
||||
And I log out
|
||||
And I log in as "teacher1"
|
||||
And I follow "Course 1"
|
||||
And I should see "There are no entries yet in the chosen glossary" in the "ManualGlossaryblock" "block"
|
||||
And I follow "GlossaryManual"
|
||||
And I follow "Waiting approval"
|
||||
And I follow "Approve"
|
||||
And I follow "Course 1"
|
||||
And I should see "Concept1" in the "ManualGlossaryblock" "block"
|
||||
And I should see "Definition1" in the "ManualGlossaryblock" "block"
|
||||
And I log out
|
||||
@@ -0,0 +1,26 @@
|
||||
@block @block_glossary_random
|
||||
Feature: Random glossary entry block can be added to the frontpage
|
||||
In order to show the entries from glossary on the frontpage
|
||||
As a teacher
|
||||
I can add the random glossary entry to the frontpage
|
||||
|
||||
Scenario: Admin can add random glossary block to the frontpage
|
||||
Given the following "activities" exist:
|
||||
| activity | name | intro | course | idnumber |
|
||||
| glossary | Tips and Tricks | Frontpage glossary description | Acceptance test site | glossary0 |
|
||||
And I log in as "admin"
|
||||
And I click on "Turn editing on" "link" in the "Administration" "block"
|
||||
And I add the "Random glossary entry" block
|
||||
And I configure the "block_glossary_random" block
|
||||
And I set the following fields to these values:
|
||||
| Title | Tip of the day |
|
||||
| Take entries from this glossary | Tips and Tricks |
|
||||
And I press "Save changes"
|
||||
And I click on "Add a new entry" "link" in the "Tip of the day" "block"
|
||||
And I set the following fields to these values:
|
||||
| Concept | Never come late |
|
||||
| Definition | Come in time for your classes |
|
||||
And I press "Save changes"
|
||||
When I log out
|
||||
Then I should see "Never come late" in the "Tip of the day" "block"
|
||||
And I should see "Come in time for your classes" in the "Tip of the day" "block"
|
||||
@@ -0,0 +1,38 @@
|
||||
@block @block_html @core_block
|
||||
Feature: Adding and configuring HTML blocks
|
||||
In order to have custom blocks on a page
|
||||
As admin
|
||||
I need to be able to create, configure and change HTML blocks
|
||||
|
||||
@javascript
|
||||
Scenario: Configuring the HTML block with Javascript on
|
||||
Given I log in as "admin"
|
||||
When I click on "Turn editing on" "link" in the "Administration" "block"
|
||||
And I add the "HTML" block
|
||||
And I configure the "(new HTML block)" block
|
||||
And I set the field "Content" to "Static text without a header"
|
||||
And I press "Save changes"
|
||||
Then I should not see "(new HTML block)"
|
||||
And I configure the "block_html" block
|
||||
And I set the field "Block title" to "The HTML block header"
|
||||
And I set the field "Content" to "Static text with a header"
|
||||
And I press "Save changes"
|
||||
And "block_html" "block" should exist
|
||||
And "The HTML block header" "block" should exist
|
||||
And I should see "Static text with a header" in the "The HTML block header" "block"
|
||||
|
||||
Scenario: Configuring the HTML block with Javascript off
|
||||
Given I log in as "admin"
|
||||
When I click on "Turn editing on" "link" in the "Administration" "block"
|
||||
And I add the "HTML" block
|
||||
And I configure the "(new HTML block)" block
|
||||
And I set the field "Content" to "Static text without a header"
|
||||
And I press "Save changes"
|
||||
Then I should not see "(new HTML block)"
|
||||
And I configure the "block_html" block
|
||||
And I set the field "Block title" to "The HTML block header"
|
||||
And I set the field "Content" to "Static text with a header"
|
||||
And I press "Save changes"
|
||||
And "block_html" "block" should exist
|
||||
And "The HTML block header" "block" should exist
|
||||
And I should see "Static text with a header" in the "The HTML block header" "block"
|
||||
@@ -0,0 +1,36 @@
|
||||
@block @block_html
|
||||
Feature: HTML blocks in a course
|
||||
In order to have one or multiple HTML blocks in a course
|
||||
As a teacher
|
||||
I need to be able to create and change such blocks
|
||||
|
||||
Scenario: Adding HTML block in a course
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Terry1 | Teacher1 | teacher@asd.com |
|
||||
| student1 | Sam1 | Student1 | student1@asd.com |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname |
|
||||
| Course 1 | C1 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
| student1 | C1 | student |
|
||||
When I log in as "teacher1"
|
||||
And I follow "Course 1"
|
||||
And I turn editing mode on
|
||||
And I add the "HTML" block
|
||||
And I configure the "(new HTML block)" block
|
||||
And I set the field "Content" to "First block content"
|
||||
And I set the field "Block title" to "First block header"
|
||||
And I press "Save changes"
|
||||
And I add the "HTML" block
|
||||
And I configure the "(new HTML block)" block
|
||||
And I set the field "Content" to "Second block content"
|
||||
And I set the field "Block title" to "Second block header"
|
||||
And I press "Save changes"
|
||||
And I log out
|
||||
And I log in as "student1"
|
||||
And I follow "Course 1"
|
||||
And I should see "First block content" in the "First block header" "block"
|
||||
And I should see "Second block content" in the "Second block header" "block"
|
||||
@@ -0,0 +1,41 @@
|
||||
@block @block_html
|
||||
Feature: Adding and configuring HTML blocks
|
||||
In order to have one or multiple HTML blocks on a page
|
||||
As admin
|
||||
I need to be able to create, configure and change HTML blocks
|
||||
|
||||
Background:
|
||||
Given I log in as "admin"
|
||||
When I click on "Turn editing on" "link" in the "Administration" "block"
|
||||
And I add the "HTML" block
|
||||
|
||||
Scenario: Other users can not see HTML block that has not been configured
|
||||
Then "(new HTML block)" "block" should exist
|
||||
And I log out
|
||||
And "(new HTML block)" "block" should not exist
|
||||
And "block_html" "block" should not exist
|
||||
|
||||
Scenario: Other users can see HTML block that has been configured even when it has no header
|
||||
And I configure the "(new HTML block)" block
|
||||
And I set the field "Content" to "Static text without a header"
|
||||
And I press "Save changes"
|
||||
Then I should not see "(new HTML block)"
|
||||
And I log out
|
||||
And I am on homepage
|
||||
And "block_html" "block" should exist
|
||||
And I should see "Static text without a header" in the "block_html" "block"
|
||||
And I should not see "(new HTML block)"
|
||||
|
||||
Scenario: Adding multiple instances of HTML block on a page
|
||||
And I configure the "block_html" block
|
||||
And I set the field "Block title" to "The HTML block header"
|
||||
And I set the field "Content" to "Static text with a header"
|
||||
And I press "Save changes"
|
||||
And I add the "HTML" block
|
||||
And I configure the "(new HTML block)" block
|
||||
And I set the field "Block title" to "The second HTML block header"
|
||||
And I set the field "Content" to "Second block contents"
|
||||
And I press "Save changes"
|
||||
And I log out
|
||||
Then I should see "Static text with a header" in the "The HTML block header" "block"
|
||||
And I should see "Second block contents" in the "The second HTML block header" "block"
|
||||
@@ -0,0 +1,190 @@
|
||||
@block @block_navigation
|
||||
Feature: Expand the courses nodes within the navigation block
|
||||
In order to navigate the site
|
||||
As an anonymous user, a guest, a student, and an admin
|
||||
I need to expand the courses node in the navigation block and check the display of courses and categories.
|
||||
|
||||
Background:
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | 1 | teacher1@local.host |
|
||||
| student1 | Student | 1 | student1@local.host |
|
||||
And the following "categories" exist:
|
||||
| name | category | idnumber | visible |
|
||||
| cat1 | 0 | cat1 | 1 |
|
||||
| cat2 | 0 | cat2 | 1 |
|
||||
| cat21 | cat2 | cat21 | 1 |
|
||||
| cat211 | cat21 | cat211 | 1 |
|
||||
| cat3 | 0 | cat3 | 0 |
|
||||
And the following "courses" exist:
|
||||
| fullname | shortname | category | visible |
|
||||
| Course 1 | c1 | cat1 | 1 |
|
||||
| Course 2 | c2 | cat2 | 1 |
|
||||
| Course 3 | c3 | cat21 | 1 |
|
||||
| Course 4 | c4 | cat211 | 1 |
|
||||
| Course 5 | c5 | cat211 | 0 |
|
||||
| Course 6 | c6 | cat211 | 0 |
|
||||
| Course 7 | c7 | cat3 | 1 |
|
||||
| Course 8 | c8 | cat3 | 0 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | c1 | teacher |
|
||||
| teacher1 | c3 | teacher |
|
||||
| teacher1 | c5 | teacher |
|
||||
| student1 | c1 | student |
|
||||
| student1 | c2 | student |
|
||||
| student1 | c4 | student |
|
||||
And I log in as "admin"
|
||||
And I follow "Course 2"
|
||||
And I turn editing mode on
|
||||
And I click on "Edit settings" "link" in the "Administration" "block"
|
||||
And I set the following fields to these values:
|
||||
| Allow guest access | Yes |
|
||||
And I press "Save changes"
|
||||
And I set the following administration settings values:
|
||||
| Show all courses | 1 |
|
||||
And I log out
|
||||
|
||||
@javascript
|
||||
Scenario: As an anonymous user I expand the courses node to see courses.
|
||||
When I should see "You are not logged in." in the ".logininfo" "css_element"
|
||||
And I should see "Home" in the "Navigation" "block"
|
||||
And I should see "Courses" in the "Navigation" "block"
|
||||
And I expand "Courses" node
|
||||
And I should see "cat1" in the "Navigation" "block"
|
||||
And I should see "cat2" in the "Navigation" "block"
|
||||
And I should not see "cat3" in the "Navigation" "block"
|
||||
And I expand "cat1" node
|
||||
And I expand "cat2" node
|
||||
And I should see "cat21" in the "Navigation" "block"
|
||||
And I expand "cat21" node
|
||||
And I should see "cat211" in the "Navigation" "block"
|
||||
And I expand "cat211" node
|
||||
Then I should see "c1" in the "Navigation" "block"
|
||||
And I should see "c2" in the "Navigation" "block"
|
||||
And I should see "c3" in the "Navigation" "block"
|
||||
And I should see "c4" in the "Navigation" "block"
|
||||
And I should not see "c5" in the "Navigation" "block"
|
||||
And I should not see "c6" in the "Navigation" "block"
|
||||
And navigation node "c1" should not be expandable
|
||||
And navigation node "c2" should not be expandable
|
||||
And navigation node "c3" should not be expandable
|
||||
And navigation node "c4" should not be expandable
|
||||
|
||||
@javascript
|
||||
Scenario: As the admin user I expand the courses and category nodes to see courses.
|
||||
When I log in as "admin"
|
||||
And I should see "Home" in the "Navigation" "block"
|
||||
And I should see "Courses" in the "Navigation" "block"
|
||||
And I expand "Courses" node
|
||||
And I should see "cat1" in the "Navigation" "block"
|
||||
And I should see "cat2" in the "Navigation" "block"
|
||||
And I should see "cat3" in the "Navigation" "block"
|
||||
And I expand "cat1" node
|
||||
And I expand "cat2" node
|
||||
And I expand "cat3" node
|
||||
And I should see "cat21" in the "Navigation" "block"
|
||||
And I expand "cat21" node
|
||||
And I should see "cat211" in the "Navigation" "block"
|
||||
And I expand "cat211" node
|
||||
Then I should see "c1" in the "Navigation" "block"
|
||||
And I should see "c2" in the "Navigation" "block"
|
||||
And I should see "c3" in the "Navigation" "block"
|
||||
And I should see "c4" in the "Navigation" "block"
|
||||
And I should see "c5" in the "Navigation" "block"
|
||||
And I should see "c6" in the "Navigation" "block"
|
||||
And I should see "c7" in the "Navigation" "block"
|
||||
And I should see "c8" in the "Navigation" "block"
|
||||
And navigation node "c1" should be expandable
|
||||
And navigation node "c2" should be expandable
|
||||
And navigation node "c3" should be expandable
|
||||
And navigation node "c4" should be expandable
|
||||
And navigation node "c5" should be expandable
|
||||
And navigation node "c6" should be expandable
|
||||
And navigation node "c7" should be expandable
|
||||
And navigation node "c8" should be expandable
|
||||
|
||||
@javascript
|
||||
Scenario: As teacher1 I expand the courses and category nodes to see courses.
|
||||
When I log in as "teacher1"
|
||||
And I should see "Home" in the "Navigation" "block"
|
||||
And I should see "Courses" in the "Navigation" "block"
|
||||
And I expand "Courses" node
|
||||
And I should see "cat1" in the "Navigation" "block"
|
||||
And I should see "cat2" in the "Navigation" "block"
|
||||
And I should not see "cat3" in the "Navigation" "block"
|
||||
And I expand "cat1" node
|
||||
And I expand "cat2" node
|
||||
And I should see "cat21" in the "Navigation" "block"
|
||||
And I expand "cat21" node
|
||||
And I should see "cat211" in the "Navigation" "block"
|
||||
And I expand "cat211" node
|
||||
Then I should see "c1" in the "Navigation" "block"
|
||||
And I should see "c2" in the "Navigation" "block"
|
||||
And I should see "c3" in the "Navigation" "block"
|
||||
And I should see "c4" in the "Navigation" "block"
|
||||
And I should see "c5" in the "Navigation" "block"
|
||||
And I should not see "c6" in the "Navigation" "block"
|
||||
And I should not see "c7" in the "Navigation" "block"
|
||||
And I should not see "c8" in the "Navigation" "block"
|
||||
And navigation node "c1" should be expandable
|
||||
And navigation node "c2" should be expandable
|
||||
And navigation node "c3" should be expandable
|
||||
And navigation node "c4" should not be expandable
|
||||
And navigation node "c5" should be expandable
|
||||
|
||||
@javascript
|
||||
Scenario: As student1 I expand the courses and category nodes to see courses.
|
||||
When I log in as "student1"
|
||||
And I should see "Home" in the "Navigation" "block"
|
||||
And I should see "Courses" in the "Navigation" "block"
|
||||
And I expand "Courses" node
|
||||
And I should see "cat1" in the "Navigation" "block"
|
||||
And I should see "cat2" in the "Navigation" "block"
|
||||
And I should not see "cat3" in the "Navigation" "block"
|
||||
And I expand "cat1" node
|
||||
And I expand "cat2" node
|
||||
And I should see "cat21" in the "Navigation" "block"
|
||||
And I expand "cat21" node
|
||||
And I should see "cat211" in the "Navigation" "block"
|
||||
And I expand "cat211" node
|
||||
Then I should see "c1" in the "Navigation" "block"
|
||||
And I should see "c2" in the "Navigation" "block"
|
||||
And I should see "c3" in the "Navigation" "block"
|
||||
And I should see "c4" in the "Navigation" "block"
|
||||
And I should not see "c5" in the "Navigation" "block"
|
||||
And I should not see "c6" in the "Navigation" "block"
|
||||
And I should not see "c7" in the "Navigation" "block"
|
||||
And I should not see "c8" in the "Navigation" "block"
|
||||
And navigation node "c1" should be expandable
|
||||
And navigation node "c2" should be expandable
|
||||
And navigation node "c3" should not be expandable
|
||||
And navigation node "c4" should be expandable
|
||||
|
||||
@javascript
|
||||
Scenario: As guest I expand the courses and category nodes to see courses.
|
||||
When I log in as "guest"
|
||||
And I should see "Home" in the "Navigation" "block"
|
||||
And I should see "Courses" in the "Navigation" "block"
|
||||
And I expand "Courses" node
|
||||
And I should see "cat1" in the "Navigation" "block"
|
||||
And I should see "cat2" in the "Navigation" "block"
|
||||
And I should not see "cat3" in the "Navigation" "block"
|
||||
And I expand "cat1" node
|
||||
And I expand "cat2" node
|
||||
And I should see "cat21" in the "Navigation" "block"
|
||||
And I expand "cat21" node
|
||||
And I should see "cat211" in the "Navigation" "block"
|
||||
And I expand "cat211" node
|
||||
Then I should see "c1" in the "Navigation" "block"
|
||||
And I should see "c2" in the "Navigation" "block"
|
||||
And I should see "c3" in the "Navigation" "block"
|
||||
And I should see "c4" in the "Navigation" "block"
|
||||
And I should not see "c5" in the "Navigation" "block"
|
||||
And I should not see "c6" in the "Navigation" "block"
|
||||
And I should not see "c7" in the "Navigation" "block"
|
||||
And I should not see "c8" in the "Navigation" "block"
|
||||
And navigation node "c1" should not be expandable
|
||||
And navigation node "c2" should be expandable
|
||||
And navigation node "c3" should not be expandable
|
||||
And navigation node "c4" should not be expandable
|
||||
@@ -66,3 +66,34 @@ Feature: View my courses in navigation block
|
||||
And I expand "cat33" node
|
||||
And I should see "c331" in the "Navigation" "block"
|
||||
And I should not see "c332" in the "Navigation" "block"
|
||||
|
||||
@javascript
|
||||
Scenario: I can expand categories and courses as guest
|
||||
Given I set the following administration settings values:
|
||||
| Show my course categories | 1 |
|
||||
| Show all courses | 1 |
|
||||
And I log out
|
||||
And I expand "Courses" node
|
||||
And I should see "cat1" in the "Navigation" "block"
|
||||
And I should see "cat2" in the "Navigation" "block"
|
||||
And I should see "cat3" in the "Navigation" "block"
|
||||
And I should not see "cat31" in the "Navigation" "block"
|
||||
And I should not see "cat32" in the "Navigation" "block"
|
||||
And I should not see "cat331" in the "Navigation" "block"
|
||||
And I should not see "c1" in the "Navigation" "block"
|
||||
And I should not see "c2" in the "Navigation" "block"
|
||||
And I should not see "c31" in the "Navigation" "block"
|
||||
And I should not see "c32" in the "Navigation" "block"
|
||||
When I expand "cat3" node
|
||||
And I expand "cat31" node
|
||||
And I expand "cat1" node
|
||||
Then I should see "cat1" in the "Navigation" "block"
|
||||
And I should see "cat2" in the "Navigation" "block"
|
||||
And I should see "cat3" in the "Navigation" "block"
|
||||
And I should see "cat31" in the "Navigation" "block"
|
||||
And I should see "cat32" in the "Navigation" "block"
|
||||
And I should not see "cat331" in the "Navigation" "block"
|
||||
And I should see "c1" in the "Navigation" "block"
|
||||
And I should not see "c2" in the "Navigation" "block"
|
||||
And I should see "c31" in the "Navigation" "block"
|
||||
And I should not see "c32" in the "Navigation" "block"
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
@block @block_news_items
|
||||
Feature: Latest news block displays the course latest news
|
||||
In order to be aware of the course news
|
||||
As a user
|
||||
I need to see the latest news in the main course page
|
||||
|
||||
@javascript
|
||||
Scenario: Latest course news are displayed and can be configured
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | 1 | teacher1@asd.com |
|
||||
And I log in as "admin"
|
||||
And I create a course with:
|
||||
| Course full name | Course 1 |
|
||||
| Course short name | C1 |
|
||||
| News items to show | 5 |
|
||||
And I enrol "Teacher 1" user as "Teacher"
|
||||
And I log out
|
||||
And I log in as "teacher1"
|
||||
And I follow "Course 1"
|
||||
When I add a new topic to "News forum" forum with:
|
||||
| Subject | Discussion One |
|
||||
| Message | Not important |
|
||||
And I add a new topic to "News forum" forum with:
|
||||
| Subject | Discussion Two |
|
||||
| Message | Not important |
|
||||
And I add a new topic to "News forum" forum with:
|
||||
| Subject | Discussion Three |
|
||||
| Message | Not important |
|
||||
And I follow "Course 1"
|
||||
Then I should see "Discussion One" in the "Latest news" "block"
|
||||
And I should see "Discussion Two" in the "Latest news" "block"
|
||||
And I should see "Discussion Three" in the "Latest news" "block"
|
||||
And I follow "Edit settings"
|
||||
And I set the following fields to these values:
|
||||
| News items to show | 2 |
|
||||
And I press "Save changes"
|
||||
And I should not see "Discussion One" in the "Latest news" "block"
|
||||
And I should see "Discussion Two" in the "Latest news" "block"
|
||||
And I should see "Discussion Three" in the "Latest news" "block"
|
||||
And I follow "Edit settings"
|
||||
And I set the following fields to these values:
|
||||
| News items to show | 0 |
|
||||
And I press "Save changes"
|
||||
And "Latest news" "block" should not exist
|
||||
@@ -83,13 +83,13 @@ class behat_blocks extends behat_base {
|
||||
public function i_open_the_blocks_action_menu($blockname) {
|
||||
|
||||
if (!$this->running_javascript()) {
|
||||
throw new DriverException('Blocks action menu not available when Javascript is disabled');
|
||||
// Action menu does not need to be open if Javascript is off.
|
||||
return;
|
||||
}
|
||||
|
||||
// If it is already opened we do nothing.
|
||||
$blocknode = $this->get_block_node($blockname);
|
||||
$classes = array_flip(explode(' ', $blocknode->getAttribute('class')));
|
||||
if (!empty($classes['action-menu-shown'])) {
|
||||
$blocknode = $this->get_text_selector_node('block', $blockname);
|
||||
if ($blocknode->hasClass('action-menu-shown')) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -97,18 +97,18 @@ class behat_blocks extends behat_base {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the DOM node of the block from <div>.
|
||||
* Clicks on Configure block for specified block. Page must be in editing mode.
|
||||
*
|
||||
* @throws ElementNotFoundException Thrown by behat_base::find
|
||||
* @param string $blockname The block name
|
||||
* @return NodeElement
|
||||
* Argument block_name may be either the name of the block or CSS class of the block.
|
||||
*
|
||||
* @Given /^I configure the "(?P<block_name_string>(?:[^"]|\\")*)" block$/
|
||||
* @param string $blockname
|
||||
*/
|
||||
protected function get_block_node($blockname) {
|
||||
|
||||
$blockname = $this->getSession()->getSelectorsHandler()->xpathLiteral($blockname);
|
||||
$xpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' block ')][contains(., $blockname)]";
|
||||
|
||||
return $this->find('xpath', $xpath);
|
||||
public function i_configure_the_block($blockname) {
|
||||
// Note that since $blockname may be either block name or CSS class, we can not use the exact label of "Configure" link.
|
||||
return array(
|
||||
new Given('I open the "'.$this->escape($blockname).'" blocks action menu'),
|
||||
new Given('I click on "Configure" "link" in the "'.$this->escape($blockname).'" "block"')
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ Feature: Add and configure blocks throughout the site
|
||||
As a manager
|
||||
I need to set and configure blocks throughout the site
|
||||
|
||||
@javascript
|
||||
Scenario: Add and configure a block throughtout the site
|
||||
Given the following "courses" exists:
|
||||
| fullname | shortname | category |
|
||||
@@ -18,8 +17,7 @@ Feature: Add and configure blocks throughout the site
|
||||
And I log in as "manager1"
|
||||
And I follow "Turn editing on"
|
||||
And I add the "Comments" block
|
||||
And I open the "Comments" blocks action menu
|
||||
And I follow "Configure Comments block"
|
||||
And I configure the "Comments" block
|
||||
And I fill the moodle form with:
|
||||
| Page contexts | Display throughout the entire site |
|
||||
And I press "Save changes"
|
||||
@@ -27,8 +25,7 @@ Feature: Add and configure blocks throughout the site
|
||||
Then I should see "Comments" in the "Comments" "block"
|
||||
And I should see "Save comment" in the "Comments" "block"
|
||||
And I am on homepage
|
||||
And I open the "Comments" blocks action menu
|
||||
And I follow "Configure Comments block"
|
||||
And I configure the "Comments" block
|
||||
And I fill the moodle form with:
|
||||
| Default weight | -10 (first) |
|
||||
And I press "Save changes"
|
||||
|
||||
@@ -39,19 +39,16 @@ Feature: Block appearances
|
||||
And I follow "Course 1"
|
||||
And I follow "Turn editing on"
|
||||
And I add the "Comments" block
|
||||
And I open the "Comments" blocks action menu
|
||||
And I follow "Configure Comments block"
|
||||
And I configure the "Comments" block
|
||||
And I fill the moodle form with:
|
||||
| Display on page types | Any page |
|
||||
And I press "Save changes"
|
||||
|
||||
@javascript
|
||||
Scenario: Block settings can be modified so that a block apprears on any page
|
||||
When I follow "Test survey name"
|
||||
Then I should see "Comments" in the "Comments" "block"
|
||||
And I follow "Course 1"
|
||||
And I open the "Comments" blocks action menu
|
||||
And I follow "Configure Comments block"
|
||||
And I configure the "Comments" block
|
||||
And I fill the moodle form with:
|
||||
| Display on page types | Any course page |
|
||||
And I press "Save changes"
|
||||
@@ -59,11 +56,9 @@ Feature: Block appearances
|
||||
And I follow "Test survey name"
|
||||
And I should not see "Comments"
|
||||
|
||||
@javascript
|
||||
Scenario: Block settings can be modified so that a block can be hidden or moved
|
||||
When I follow "Test book name"
|
||||
And I open the "Comments" blocks action menu
|
||||
And I follow "Configure Comments block"
|
||||
And I configure the "Comments" block
|
||||
And I fill the moodle form with:
|
||||
| Visible | No |
|
||||
And I press "Save changes"
|
||||
@@ -72,8 +67,7 @@ Feature: Block appearances
|
||||
Then I should not see "Comments"
|
||||
And I expand "Course administration" node
|
||||
And I follow "Turn editing on"
|
||||
And I open the "Comments" blocks action menu
|
||||
And I follow "Configure Comments block"
|
||||
And I configure the "Comments" block
|
||||
And I fill the moodle form with:
|
||||
| Visible | Yes |
|
||||
| Region | Right |
|
||||
|
||||
@@ -15,7 +15,6 @@ Feature: Allowed blocks controls
|
||||
| user | course | role |
|
||||
| teacher1 | C1 | editingteacher |
|
||||
|
||||
@javascript
|
||||
Scenario: Blocks can be added with the default permissions
|
||||
Given I log in as "teacher1"
|
||||
And I follow "Course 1"
|
||||
|
||||
@@ -4,7 +4,6 @@ Feature: The context of a block can always be returned to it's original state.
|
||||
As an admin
|
||||
I need to be able to return the block to original state
|
||||
|
||||
@javascript
|
||||
Scenario: Add and configure a block to display on every page and revert back
|
||||
Given the following "courses" exists:
|
||||
| fullname | shortname | category |
|
||||
@@ -15,8 +14,7 @@ Feature: The context of a block can always be returned to it's original state.
|
||||
And I add the "Tags" block
|
||||
Then I should see "Tags" in the "Tags" "block"
|
||||
And I click on "Participants" "link" in the "//li[p/span[contains(normalize-space(string(.)), 'Current course')]]" "xpath_element"
|
||||
And I open the "Tags" blocks action menu
|
||||
And I follow "Configure Tags block"
|
||||
And I configure the "Tags" block
|
||||
And I fill the moodle form with:
|
||||
| Display on page types | Any page |
|
||||
And I press "Save changes"
|
||||
@@ -25,8 +23,7 @@ Feature: The context of a block can always be returned to it's original state.
|
||||
| Assignment name | Assignment1 |
|
||||
| Description | Description |
|
||||
And I follow "Assignment1"
|
||||
And I open the "Tags" blocks action menu
|
||||
And I follow "Configure Tags block"
|
||||
And I configure the "Tags" block
|
||||
And I fill the moodle form with:
|
||||
| Display on page types | Any assignment module page |
|
||||
And I press "Save changes"
|
||||
@@ -41,8 +38,7 @@ Feature: The context of a block can always be returned to it's original state.
|
||||
| Description | Description |
|
||||
And I follow "Assignment2"
|
||||
And I should see "Tags" in the "Tags" "block"
|
||||
And I open the "Tags" blocks action menu
|
||||
And I follow "Configure Tags block"
|
||||
And I configure the "Tags" block
|
||||
And I fill the moodle form with:
|
||||
| Display on page types | Any page |
|
||||
And I press "Save changes"
|
||||
|
||||
Vendored
+4
@@ -246,3 +246,7 @@ The following snippet illustates how to configure the three core cache stores th
|
||||
define('TEST_CACHESTORE_MEMCACHE_TESTSERVERS', '127.0.0.1:11211');
|
||||
define('TEST_CACHESTORE_MEMCACHED_TESTSERVERS', '127.0.0.1:11211');
|
||||
define('TEST_CACHESTORE_MONGODB_TESTSERVER', 'mongodb://localhost:27017');
|
||||
|
||||
Please be aware that if you are using Memcache or Memcached it is recommended to use dedicated Memcached servers.
|
||||
When caches get purged the memcached servers you have configured get purged, any data stored within them whether it belongs to Moodle or not will be removed.
|
||||
If you are using Memcached for sessions as well as caching/testing and caches get purged your sessions will be removed prematurely and users will be need to start again.
|
||||
|
||||
Vendored
+2
-1
@@ -718,7 +718,8 @@ class cache_helper {
|
||||
return $stores;
|
||||
} else {
|
||||
$stores = self::get_cache_stores($definition);
|
||||
if (count($stores) === 0) {
|
||||
// If mappingsonly is set, having 0 stores is ok.
|
||||
if ((count($stores) === 0) && (!$definition->is_for_mappings_only())) {
|
||||
// No suitable stores we found for the definition. We need to come up with a sensible default.
|
||||
// If this has happened we can be sure that the user has mapped custom stores to either the
|
||||
// mode of the definition. The first alternative to try is the system default for the mode.
|
||||
|
||||
Vendored
+1
-1
@@ -204,7 +204,7 @@ class cachestore_mongodb extends cache_store implements cache_is_configurable {
|
||||
throw new coding_exception('This mongodb instance has already been initialised.');
|
||||
}
|
||||
$this->database = $this->connection->selectDB($this->databasename);
|
||||
$this->definitionhash = $definition->generate_definition_hash();
|
||||
$this->definitionhash = 'm'.$definition->generate_definition_hash();
|
||||
$this->collection = $this->database->selectCollection($this->definitionhash);
|
||||
|
||||
$options = array('name' => 'idx_key');
|
||||
|
||||
+22
@@ -59,4 +59,26 @@ class cachestore_mongodb_test extends cachestore_tests {
|
||||
protected function get_class_name() {
|
||||
return 'cachestore_mongodb';
|
||||
}
|
||||
|
||||
/**
|
||||
* A small additional test to make sure definitions that hash a hash starting with a number work OK
|
||||
*/
|
||||
public function test_collection_name() {
|
||||
// This generates a definition that has a hash starting with a number. MDL-46208.
|
||||
$definition = cache_definition::load_adhoc(cache_store::MODE_APPLICATION, 'cachestore_mongodb', 'abc');
|
||||
$instance = cachestore_mongodb::initialise_test_instance($definition);
|
||||
|
||||
if (!$instance) {
|
||||
$this->markTestSkipped();
|
||||
}
|
||||
|
||||
$this->assertTrue($instance->set(1, 'alpha'));
|
||||
$this->assertTrue($instance->set(2, 'beta'));
|
||||
$this->assertEquals('alpha', $instance->get(1));
|
||||
$this->assertEquals('beta', $instance->get(2));
|
||||
$this->assertEquals(array(
|
||||
1 => 'alpha',
|
||||
2 => 'beta'
|
||||
), $instance->get_many(array(1, 2)));
|
||||
}
|
||||
}
|
||||
Vendored
+27
@@ -470,6 +470,33 @@ class core_cache_testcase extends advanced_testcase {
|
||||
$this->assertEquals('Test has no value really.', $cache->get('Test'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the mappingsonly setting.
|
||||
*/
|
||||
public function test_definition_mappings_only() {
|
||||
$instance = cache_config_phpunittest::instance(true);
|
||||
$instance->phpunit_add_definition('phpunit/mappingsonly', array(
|
||||
'mode' => cache_store::MODE_APPLICATION,
|
||||
'component' => 'phpunit',
|
||||
'area' => 'mappingsonly',
|
||||
'mappingsonly' => true
|
||||
));
|
||||
$instance->phpunit_add_definition('phpunit/nonmappingsonly', array(
|
||||
'mode' => cache_store::MODE_APPLICATION,
|
||||
'component' => 'phpunit',
|
||||
'area' => 'nonmappingsonly',
|
||||
'mappingsonly' => false
|
||||
));
|
||||
|
||||
$cacheonly = cache::make('phpunit', 'mappingsonly');
|
||||
$this->assertInstanceOf('cache_application', $cacheonly);
|
||||
$this->assertEquals('cachestore_dummy', $cacheonly->phpunit_get_store_class());
|
||||
|
||||
$cachenon = cache::make('phpunit', 'nonmappingsonly');
|
||||
$this->assertInstanceOf('cache_application', $cachenon);
|
||||
$this->assertEquals('cachestore_file', $cachenon->phpunit_get_store_class());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test a very basic definition.
|
||||
*/
|
||||
|
||||
+16
-7
@@ -114,14 +114,23 @@ class event_form extends moodleform {
|
||||
$mform->addRule('timestart', get_string('required'), 'required');
|
||||
|
||||
$mform->addElement('header', 'durationdetails', get_string('eventduration', 'calendar'));
|
||||
$mform->addElement('radio', 'duration', null, get_string('durationnone', 'calendar'), 0);
|
||||
|
||||
$mform->addElement('radio', 'duration', null, get_string('durationuntil', 'calendar'), 1);
|
||||
$mform->addElement('date_time_selector', 'timedurationuntil', ' ');
|
||||
$mform->disabledIf('timedurationuntil','duration','noteq', 1);
|
||||
$group = array();
|
||||
$group[] =& $mform->createElement('radio', 'duration', null, get_string('durationnone', 'calendar'), 0);
|
||||
$group[] =& $mform->createElement('radio', 'duration', null, get_string('durationuntil', 'calendar'), 1);
|
||||
$group[] =& $mform->createElement('date_time_selector', 'timedurationuntil', '');
|
||||
$group[] =& $mform->createElement('radio', 'duration', null, get_string('durationminutes', 'calendar'), 2);
|
||||
$group[] =& $mform->createElement('text', 'timedurationminutes', null);
|
||||
|
||||
$mform->addGroup($group, 'durationgroup', '', '<br />', false);
|
||||
|
||||
$mform->disabledIf('timedurationuntil', 'duration', 'noteq', 1);
|
||||
$mform->disabledIf('timedurationuntil[day]', 'duration', 'noteq', 1);
|
||||
$mform->disabledIf('timedurationuntil[month]', 'duration', 'noteq', 1);
|
||||
$mform->disabledIf('timedurationuntil[year]', 'duration', 'noteq', 1);
|
||||
$mform->disabledIf('timedurationuntil[hour]', 'duration', 'noteq', 1);
|
||||
$mform->disabledIf('timedurationuntil[minute]', 'duration', 'noteq', 1);
|
||||
|
||||
$mform->addElement('radio', 'duration', null, get_string('durationminutes', 'calendar'), 2);
|
||||
$mform->addElement('text', 'timedurationminutes', null);
|
||||
$mform->setType('timedurationminutes', PARAM_INT);
|
||||
$mform->disabledIf('timedurationminutes','duration','noteq', 2);
|
||||
|
||||
@@ -130,7 +139,7 @@ class event_form extends moodleform {
|
||||
if ($newevent) {
|
||||
|
||||
$mform->addElement('header', 'repeatevents', get_string('repeatedevents', 'calendar'));
|
||||
$mform->addElement('checkbox', 'repeat', get_string('repeatevent', 'calendar'), null, 'repeat');
|
||||
$mform->addElement('checkbox', 'repeat', get_string('repeatevent', 'calendar'), null);
|
||||
$mform->addElement('text', 'repeats', get_string('repeatweeksl', 'calendar'), 'maxlength="10" size="10"');
|
||||
$mform->setType('repeats', PARAM_INT);
|
||||
$mform->setDefault('repeats', 1);
|
||||
|
||||
+1
-1
@@ -1044,7 +1044,7 @@ function calendar_filter_controls(moodle_url $returnurl) {
|
||||
|
||||
$groupevents = true;
|
||||
$id = optional_param( 'id',0,PARAM_INT );
|
||||
$seturl = new moodle_url('/calendar/set.php', array('return' => base64_encode($returnurl->out(false)), 'sesskey'=>sesskey()));
|
||||
$seturl = new moodle_url('/calendar/set.php', array('return' => base64_encode($returnurl->out_as_local_url(false)), 'sesskey'=>sesskey()));
|
||||
$content = html_writer::start_tag('ul');
|
||||
|
||||
$seturl->param('var', 'showglobal');
|
||||
|
||||
@@ -259,8 +259,7 @@ class core_calendar_renderer extends plugin_renderer_base {
|
||||
if (calendar_user_can_add_event($calendar->course)) {
|
||||
$output .= $this->add_event_button($calendar->course->id, 0, 0, 0, $calendar->time);
|
||||
}
|
||||
//$output .= html_writer::tag('label', get_string('dayview', 'calendar'), array('for'=>'cal_course_flt_jump'));
|
||||
$output .= $this->course_filter_selector($returnurl, get_string('dayview', 'calendar'));
|
||||
$output .= $this->course_filter_selector($returnurl, get_string('dayviewfor', 'calendar'));
|
||||
$output .= html_writer::end_tag('div');
|
||||
// Controls
|
||||
$output .= html_writer::tag('div', calendar_top_controls('day', array('id' => $calendar->courseid, 'time' => $calendar->time)), array('class'=>'controls'));
|
||||
@@ -468,7 +467,7 @@ class core_calendar_renderer extends plugin_renderer_base {
|
||||
if (calendar_user_can_add_event($calendar->course)) {
|
||||
$output .= $this->add_event_button($calendar->course->id, 0, 0, 0, $calendar->time);
|
||||
}
|
||||
$output .= get_string('detailedmonthview', 'calendar').': '.$this->course_filter_selector($returnurl);
|
||||
$output .= $this->course_filter_selector($returnurl, get_string('detailedmonthviewfor', 'calendar'));
|
||||
$output .= html_writer::end_tag('div', array('class'=>'header'));
|
||||
// Controls
|
||||
$output .= html_writer::tag('div', calendar_top_controls('month', array('id' => $calendar->courseid, 'time' => $calendar->time)), array('class' => 'controls'));
|
||||
@@ -691,8 +690,7 @@ class core_calendar_renderer extends plugin_renderer_base {
|
||||
if (calendar_user_can_add_event($calendar->course)) {
|
||||
$output .= $this->add_event_button($calendar->course->id);
|
||||
}
|
||||
$output .= html_writer::tag('label', get_string('upcomingevents', 'calendar'), array('for'=>'cal_course_flt_jump'));
|
||||
$output .= $this->course_filter_selector($returnurl);
|
||||
$output .= $this->course_filter_selector($returnurl, get_string('upcomingeventsfor', 'calendar'));
|
||||
$output .= html_writer::end_tag('div');
|
||||
|
||||
if ($events) {
|
||||
|
||||
+2
-2
@@ -44,14 +44,14 @@ require_once($CFG->dirroot.'/calendar/lib.php');
|
||||
require_sesskey();
|
||||
|
||||
$var = required_param('var', PARAM_ALPHA);
|
||||
$return = clean_param(base64_decode(required_param('return', PARAM_RAW)), PARAM_URL);
|
||||
$return = clean_param(base64_decode(required_param('return', PARAM_RAW)), PARAM_LOCALURL);
|
||||
$courseid = optional_param('id', -1, PARAM_INT);
|
||||
if ($courseid != -1) {
|
||||
$return = new moodle_url($return, array('course' => $courseid));
|
||||
} else {
|
||||
$return = new moodle_url($return);
|
||||
}
|
||||
$url = new moodle_url('/calendar/set.php', array('return'=>base64_encode($return->out(false)), 'course' => $courseid, 'var'=>$var, 'sesskey'=>sesskey()));
|
||||
$url = new moodle_url('/calendar/set.php', array('return'=>base64_encode($return->out_as_local_url(false)), 'course' => $courseid, 'var'=>$var, 'sesskey'=>sesskey()));
|
||||
$PAGE->set_url($url);
|
||||
$PAGE->set_context(context_system::instance());
|
||||
|
||||
|
||||
+2
@@ -62,6 +62,7 @@ Y.extend(EVENT, Y.Base, {
|
||||
}
|
||||
},
|
||||
startShow : function() {
|
||||
this.cancelHide();
|
||||
if (this.get(SHOWTIMEOUT) !== null) {
|
||||
this.cancelShow();
|
||||
}
|
||||
@@ -82,6 +83,7 @@ Y.extend(EVENT, Y.Base, {
|
||||
this.fire('showevent');
|
||||
},
|
||||
startHide : function() {
|
||||
this.cancelShow();
|
||||
if (this.get(HIDETIMEOUT) !== null) {
|
||||
this.cancelHide();
|
||||
}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
YUI.add("moodle-calendar-eventmanager",function(e,t){var n="Calendar event",r="eventId",i="node",s="title",o="content",u="delay",a="showTimeout",f="hideTimeout",l=function(){l.superclass.constructor.apply(this,arguments)},c;e.extend(l,e.Base,{initpanelcalled:!1,initializer:function(){this.get(r);var e=this.get(i),t;return e?(t=e.ancestor("td"),this.publish("showevent"),this.publish("hideevent"),t.on("mouseenter",this.startShow,this),t.on("mouseleave",this.startHide,this),t.on("focus",this.startShow,this),t.on("blur",this.startHide,this),!0):!1},initPanel:function(){if(!this.initpanelcalled){this.initpanelcalled=!0;var t=this.get(i),n=t.ancestor("td"),u=n.ancestor("div"),a;a=new e.Overlay({constrain:u,align:{node:n,points:[e.WidgetPositionAlign.TL,e.WidgetPositionAlign.BC]},headerContent:e.Node.create('<h2 class="eventtitle">'+this.get(s)+"</h2>"),bodyContent:e.Node.create('<div class="eventcontent">'+this.get(o)+"</div>"),visible:!1,id:this.get(r)+"_panel",width:Math.floor(u.get("offsetWidth")*.9)+"px"}),a.render(n),t.setAttribute("aria-controls",a.get("id")),a.get("boundingBox").addClass("calendar-event-panel"),a.get("boundingBox").setAttribute("aria-live","off"),this.on("showevent",a.show,a),this.on("showevent",this.setAriashow,a),this.on("hideevent",this.setAriahide,a),this.on("hideevent",a.hide,a)}},startShow:function(){this.get(a)!==null&&this.cancelShow();var e=this;this.set(a,setTimeout(function(){e.show()},this.get(u)))},cancelShow:function(){clearTimeout(this.get(a))},setAriashow:function(){this.get("boundingBox").setAttribute("aria-live","assertive")},setAriahide:function(){this.get("boundingBox").setAttribute("aria-live","off")},show:function(){this.initPanel(),this.fire("showevent")},startHide:function(){this.get(f)!==null&&this.cancelHide();var e=this;this.set(f,setTimeout(function(){e.hide()},this.get(u)))},hide:function(){this.fire("hideevent")},cancelHide:function(){clearTimeout(this.get(f))}},{NAME:n,ATTRS:{eventId:{setter:function(t){return this.set(i,e.one("#"+t)),t},validator:e.Lang.isString},node:{setter:function(t){return typeof t=="string"&&(t=e.one("#"+t)),t}},title:{validator:e.Lang.isString},content:{validator:e.Lang.isString},delay:{value:300,validator:e.Lang.isNumber},showTimeout:{value:null},hideTimeout:{value:null}}}),e.augment(l,e.EventTarget),c={add_event:function(e){new l(e)}},M.core_calendar=M.core_calendar||{},e.mix(M.core_calendar,c)},"@VERSION@",{requires:["base","node","event-mouseenter","overlay","moodle-calendar-eventmanager-skin"]});
|
||||
YUI.add("moodle-calendar-eventmanager",function(e,t){var n="Calendar event",r="eventId",i="node",s="title",o="content",u="delay",a="showTimeout",f="hideTimeout",l=function(){l.superclass.constructor.apply(this,arguments)},c;e.extend(l,e.Base,{initpanelcalled:!1,initializer:function(){this.get(r);var e=this.get(i),t;return e?(t=e.ancestor("td"),this.publish("showevent"),this.publish("hideevent"),t.on("mouseenter",this.startShow,this),t.on("mouseleave",this.startHide,this),t.on("focus",this.startShow,this),t.on("blur",this.startHide,this),!0):!1},initPanel:function(){if(!this.initpanelcalled){this.initpanelcalled=!0;var t=this.get(i),n=t.ancestor("td"),u=n.ancestor("div"),a;a=new e.Overlay({constrain:u,align:{node:n,points:[e.WidgetPositionAlign.TL,e.WidgetPositionAlign.BC]},headerContent:e.Node.create('<h2 class="eventtitle">'+this.get(s)+"</h2>"),bodyContent:e.Node.create('<div class="eventcontent">'+this.get(o)+"</div>"),visible:!1,id:this.get(r)+"_panel",width:Math.floor(u.get("offsetWidth")*.9)+"px"}),a.render(n),t.setAttribute("aria-controls",a.get("id")),a.get("boundingBox").addClass("calendar-event-panel"),a.get("boundingBox").setAttribute("aria-live","off"),this.on("showevent",a.show,a),this.on("showevent",this.setAriashow,a),this.on("hideevent",this.setAriahide,a),this.on("hideevent",a.hide,a)}},startShow:function(){this.cancelHide(),this.get(a)!==null&&this.cancelShow();var e=this;this.set(a,setTimeout(function(){e.show()},this.get(u)))},cancelShow:function(){clearTimeout(this.get(a))},setAriashow:function(){this.get("boundingBox").setAttribute("aria-live","assertive")},setAriahide:function(){this.get("boundingBox").setAttribute("aria-live","off")},show:function(){this.initPanel(),this.fire("showevent")},startHide:function(){this.cancelShow(),this.get(f)!==null&&this.cancelHide();var e=this;this.set(f,setTimeout(function(){e.hide()},this.get(u)))},hide:function(){this.fire("hideevent")},cancelHide:function(){clearTimeout(this.get(f))}},{NAME:n,ATTRS:{eventId:{setter:function(t){return this.set(i,e.one("#"+t)),t},validator:e.Lang.isString},node:{setter:function(t){return typeof t=="string"&&(t=e.one("#"+t)),t}},title:{validator:e.Lang.isString},content:{validator:e.Lang.isString},delay:{value:300,validator:e.Lang.isNumber},showTimeout:{value:null},hideTimeout:{value:null}}}),e.augment(l,e.EventTarget),c={add_event:function(e){new l(e)}},M.core_calendar=M.core_calendar||{},e.mix(M.core_calendar,c)},"@VERSION@",{requires:["base","node","event-mouseenter","overlay","moodle-calendar-eventmanager-skin"]});
|
||||
|
||||
+2
@@ -62,6 +62,7 @@ Y.extend(EVENT, Y.Base, {
|
||||
}
|
||||
},
|
||||
startShow : function() {
|
||||
this.cancelHide();
|
||||
if (this.get(SHOWTIMEOUT) !== null) {
|
||||
this.cancelShow();
|
||||
}
|
||||
@@ -82,6 +83,7 @@ Y.extend(EVENT, Y.Base, {
|
||||
this.fire('showevent');
|
||||
},
|
||||
startHide : function() {
|
||||
this.cancelShow();
|
||||
if (this.get(HIDETIMEOUT) !== null) {
|
||||
this.cancelHide();
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ Y.extend(EVENT, Y.Base, {
|
||||
}
|
||||
},
|
||||
startShow : function() {
|
||||
this.cancelHide();
|
||||
if (this.get(SHOWTIMEOUT) !== null) {
|
||||
this.cancelShow();
|
||||
}
|
||||
@@ -80,6 +81,7 @@ Y.extend(EVENT, Y.Base, {
|
||||
this.fire('showevent');
|
||||
},
|
||||
startHide : function() {
|
||||
this.cancelShow();
|
||||
if (this.get(HIDETIMEOUT) !== null) {
|
||||
this.cancelHide();
|
||||
}
|
||||
|
||||
+2
-2
@@ -526,9 +526,9 @@ class comment {
|
||||
$c->content = $u->ccontent;
|
||||
$c->format = $u->cformat;
|
||||
$c->timecreated = $u->ctimecreated;
|
||||
$c->strftimeformat = get_string('strftimerecent', 'langconfig');
|
||||
$c->strftimeformat = get_string('strftimerecentfull', 'langconfig');
|
||||
$url = new moodle_url('/user/view.php', array('id'=>$u->id, 'course'=>$this->courseid));
|
||||
$c->profileurl = $url->out(true);
|
||||
$c->profileurl = $url->out(false); // URL should not be escaped just yet.
|
||||
$c->fullname = fullname($u);
|
||||
$c->time = userdate($c->timecreated, $c->strftimeformat);
|
||||
$c->content = format_text($c->content, $c->format, $formatoptions);
|
||||
|
||||
+1
-1
@@ -8,6 +8,6 @@
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "3.7.*",
|
||||
"phpunit/dbUnit": "1.2.*",
|
||||
"moodlehq/behat-extension": "1.26.8"
|
||||
"moodlehq/behat-extension": "1.26.9"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,6 +240,11 @@ $CFG->admin = 'admin';
|
||||
// $CFG->session_memcached_acquire_lock_timeout = 120;
|
||||
// $CFG->session_memcached_lock_expire = 7200; // Ignored if memcached extension <= 2.1.0
|
||||
//
|
||||
// Please be aware that when selecting either Memcached or Memcache for sessions that it is advised to use a dedicated
|
||||
// memcache server. The memcache and memcached extensions do not provide isolated environments for individual uses.
|
||||
// Using the same server for other purposes (MUC for example) can lead to sessions being prematurely removed should
|
||||
// the other uses of the server purge the cache.
|
||||
//
|
||||
// Following setting allows you to alter how frequently is timemodified updated in sessions table.
|
||||
// $CFG->session_update_timemodified_frequency = 20; // In seconds.
|
||||
//
|
||||
|
||||
+1
-10
@@ -696,16 +696,7 @@ class dndupload_ajax_processor {
|
||||
$mod = $info->get_cm($this->cm->id);
|
||||
|
||||
// Trigger course module created event.
|
||||
$event = \core\event\course_module_created::create(array(
|
||||
'courseid' => $this->course->id,
|
||||
'context' => context_module::instance($mod->id),
|
||||
'objectid' => $mod->id,
|
||||
'other' => array(
|
||||
'modulename' => $mod->modname,
|
||||
'name' => $mod->name,
|
||||
'instanceid' => $instanceid
|
||||
)
|
||||
));
|
||||
$event = \core\event\course_module_created::create_from_cm($mod);
|
||||
$event->trigger();
|
||||
|
||||
add_to_log($this->course->id, $mod->modname, "add",
|
||||
|
||||
+24
-8
@@ -31,9 +31,10 @@ $categoryid = optional_param('category', 0, PARAM_INT); // Course category - can
|
||||
$returnto = optional_param('returnto', 0, PARAM_ALPHANUM); // Generic navigation return page switch.
|
||||
|
||||
$PAGE->set_pagelayout('admin');
|
||||
$pageparams = array('id'=>$id);
|
||||
if (empty($id)) {
|
||||
$pageparams = array('category'=>$categoryid);
|
||||
if ($id) {
|
||||
$pageparams = array('id' => $id);
|
||||
} else {
|
||||
$pageparams = array('category' => $categoryid);
|
||||
}
|
||||
$PAGE->set_url('/course/edit.php', $pageparams);
|
||||
|
||||
@@ -166,22 +167,37 @@ $stradministration = get_string("administration");
|
||||
$strcategories = get_string("categories");
|
||||
|
||||
if (!empty($course->id)) {
|
||||
$PAGE->navbar->add($streditcoursesettings);
|
||||
// Navigation note: The user is editing a course, the course will exist within the navigation and settings.
|
||||
// The navigation will automatically find the Edit settings page under course navigation.
|
||||
$pagedesc = $streditcoursesettings;
|
||||
$title = $streditcoursesettings;
|
||||
$fullname = $course->fullname;
|
||||
} else {
|
||||
$PAGE->navbar->add($stradministration, new moodle_url('/admin/index.php'));
|
||||
$PAGE->navbar->add($strcategories, new moodle_url('/course/index.php'));
|
||||
$PAGE->navbar->add($straddnewcourse);
|
||||
// The user is adding a course, this page isn't presented in the site navigation/admin.
|
||||
// Adding a new course is part of course category management territory.
|
||||
// We'd prefer to use the management interface URL without args.
|
||||
$managementurl = new moodle_url('/course/management.php');
|
||||
// These are the caps required in order to see the management interface.
|
||||
$managementcaps = array('moodle/category:manage', 'moodle/course:create');
|
||||
if ($categoryid && !has_any_capability($managementcaps, context_system::instance())) {
|
||||
// If the user doesn't have either manage caps then they can only manage within the given category.
|
||||
$managementurl->param('categoryid', $categoryid);
|
||||
}
|
||||
// Because the course category management interfaces are buried in the admin tree and that is loaded by ajax
|
||||
// we need to manually tell the navigation we need it loaded. The second arg does this.
|
||||
navigation_node::override_active_url($managementurl, true);
|
||||
|
||||
$pagedesc = $straddnewcourse;
|
||||
$title = "$site->shortname: $straddnewcourse";
|
||||
$fullname = $site->fullname;
|
||||
$PAGE->navbar->add($pagedesc);
|
||||
}
|
||||
|
||||
$PAGE->set_title($title);
|
||||
$PAGE->set_heading($fullname);
|
||||
|
||||
echo $OUTPUT->header();
|
||||
echo $OUTPUT->heading($streditcoursesettings);
|
||||
echo $OUTPUT->heading($pagedesc);
|
||||
|
||||
$editform->display();
|
||||
|
||||
|
||||
@@ -3488,6 +3488,10 @@ function mod_duplicate_activity($course, $cm, $sr = null) {
|
||||
$section = $DB->get_record('course_sections', array('id' => $cm->section, 'course' => $cm->course));
|
||||
moveto_module($newcm, $section, $cm);
|
||||
moveto_module($cm, $section, $newcm);
|
||||
|
||||
// Trigger course module created event. We can trigger the event only if we know the newcmid.
|
||||
$event = \core\event\course_module_created::create_from_cm($newcm);
|
||||
$event->trigger();
|
||||
}
|
||||
rebuild_course_cache($cm->course);
|
||||
|
||||
|
||||
@@ -120,6 +120,10 @@ if ($newcmid) {
|
||||
$newcm = get_coursemodule_from_id('', $newcmid, $course->id, true, MUST_EXIST);
|
||||
moveto_module($newcm, $section, $cm);
|
||||
moveto_module($cm, $section, $newcm);
|
||||
|
||||
// Trigger course module created event. We can trigger the event only if we know the newcmid.
|
||||
$event = \core\event\course_module_created::create_from_cm($newcm);
|
||||
$event->trigger();
|
||||
}
|
||||
|
||||
$rc->destroy();
|
||||
|
||||
+6
-10
@@ -145,16 +145,11 @@ function add_moduleinfo($moduleinfo, $course, $mform = null) {
|
||||
}
|
||||
|
||||
// Trigger event based on the action we did.
|
||||
$event = \core\event\course_module_created::create(array(
|
||||
'courseid' => $course->id,
|
||||
'context' => $modcontext,
|
||||
'objectid' => $moduleinfo->coursemodule,
|
||||
'other' => array(
|
||||
'modulename' => $moduleinfo->modulename,
|
||||
'name' => $moduleinfo->name,
|
||||
'instanceid' => $moduleinfo->instance
|
||||
)
|
||||
));
|
||||
// Api create_from_cm expects modname and id property, and we don't want to modify $moduleinfo since we are returning it.
|
||||
$eventdata = clone $moduleinfo;
|
||||
$eventdata->modname = $eventdata->modulename;
|
||||
$eventdata->id = $eventdata->coursemodule;
|
||||
$event = \core\event\course_module_created::create_from_cm($eventdata, $modcontext);
|
||||
$event->trigger();
|
||||
|
||||
add_to_log($course->id, $moduleinfo->modulename, "add",
|
||||
@@ -180,6 +175,7 @@ function add_moduleinfo($moduleinfo, $course, $mform = null) {
|
||||
*/
|
||||
function edit_module_post_actions($moduleinfo, $course) {
|
||||
global $CFG;
|
||||
require_once($CFG->libdir.'/gradelib.php');
|
||||
|
||||
$modcontext = context_module::instance($moduleinfo->coursemodule);
|
||||
$hasgrades = plugin_supports('mod', $moduleinfo->modulename, FEATURE_GRADE_HAS_GRADE, false);
|
||||
|
||||
@@ -122,9 +122,7 @@ class behat_course extends behat_base {
|
||||
public function i_go_to_the_courses_management_page() {
|
||||
return array(
|
||||
new Given('I am on homepage'),
|
||||
new Given('I expand "' . get_string('administrationsite') . '" node'),
|
||||
new Given('I expand "' . get_string('courses', 'admin') . '" node'),
|
||||
new Given('I follow "' . get_string('coursemgmt', 'admin') . '"')
|
||||
new Given('I navigate to "' . get_string('coursemgmt', 'admin') . '" node in "' . get_string('administrationsite') . ' > ' . get_string('courses', 'admin') . '"')
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
@core @core_course
|
||||
Feature: Managers can create courses
|
||||
In order to group users and contents
|
||||
As a manager
|
||||
I need to create courses and set default values on them
|
||||
|
||||
@javascript
|
||||
Scenario: Courses are created with the default forum and blocks
|
||||
Given the following "users" exist:
|
||||
| username | firstname | lastname | email |
|
||||
| teacher1 | Teacher | 1 | teacher1@asd.com |
|
||||
| student1 | Student | 1 | student1@asd.com |
|
||||
And I log in as "admin"
|
||||
And I create a course with:
|
||||
| Course full name | Course 1 |
|
||||
| Course short name | C1 |
|
||||
And I enrol "Teacher 1" user as "Teacher"
|
||||
And I enrol "Student 1" user as "Student"
|
||||
And I log out
|
||||
When I log in as "teacher1"
|
||||
And I follow "Course 1"
|
||||
Then "Latest news" "block" should exist
|
||||
And I follow "News forum"
|
||||
And "Add a new topic" "button" should exist
|
||||
And "Forced subscription" "link" should not exist
|
||||
And I log out
|
||||
And I log in as "student1"
|
||||
And I follow "Course 1"
|
||||
And I follow "News forum"
|
||||
And "Add a new topic" "button" should not exist
|
||||
And I should see "Forced subscription" in the "Administration" "block"
|
||||
@@ -12,9 +12,7 @@ Feature: The maximum number of weeks/topics in a course can be configured
|
||||
| user | course | role |
|
||||
| manager1 | Acceptance test site | manager |
|
||||
And I log in as "admin"
|
||||
And I expand "Site administration" node
|
||||
And I expand "Courses" node
|
||||
And I follow "Course default settings"
|
||||
And I navigate to "Course default settings" node in "Site administration > Courses"
|
||||
|
||||
@javascript
|
||||
Scenario: The number of sections can be increased and the limits are applied to courses
|
||||
|
||||
@@ -1921,7 +1921,7 @@ class core_course_courselib_testcase extends advanced_testcase {
|
||||
* Tests for event related to course module creation.
|
||||
*/
|
||||
public function test_course_module_created_event() {
|
||||
global $USER, $DB;
|
||||
global $USER, $DB, $CFG;
|
||||
$this->resetAfterTest();
|
||||
|
||||
// Create an assign module.
|
||||
@@ -1929,9 +1929,8 @@ class core_course_courselib_testcase extends advanced_testcase {
|
||||
$modinfo = $this->create_specific_module_test('assign');
|
||||
$events = $sink->get_events();
|
||||
$event = array_pop($events);
|
||||
$sink->close();
|
||||
|
||||
$cm = $DB->get_record('course_modules', array('id' => $modinfo->coursemodule), '*', MUST_EXIST);
|
||||
$cm = get_coursemodule_from_id('assign', $modinfo->coursemodule, 0, false, MUST_EXIST);
|
||||
$mod = $DB->get_record('assign', array('id' => $modinfo->instance), '*', MUST_EXIST);
|
||||
|
||||
// Validate event data.
|
||||
@@ -1955,6 +1954,30 @@ class core_course_courselib_testcase extends advanced_testcase {
|
||||
$arr = array($cm->course, "course", "add mod", "../mod/assign/view.php?id=$cm->id", "assign $cm->instance");
|
||||
$this->assertEventLegacyLogData($arr, $event);
|
||||
|
||||
// Let us see if duplicating an activity results in a nice course module created event.
|
||||
$sink->clear();
|
||||
$course = get_course($mod->course);
|
||||
|
||||
// Discard error logs.
|
||||
$oldlog = ini_get('error_log');
|
||||
ini_set('error_log', "$CFG->dataroot/testlog.log");
|
||||
$newcmhtml = mod_duplicate_activity($course, $cm);
|
||||
ini_set('error_log', $oldlog);
|
||||
|
||||
$events = $sink->get_events();
|
||||
$event = array_pop($events);
|
||||
$sink->close();
|
||||
|
||||
// Validate event data.
|
||||
$this->assertInstanceOf('\core\event\course_module_created', $event);
|
||||
$this->assertEquals($newcmhtml->cmid, $event->objectid);
|
||||
$this->assertEquals($USER->id, $event->userid);
|
||||
$this->assertEquals($course->id, $event->courseid);
|
||||
$url = new moodle_url('/mod/assign/view.php', array('id' => $newcmhtml->cmid));
|
||||
$this->assertEquals($url, $event->get_url());
|
||||
|
||||
// Clear the time limit, otherwise PHPUnit complains.
|
||||
set_time_limit(0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -64,14 +64,14 @@ Feature: Users can auto-enrol themself in courses where self enrolment is allowe
|
||||
And I press "Create group"
|
||||
And I fill the moodle form with:
|
||||
| Group name | Group 1 |
|
||||
| Enrolment key | testgroupenrolkey |
|
||||
| Enrolment key | Test-groupenrolkey1 |
|
||||
And I press "Save changes"
|
||||
And I log out
|
||||
And I log in as "student1"
|
||||
And I follow "Course 1"
|
||||
And I fill the moodle form with:
|
||||
| Enrolment key | testgroupenrolkey |
|
||||
| Enrolment key | Test-groupenrolkey1 |
|
||||
And I press "Enrol me"
|
||||
Then I should see "Topic 1"
|
||||
And I should not see "Enrolment options"
|
||||
And I should not see "Enrol me in this course"
|
||||
And I should not see "Enrol me in this course"
|
||||
|
||||
@@ -58,4 +58,42 @@ class behat_enrol extends behat_base {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enrols the specified user in the current course without options.
|
||||
*
|
||||
* This is a simple step, to set enrolment options would be better to
|
||||
* create a separate step as a TableNode will be required.
|
||||
*
|
||||
* @Given /^I enrol "(?P<user_fullname_string>(?:[^"]|\\")*)" user as "(?P<rolename_string>(?:[^"]|\\")*)"$/
|
||||
* @param string $userfullname
|
||||
* @param string $rolename
|
||||
* @return Given[]
|
||||
*/
|
||||
public function i_enrol_user_as($userfullname, $rolename) {
|
||||
|
||||
$steps = array(
|
||||
new Given('I follow "' . get_string('enrolledusers', 'enrol') . '"'),
|
||||
new Given('I press "' . get_string('enrolusers', 'enrol') . '"')
|
||||
);
|
||||
|
||||
if ($this->running_javascript()) {
|
||||
|
||||
// We have a div here, not a tr.
|
||||
$userliteral = $this->getSession()->getSelectorsHandler()->xpathLiteral($userfullname);
|
||||
$userrowxpath = "//div[contains(concat(' ',normalize-space(@class),' '),' user ')][contains(., $userliteral)]";
|
||||
|
||||
$steps[] = new Given('I set the field "' . get_string('assignroles', 'role') . '" to "' . $rolename . '"');
|
||||
$steps[] = new Given('I click on "' . get_string('enrol', 'enrol') . '" "button" in the "' . $userrowxpath . '" "xpath_element"');
|
||||
$steps[] = new Given('I press "' . get_string('finishenrollingusers', 'enrol') . '"');
|
||||
|
||||
} else {
|
||||
|
||||
$steps[] = new Given('I set the field "' . get_string('assignrole', 'role') . '" to "' . $rolename . '"');
|
||||
$steps[] = new Given('I set the field "addselect" to "' . $userfullname . '"');
|
||||
$steps[] = new Given('I press "add"');
|
||||
}
|
||||
|
||||
return $steps;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -86,19 +86,15 @@ class edit_category_form extends moodleform {
|
||||
$mform->setAdvanced('aggregatesubcats');
|
||||
}
|
||||
|
||||
$options = array(0 => get_string('none'));
|
||||
|
||||
for ($i=1; $i<=20; $i++) {
|
||||
$options[$i] = $i;
|
||||
}
|
||||
|
||||
$mform->addElement('select', 'keephigh', get_string('keephigh', 'grades'), $options);
|
||||
$mform->addElement('text', 'keephigh', get_string('keephigh', 'grades'), 'size="3"');
|
||||
$mform->setType('keephigh', PARAM_INT);
|
||||
$mform->addHelpButton('keephigh', 'keephigh', 'grades');
|
||||
if ((int)$CFG->grade_keephigh_flag & 2) {
|
||||
$mform->setAdvanced('keephigh');
|
||||
}
|
||||
|
||||
$mform->addElement('select', 'droplow', get_string('droplow', 'grades'), $options);
|
||||
$mform->addElement('text', 'droplow', get_string('droplow', 'grades'), 'size="3"');
|
||||
$mform->setType('droplow', PARAM_INT);
|
||||
$mform->addHelpButton('droplow', 'droplow', 'grades');
|
||||
$mform->disabledIf('droplow', 'keephigh', 'noteq', 0);
|
||||
if ((int)$CFG->grade_droplow_flag & 2) {
|
||||
|
||||
@@ -100,6 +100,7 @@ class moodlequickform_guideeditor extends HTML_QuickForm_input {
|
||||
$mode = gradingform_guide_controller::DISPLAY_EDIT_FULL;
|
||||
$module = array('name'=>'gradingform_guideeditor',
|
||||
'fullpath'=>'/grade/grading/form/guide/js/guideeditor.js',
|
||||
'requires' => array('base', 'dom', 'event', 'event-touch', 'escape'),
|
||||
'strings' => array(
|
||||
array('confirmdeletecriterion', 'gradingform_guide'),
|
||||
array('clicktoedit', 'gradingform_guide'),
|
||||
|
||||
@@ -10,7 +10,7 @@ M.gradingform_guide.init = function(Y, options) {
|
||||
currentfocus = e.currentTarget;
|
||||
});
|
||||
Y.all('.markingguidecomment').on('click', function(e) {
|
||||
currentfocus.set('value', currentfocus.get('value') + '\n' + e.currentTarget.get('innerHTML'));
|
||||
currentfocus.set('value', currentfocus.get('value') + '\n' + e.currentTarget.get('text'));
|
||||
currentfocus.focus();
|
||||
});
|
||||
|
||||
|
||||
@@ -111,9 +111,9 @@ M.gradingform_guideeditor.editmode = function(el, editmode) {
|
||||
value = M.str.gradingform_guide.clicktoedit
|
||||
taplain.addClass('empty')
|
||||
}
|
||||
taplain.one('.textvalue').set('innerHTML', value)
|
||||
taplain.one('.textvalue').set('innerHTML', Y.Escape.html(value))
|
||||
if (tb) {
|
||||
tbplain.one('.textvalue').set('innerHTML', tb.get('value'))
|
||||
tbplain.one('.textvalue').set('innerHTML', Y.Escape.html(tb.get('value')))
|
||||
}
|
||||
// hide/display textarea, textbox and plaintexts
|
||||
taplain.removeClass('hiddenelement')
|
||||
|
||||
@@ -907,7 +907,7 @@ class gradingform_guide_instance extends gradingform_instance {
|
||||
if (!empty($this->validationerrors)) {
|
||||
foreach ($this->validationerrors as $id => $err) {
|
||||
$a = new stdClass();
|
||||
$a->criterianame = $criteria[$id]['shortname'];
|
||||
$a->criterianame = s($criteria[$id]['shortname']);
|
||||
$a->maxscore = $criteria[$id]['maxscore'];
|
||||
$html .= html_writer::tag('div', get_string('err_scoreinvalid', 'gradingform_guide', $a),
|
||||
array('class' => 'gradingform_guide-error'));
|
||||
|
||||
@@ -93,20 +93,20 @@ class gradingform_guide_renderer extends plugin_renderer_base {
|
||||
'name' => '{NAME}[criteria][{CRITERION-id}][sortorder]', 'value' => $criterion['sortorder']));
|
||||
|
||||
$shortname = html_writer::empty_tag('input', array('type'=> 'text',
|
||||
'name' => '{NAME}[criteria][{CRITERION-id}][shortname]', 'value' => htmlspecialchars($criterion['shortname']),
|
||||
'name' => '{NAME}[criteria][{CRITERION-id}][shortname]', 'value' => $criterion['shortname'],
|
||||
'id ' => '{NAME}[criteria][{CRITERION-id}][shortname]'));
|
||||
$shortname = html_writer::tag('div', $shortname, array('class'=>'criterionname'));
|
||||
$description = html_writer::tag('textarea', htmlspecialchars($criterion['description']),
|
||||
$description = html_writer::tag('textarea', s($criterion['description']),
|
||||
array('name' => '{NAME}[criteria][{CRITERION-id}][description]', 'cols' => '65', 'rows' => '5'));
|
||||
$description = html_writer::tag('div', $description, array('class'=>'criteriondesc'));
|
||||
|
||||
$descriptionmarkers = html_writer::tag('textarea', htmlspecialchars($criterion['descriptionmarkers']),
|
||||
$descriptionmarkers = html_writer::tag('textarea', s($criterion['descriptionmarkers']),
|
||||
array('name' => '{NAME}[criteria][{CRITERION-id}][descriptionmarkers]', 'cols' => '65', 'rows' => '5'));
|
||||
$descriptionmarkers = html_writer::tag('div', $descriptionmarkers, array('class'=>'criteriondescmarkers'));
|
||||
|
||||
$maxscore = html_writer::empty_tag('input', array('type'=> 'text',
|
||||
'name' => '{NAME}[criteria][{CRITERION-id}][maxscore]', 'size' => '3',
|
||||
'value' => htmlspecialchars($criterion['maxscore']),
|
||||
'value' => $criterion['maxscore'],
|
||||
'id' => '{NAME}[criteria][{CRITERION-id}][maxscore]'));
|
||||
$maxscore = html_writer::tag('div', $maxscore, array('class'=>'criterionmaxscore'));
|
||||
} else {
|
||||
@@ -125,7 +125,7 @@ class gradingform_guide_renderer extends plugin_renderer_base {
|
||||
$mode == gradingform_guide_controller::DISPLAY_VIEW) {
|
||||
$descriptionclass = 'descriptionreadonly';
|
||||
}
|
||||
$shortname = html_writer::tag('div', $criterion['shortname'],
|
||||
$shortname = html_writer::tag('div', s($criterion['shortname']),
|
||||
array('class'=>'criterionshortname', 'name' => '{NAME}[criteria][{CRITERION-id}][shortname]'));
|
||||
$descmarkerclass = '';
|
||||
$descstudentclass = '';
|
||||
@@ -137,13 +137,13 @@ class gradingform_guide_renderer extends plugin_renderer_base {
|
||||
$descstudentclass = ' hide';
|
||||
}
|
||||
}
|
||||
$description = html_writer::tag('div', $criterion['description'],
|
||||
$description = html_writer::tag('div', s($criterion['description']),
|
||||
array('class'=>'criteriondescription'.$descstudentclass,
|
||||
'name' => '{NAME}[criteria][{CRITERION-id}][descriptionmarkers]'));
|
||||
$descriptionmarkers = html_writer::tag('div', $criterion['descriptionmarkers'],
|
||||
$descriptionmarkers = html_writer::tag('div', s($criterion['descriptionmarkers']),
|
||||
array('class'=>'criteriondescriptionmarkers'.$descmarkerclass,
|
||||
'name' => '{NAME}[criteria][{CRITERION-id}][descriptionmarkers]'));
|
||||
$maxscore = html_writer::tag('div', $criterion['maxscore'],
|
||||
$maxscore = html_writer::tag('div', s($criterion['maxscore']),
|
||||
array('class'=>'criteriondescriptionscore', 'name' => '{NAME}[criteria][{CRITERION-id}][maxscore]'));
|
||||
}
|
||||
|
||||
@@ -193,7 +193,7 @@ class gradingform_guide_renderer extends plugin_renderer_base {
|
||||
$scoreclass = 'error';
|
||||
$currentscore = $validationerrors[$criterion['id']]['score']; // Show invalid score in form.
|
||||
}
|
||||
$input = html_writer::tag('textarea', htmlspecialchars($currentremark),
|
||||
$input = html_writer::tag('textarea', s($currentremark),
|
||||
array('name' => '{NAME}[criteria][{CRITERION-id}][remark]', 'cols' => '65', 'rows' => '5',
|
||||
'class' => 'markingguideremark'));
|
||||
$criteriontemplate .= html_writer::tag('td', $input, array('class' => 'remark'));
|
||||
@@ -202,7 +202,7 @@ class gradingform_guide_renderer extends plugin_renderer_base {
|
||||
$score .= html_writer::empty_tag('input', array('type'=> 'text',
|
||||
'name' => '{NAME}[criteria][{CRITERION-id}][score]', 'class' => $scoreclass,
|
||||
'id' => '{NAME}[criteria][{CRITERION-id}][score]',
|
||||
'size' => '3', 'value' => htmlspecialchars($currentscore)));
|
||||
'size' => '3', 'value' => $currentscore));
|
||||
$score .= '/'.$maxscore;
|
||||
|
||||
$criteriontemplate .= html_writer::tag('td', $score, array('class' => 'score'));
|
||||
@@ -211,9 +211,9 @@ class gradingform_guide_renderer extends plugin_renderer_base {
|
||||
'name' => '{NAME}[criteria][{CRITERION-id}][remark]', 'value' => $currentremark));
|
||||
} else if ($mode == gradingform_guide_controller::DISPLAY_REVIEW ||
|
||||
$mode == gradingform_guide_controller::DISPLAY_VIEW) {
|
||||
$criteriontemplate .= html_writer::tag('td', $currentremark, array('class' => 'remark'));
|
||||
$criteriontemplate .= html_writer::tag('td', s($currentremark), array('class' => 'remark'));
|
||||
if (!empty($options['showmarkspercriterionstudents'])) {
|
||||
$criteriontemplate .= html_writer::tag('td', htmlspecialchars($currentscore). ' / '.$maxscore,
|
||||
$criteriontemplate .= html_writer::tag('td', s($currentscore). ' / '.$maxscore,
|
||||
array('class' => 'score'));
|
||||
}
|
||||
}
|
||||
@@ -272,7 +272,7 @@ class gradingform_guide_renderer extends plugin_renderer_base {
|
||||
$criteriontemplate .= html_writer::end_tag('td'); // Controls.
|
||||
$criteriontemplate .= html_writer::empty_tag('input', array('type' => 'hidden',
|
||||
'name' => '{NAME}[comments][{COMMENT-id}][sortorder]', 'value' => $comment['sortorder']));
|
||||
$description = html_writer::tag('textarea', htmlspecialchars($comment['description']),
|
||||
$description = html_writer::tag('textarea', s($comment['description']),
|
||||
array('name' => '{NAME}[comments][{COMMENT-id}][description]', 'cols' => '65', 'rows' => '5'));
|
||||
$description = html_writer::tag('div', $description, array('class'=>'criteriondesc'));
|
||||
} else {
|
||||
@@ -283,12 +283,12 @@ class gradingform_guide_renderer extends plugin_renderer_base {
|
||||
'name' => '{NAME}[comments][{COMMENT-id}][description]', 'value' => $comment['description']));
|
||||
}
|
||||
if ($mode == gradingform_guide_controller::DISPLAY_EVAL) {
|
||||
$description = html_writer::tag('span', htmlspecialchars($comment['description']),
|
||||
$description = html_writer::tag('span', s($comment['description']),
|
||||
array('name' => '{NAME}[comments][{COMMENT-id}][description]',
|
||||
'title' => get_string('clicktocopy', 'gradingform_guide'),
|
||||
'id' => '{NAME}[comments][{COMMENT-id}]', 'class'=>'markingguidecomment'));
|
||||
} else {
|
||||
$description = $comment['description'];
|
||||
$description = s($comment['description']);
|
||||
}
|
||||
}
|
||||
$descriptionclass = 'description';
|
||||
|
||||
@@ -93,8 +93,8 @@ M.gradingform_rubriceditor.editmode = function(el, editmode, focustb) {
|
||||
value = (el.hasClass('level')) ? M.str.gradingform_rubric.levelempty : M.str.gradingform_rubric.criterionempty
|
||||
taplain.addClass('empty')
|
||||
}
|
||||
taplain.one('.textvalue').set('innerHTML', value)
|
||||
if (tb) tbplain.one('.textvalue').set('innerHTML', tb.get('value'))
|
||||
taplain.one('.textvalue').set('innerHTML', Y.Escape.html(value));
|
||||
if (tb) tbplain.one('.textvalue').set('innerHTML', Y.Escape.html(tb.get('value')));
|
||||
// hide/display textarea, textbox and plaintexts
|
||||
taplain.removeClass('hiddenelement')
|
||||
ta.addClass('hiddenelement')
|
||||
|
||||
@@ -79,13 +79,13 @@ class gradingform_rubric_renderer extends plugin_renderer_base {
|
||||
}
|
||||
$criteriontemplate .= html_writer::end_tag('td'); // .controls
|
||||
$criteriontemplate .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[criteria][{CRITERION-id}][sortorder]', 'value' => $criterion['sortorder']));
|
||||
$description = html_writer::tag('textarea', htmlspecialchars($criterion['description']), array('name' => '{NAME}[criteria][{CRITERION-id}][description]', 'cols' => '10', 'rows' => '5'));
|
||||
$description = html_writer::tag('textarea', s($criterion['description']), array('name' => '{NAME}[criteria][{CRITERION-id}][description]', 'cols' => '10', 'rows' => '5'));
|
||||
} else {
|
||||
if ($mode == gradingform_rubric_controller::DISPLAY_EDIT_FROZEN) {
|
||||
$criteriontemplate .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[criteria][{CRITERION-id}][sortorder]', 'value' => $criterion['sortorder']));
|
||||
$criteriontemplate .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[criteria][{CRITERION-id}][description]', 'value' => $criterion['description']));
|
||||
}
|
||||
$description = $criterion['description'];
|
||||
$description = s($criterion['description']);
|
||||
}
|
||||
$descriptionclass = 'description';
|
||||
if (isset($criterion['error_description'])) {
|
||||
@@ -111,12 +111,12 @@ class gradingform_rubric_renderer extends plugin_renderer_base {
|
||||
$currentremark = $value['remark'];
|
||||
}
|
||||
if ($mode == gradingform_rubric_controller::DISPLAY_EVAL) {
|
||||
$input = html_writer::tag('textarea', htmlspecialchars($currentremark), array('name' => '{NAME}[criteria][{CRITERION-id}][remark]', 'cols' => '10', 'rows' => '5'));
|
||||
$input = html_writer::tag('textarea', s($currentremark), array('name' => '{NAME}[criteria][{CRITERION-id}][remark]', 'cols' => '10', 'rows' => '5'));
|
||||
$criteriontemplate .= html_writer::tag('td', $input, array('class' => 'remark'));
|
||||
} else if ($mode == gradingform_rubric_controller::DISPLAY_EVAL_FROZEN) {
|
||||
$criteriontemplate .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[criteria][{CRITERION-id}][remark]', 'value' => $currentremark));
|
||||
}else if ($mode == gradingform_rubric_controller::DISPLAY_REVIEW || $mode == gradingform_rubric_controller::DISPLAY_VIEW) {
|
||||
$criteriontemplate .= html_writer::tag('td', $currentremark, array('class' => 'remark'));
|
||||
$criteriontemplate .= html_writer::tag('td', s($currentremark), array('class' => 'remark'));
|
||||
}
|
||||
}
|
||||
$criteriontemplate .= html_writer::end_tag('tr'); // .criterion
|
||||
@@ -169,7 +169,7 @@ class gradingform_rubric_renderer extends plugin_renderer_base {
|
||||
$leveltemplate = html_writer::start_tag('td', $tdattributes);
|
||||
$leveltemplate .= html_writer::start_tag('div', array('class' => 'level-wrapper'));
|
||||
if ($mode == gradingform_rubric_controller::DISPLAY_EDIT_FULL) {
|
||||
$definition = html_writer::tag('textarea', htmlspecialchars($level['definition']), array('name' => '{NAME}[criteria][{CRITERION-id}][levels][{LEVEL-id}][definition]', 'cols' => '10', 'rows' => '4'));
|
||||
$definition = html_writer::tag('textarea', s($level['definition']), array('name' => '{NAME}[criteria][{CRITERION-id}][levels][{LEVEL-id}][definition]', 'cols' => '10', 'rows' => '4'));
|
||||
$score = html_writer::label(get_string('criterionempty', 'gradingform_rubric'), '{NAME}criteria{CRITERION-id}levels{LEVEL-id}', false, array('class' => 'accesshide'));
|
||||
$score .= html_writer::empty_tag('input', array('type' => 'text','id' => '{NAME}criteria{CRITERION-id}levels{LEVEL-id}', 'name' => '{NAME}[criteria][{CRITERION-id}][levels][{LEVEL-id}][score]', 'size' => '3', 'value' => $level['score']));
|
||||
} else {
|
||||
@@ -177,7 +177,7 @@ class gradingform_rubric_renderer extends plugin_renderer_base {
|
||||
$leveltemplate .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[criteria][{CRITERION-id}][levels][{LEVEL-id}][definition]', 'value' => $level['definition']));
|
||||
$leveltemplate .= html_writer::empty_tag('input', array('type' => 'hidden', 'name' => '{NAME}[criteria][{CRITERION-id}][levels][{LEVEL-id}][score]', 'value' => $level['score']));
|
||||
}
|
||||
$definition = $level['definition'];
|
||||
$definition = s($level['definition']);
|
||||
$score = $level['score'];
|
||||
}
|
||||
if ($mode == gradingform_rubric_controller::DISPLAY_EVAL) {
|
||||
|
||||
@@ -106,6 +106,7 @@ class MoodleQuickForm_rubriceditor extends HTML_QuickForm_input {
|
||||
if (!$this->_flagFrozen) {
|
||||
$mode = gradingform_rubric_controller::DISPLAY_EDIT_FULL;
|
||||
$module = array('name'=>'gradingform_rubriceditor', 'fullpath'=>'/grade/grading/form/rubric/js/rubriceditor.js',
|
||||
'requires' => array('base', 'dom', 'event', 'event-touch', 'escape'),
|
||||
'strings' => array(array('confirmdeletecriterion', 'gradingform_rubric'), array('confirmdeletelevel', 'gradingform_rubric'),
|
||||
array('criterionempty', 'gradingform_rubric'), array('levelempty', 'gradingform_rubric')
|
||||
));
|
||||
|
||||
+2
-2
@@ -251,9 +251,9 @@ function grade_get_grade_items_for_activity($cm, $only_main_item=false) {
|
||||
|
||||
if (!isset($cm->modname)) {
|
||||
$params = array($cm->id);
|
||||
$cm = $DB->get_record_sql("SELECT cm.*, m.name, md.name as modname
|
||||
$cm = $DB->get_record_sql("SELECT cm.*, md.name as modname
|
||||
FROM {course_modules} cm,
|
||||
{modules} md,
|
||||
{modules} md
|
||||
WHERE cm.id = ? AND md.id = cm.module", $params);
|
||||
}
|
||||
|
||||
|
||||
@@ -50,4 +50,15 @@ class core_grade_querylib_testcase extends advanced_testcase {
|
||||
$this->assertEquals(1, count($cms));
|
||||
$this->assertTrue(isset($cms[$forum1->cmid]));
|
||||
}
|
||||
|
||||
public function test_grade_get_grade_items_for_activity() {
|
||||
$this->resetAfterTest(true);
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$forum = $this->getDataGenerator()->create_module('forum', array('course'=>$course->id));
|
||||
$cm = get_coursemodule_from_id('forum', $forum->cmid, $forum->course);
|
||||
unset($cm->modname);
|
||||
$grade = grade_get_grade_items_for_activity($cm);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ class autogroup_form extends moodleform {
|
||||
$mform->setType('seed', PARAM_INT);
|
||||
|
||||
$buttonarray = array();
|
||||
$buttonarray[] = &$mform->createElement('submit', 'preview', get_string('preview'), 'xx');
|
||||
$buttonarray[] = &$mform->createElement('submit', 'preview', get_string('preview'));
|
||||
$buttonarray[] = &$mform->createElement('submit', 'submitbutton', get_string('submit'));
|
||||
$buttonarray[] = &$mform->createElement('cancel');
|
||||
$mform->addGroup($buttonarray, 'buttonar', '', array(' '), false);
|
||||
|
||||
+20
-3
@@ -111,11 +111,19 @@ class group_form extends moodleform {
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($CFG->groupenrolmentkeypolicy) and $data['enrolmentkey'] != '' and $group->enrolmentkey !== $data['enrolmentkey']) {
|
||||
// enforce password policy only if changing password
|
||||
if ($data['enrolmentkey'] != '') {
|
||||
$errmsg = '';
|
||||
if (!check_password_policy($data['enrolmentkey'], $errmsg)) {
|
||||
if (!empty($CFG->groupenrolmentkeypolicy) && $group->enrolmentkey !== $data['enrolmentkey']
|
||||
&& !check_password_policy($data['enrolmentkey'], $errmsg)) {
|
||||
// Enforce password policy when the password is changed.
|
||||
$errors['enrolmentkey'] = $errmsg;
|
||||
} else {
|
||||
// Prevent twice the same enrolment key in course groups.
|
||||
$sql = "SELECT id FROM {groups} WHERE id <> :groupid AND courseid = :courseid AND enrolmentkey = :key";
|
||||
$params = array('groupid' => $data['id'], 'courseid' => $COURSE->id, 'key' => $data['enrolmentkey']);
|
||||
if ($DB->record_exists_sql($sql, $params)) {
|
||||
$errors['enrolmentkey'] = get_string('enrolmentkeyalreadyinuse', 'group');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,6 +131,15 @@ class group_form extends moodleform {
|
||||
$errors['name'] = get_string('groupnameexists', 'group', $name);
|
||||
} else if (!empty($idnumber) && groups_get_group_by_idnumber($COURSE->id, $idnumber)) {
|
||||
$errors['idnumber']= get_string('idnumbertaken');
|
||||
} else if ($data['enrolmentkey'] != '') {
|
||||
$errmsg = '';
|
||||
if (!empty($CFG->groupenrolmentkeypolicy) && !check_password_policy($data['enrolmentkey'], $errmsg)) {
|
||||
// Enforce password policy.
|
||||
$errors['enrolmentkey'] = $errmsg;
|
||||
} else if ($DB->record_exists('groups', array('courseid' => $COURSE->id, 'enrolmentkey' => $data['enrolmentkey']))) {
|
||||
// Prevent the same enrolment key from being used multiple times in course groups.
|
||||
$errors['enrolmentkey'] = get_string('enrolmentkeyalreadyinuse', 'group');
|
||||
}
|
||||
}
|
||||
|
||||
return $errors;
|
||||
|
||||
@@ -89,3 +89,42 @@ Feature: Organize students into groups
|
||||
| Grouping name | Not the greatest grouping, but it's ok! |
|
||||
And I press "Save changes"
|
||||
And I should see "Not the greatest grouping, but it's ok!"
|
||||
|
||||
Scenario: Create groups with enrolment key
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname | category | groupmode |
|
||||
| Course 1 | C1 | 0 | 1 |
|
||||
| Course 2 | C2 | 0 | 1 |
|
||||
And I log in as "admin"
|
||||
And I follow "Course 1"
|
||||
And I expand "Users" node
|
||||
And I follow "Groups"
|
||||
When I press "Create group"
|
||||
And I set the following fields to these values:
|
||||
| Group name | Group A |
|
||||
| Enrolment key | badpasswd |
|
||||
And I press "Save changes"
|
||||
And I should see "Passwords must have at least 1 digit(s)"
|
||||
And I set the following fields to these values:
|
||||
| Group name | Group A |
|
||||
| Enrolment key | Abcdef-1 |
|
||||
And I press "Save changes"
|
||||
And I press "Create group"
|
||||
And I set the following fields to these values:
|
||||
| Group name | Group B |
|
||||
| Enrolment key | Abcdef-1 |
|
||||
And I press "Save changes"
|
||||
Then I should see "This enrolment key is already used for another group."
|
||||
And I set the following fields to these values:
|
||||
| Enrolment key | Abcdef-2 |
|
||||
And I press "Save changes"
|
||||
And the "groups" select box should contain "Group B (0)"
|
||||
And I am on homepage
|
||||
And I follow "Course 2"
|
||||
And I expand "Users" node
|
||||
And I follow "Groups"
|
||||
And I press "Create group"
|
||||
And I set the following fields to these values:
|
||||
| Group name | Group A |
|
||||
| Enrolment key | Abcdef-1 |
|
||||
And I should not see "This enrolment key is already used for another group."
|
||||
|
||||
@@ -100,3 +100,54 @@ Feature: Automatic updating of groups and groupings
|
||||
And the "idnumber" "field" should be readonly
|
||||
And the "idnumber" field should match "An ID" value
|
||||
|
||||
@javascript
|
||||
Scenario: Update groups with enrolment key
|
||||
Given the following "courses" exist:
|
||||
| fullname | shortname |
|
||||
| Course 2 | C2 |
|
||||
And the following "course enrolments" exist:
|
||||
| user | course | role |
|
||||
| teacher1 | C2 | editingteacher |
|
||||
And I log out
|
||||
And I log in as "teacher1"
|
||||
And I follow "Course 1"
|
||||
And I expand "Users" node
|
||||
And I follow "Groups"
|
||||
And I set the field "groups" to "Group (with ID)"
|
||||
And I press "Edit group settings"
|
||||
And I set the following fields to these values:
|
||||
| Enrolment key | badpasswd |
|
||||
When I press "Save changes"
|
||||
Then I should see "Passwords must have at least 1 digit(s)"
|
||||
And I set the following fields to these values:
|
||||
| Enrolment key | Abcdef-1 |
|
||||
And I press "Save changes"
|
||||
And I set the field "groups" to "Group (with ID)"
|
||||
And I press "Edit group settings"
|
||||
And I press "Save changes"
|
||||
And I should not see "This enrolment key is already used for another group."
|
||||
And I set the field "groups" to "Group (without ID)"
|
||||
And I press "Edit group settings"
|
||||
And I set the following fields to these values:
|
||||
| Enrolment key | Abcdef-1 |
|
||||
And I press "Save changes"
|
||||
And I should see "This enrolment key is already used for another group."
|
||||
And I set the following fields to these values:
|
||||
| Enrolment key | Abcdef-2 |
|
||||
And I press "Save changes"
|
||||
And I should not see "This enrolment key is already used for another group."
|
||||
And I am on homepage
|
||||
And I follow "Course 2"
|
||||
And I expand "Users" node
|
||||
And I follow "Groups"
|
||||
And I press "Create group"
|
||||
And I set the following fields to these values:
|
||||
| Group name | Group A |
|
||||
And I press "Save changes"
|
||||
And I should not see "This enrolment key is already used for another group."
|
||||
And I set the field "groups" to "Group A"
|
||||
And I press "Edit group settings"
|
||||
And I set the following fields to these values:
|
||||
| Enrolment key | Abcdef-1 |
|
||||
And I press "Save changes"
|
||||
And I should not see "This enrolment key is already used for another group."
|
||||
|
||||
+1
-9
@@ -233,17 +233,9 @@ if (defined('COMPONENT_CLASSLOADER')) {
|
||||
require('version.php');
|
||||
$CFG->target_release = $release;
|
||||
|
||||
$_SESSION = array();
|
||||
$_SESSION['SESSION'] = new stdClass();
|
||||
$_SESSION['SESSION']->lang = $CFG->lang;
|
||||
$_SESSION['USER'] = new stdClass();
|
||||
$_SESSION['USER']->id = 0;
|
||||
$_SESSION['USER']->mnethostid = 1;
|
||||
|
||||
\core\session\manager::init_empty_session();
|
||||
global $SESSION;
|
||||
global $USER;
|
||||
$SESSION = &$_SESSION['SESSION'];
|
||||
$USER = &$_SESSION['USER'];
|
||||
|
||||
global $COURSE;
|
||||
$COURSE = new stdClass();
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Automatically generated strings for Moodle installer
|
||||
*
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['thisdirection'] = 'ltr';
|
||||
$string['thislanguage'] = 'Aragonés';
|
||||
@@ -0,0 +1,55 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Automatically generated strings for Moodle installer
|
||||
*
|
||||
* Do not edit this file manually! It contains just a subset of strings
|
||||
* needed during the very first steps of installation. This file was
|
||||
* generated automatically by export-installer.php (which is part of AMOS
|
||||
* {@link http://docs.moodle.org/dev/Languages/AMOS}) using the
|
||||
* list of strings defined in /install/stringnames.txt.
|
||||
*
|
||||
* @package installer
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
$string['cannotcreatedboninstall'] = '<p>ناتوانرێت داتابەیس دروست بکرێت.</p>
|
||||
<p>داتابەیستی دیاری کراو بوونی نییەو بەکارهێنەری دراو مۆڵەتی دروست کردنی داتابەیسی نییە.</p>
|
||||
<p>پێویستە سەرپەرشتیاری ماڵپەڕ سازاندنی داتابەیس پەسەند بکات.</p>';
|
||||
$string['cannotcreatelangdir'] = 'ناتوانریت ڕابەرنامەی زمان دروست بکرێت';
|
||||
$string['cannotcreatetempdir'] = 'ناتوانرێت ڕابەرنامەی کاتیی دروست بکرێت';
|
||||
$string['cannotdownloadcomponents'] = 'ناتوانرێت پێکهێنەرەکان داببەزێنرێن';
|
||||
$string['cannotdownloadzipfile'] = 'ناتوانریت فایلی زیپ داببەزێنرێت';
|
||||
$string['cannotfindcomponent'] = 'تانتوانرێت پێکهێنەر بدۆزرێتەوە';
|
||||
$string['cannotsavemd5file'] = 'ناتوانرێت فایلی md5 پاشەکەوت بکرێت';
|
||||
$string['cannotsavezipfile'] = 'ناتوانرێت فایلی زیپ پاشەکەوت بکرێت';
|
||||
$string['cannotunzipfile'] = 'ناتوانرێت فایلەکە لە زیپ بهێنرێتە دەرەوە';
|
||||
$string['componentisuptodate'] = 'سەرنجەکە تەواو نوێیە';
|
||||
$string['dmlexceptiononinstall'] = '<p>هەڵە ڕویدا لە داتابەیس [{$a->errorcode}]. <br />{$a->debuginfo}</p>.
|
||||
|
||||
<br />{$a->debuginfo}</p>';
|
||||
$string['downloadedfilecheckfailed'] = 'پشکنینی دابەزاندنی فایل سەرکەوتوو نەبوو';
|
||||
$string['invalidmd5'] = 'پشکنینی گۆڕاو هەڵە بو - دووبارە بکەوە';
|
||||
$string['missingrequiredfield'] = 'هەندێ بواری داواکراو دیاری نەکراون';
|
||||
$string['remotedownloaderror'] = '<p>دابەزاندنی پێکهێنەری ڕاژەکارەکەت سەرکەوتو نەبوو. تکایە ڕێکخستنەکانی پڕۆکسی پشت ڕاست بکەوە: درێژبوونەوەی PHP cURL بەشێوەیەکی زۆر پێشنیاز کراوە. </p>
|
||||
<p>پێویستە تۆ فایلی <a href="{$a->url}">{$a->url}</a> بەدەست دابەزێنی، کۆپی بکە بۆ "{$a->dest}" لە نێو ڕاژەکارەکەت و لەوێ لەنێو زیپ بیهێنە دەرەوە. </p>';
|
||||
$string['wrongdestpath'] = 'ڕێڕەوی ئامانج هەڵەیە';
|
||||
$string['wrongsourcebase'] = 'بنچینەی URLی سەرچاوە هەڵەیە';
|
||||
$string['wrongzipfilename'] = 'ناوی فایلی زیپ هەڵەیە';
|
||||
@@ -41,4 +41,4 @@ $string['cliunknowoption'] = 'Nicht erkannte Optionen:
|
||||
Hilfe wird über die Option --help angezeigt.';
|
||||
$string['cliyesnoprompt'] = 'y (yes=ja) oder n (no=nein) eingeben';
|
||||
$string['environmentrequireinstall'] = 'muss installiert und aktiviert sein';
|
||||
$string['environmentrequireversion'] = 'Version {$a->needed} ist erforderlich - aktuell ist {$a->current} installiert.';
|
||||
$string['environmentrequireversion'] = 'notwendig: {$a->needed} - installiert: {$a->current}';
|
||||
|
||||
@@ -34,8 +34,8 @@ $string['admindirname'] = 'Admin-Verzeichnis';
|
||||
$string['availablelangs'] = 'Verfügbare Sprachpakete';
|
||||
$string['chooselanguagehead'] = 'Sprache wählen';
|
||||
$string['chooselanguagesub'] = 'Wählen Sie eine Sprache, die Sie während der Installation verwenden wollen. Die ausgewählte Sprache wird nach der Installation als Standardsprache der Instanz benutzt, aber Sie dürfen die Sprache jederzeit ändern.';
|
||||
$string['clialreadyconfigured'] = 'Die Datei config.php existiert bereits. Bitte benutzen Sie admin/cli/install_database.php, wenn sie diese Website installieren möchten.';
|
||||
$string['clialreadyinstalled'] = 'Die Datei config.php existiert bereits. Bitte benutzen Sie admin/cli/upgrade.php, wenn Sie diese Website aktualisieren möchten.';
|
||||
$string['clialreadyconfigured'] = 'Die Datei config.php existiert bereits. Bitte benutzen Sie admin/cli/install_database.php, wenn Sie diese Site installieren möchten.';
|
||||
$string['clialreadyinstalled'] = 'Die Datei config.php existiert bereits. Bitte benutzen Sie admin/cli/install_database.php, wenn Sie diese Site aktualisieren möchten.';
|
||||
$string['cliinstallheader'] = 'Installation von Moodle {$a} über die Kommandozeile';
|
||||
$string['databasehost'] = 'Datenbank-Server';
|
||||
$string['databasename'] = 'Datenbank-Name';
|
||||
@@ -64,11 +64,11 @@ $string['pathshead'] = 'Pfade bestätigen';
|
||||
$string['pathsrodataroot'] = 'Das Verzeichnis dataroot ist schreibgeschützt.';
|
||||
$string['pathsroparentdataroot'] = 'Das Verzeichnis ({$a->parent}) ist schreibgeschützt. Deswegen kann das Datenverzeichnis ({$a->dataroot}) vom Installer nicht angelegt werden.';
|
||||
$string['pathssubadmindir'] = 'Einige Webserver benutzen /admin als speziellen Link, um auf Einstellungsseiten oder Ähnliches zu verweisen. Unglücklicherweise kollidiert dies mit dem standardmäßigen Verzeichnis für die Moodle-Administration. Sie können dieses Problem beheben, indem Sie das Verzeichnis admin in Ihrer Moodle-Installation umbenennen und den neuen Namen hier eingeben (z.B. <em>moodleadmin</em>). Mit dieser Änderung werden alle Admin-Links korrigiert.';
|
||||
$string['pathssubdataroot'] = 'Sie benötigen einen Platz, wo Moodle hochgeladene Dateien abspeichern kann. Dieses Verzeichnis muss Lese- und Schreibrechte für das Nutzerkonto besitzen, mit dem Ihr Webservers läuft (üblicherweise \'nobody\', \'apache\' oder \'www\'). Außerdem sollte das Verzeichnis nicht direkt aus dem Internet erreichbar sein. Das Intallationsskript wird versuchen, ein solches Verzeichnis zu erstellen, falls es nicht existiert.</p>';
|
||||
$string['pathssubdirroot'] = 'Vollständiger Pfad der Moodle-Installation';
|
||||
$string['pathssubwwwroot'] = 'Vollständige Webadresse für den Zugriff auf Moodle. Es ist nicht möglich, über unterschiedliche Adressen auf Moodle zuzugreifen. Sollte Ihre Website mehrere öffentliche Adressen verwenden, so müssen Sie eine Adresse festlegen und für die übrigen Adressen dauerhafte Weiterleitungen dorthin einrichten.
|
||||
<p>Falls Ihre Website gleichzeitig im Intranet und im Internet erreichbar ist, so tragen Sie die öffentliche Adresse ein. Konfigurieren Sie den DNS so, dass Moodle auch aus dem Intranet über die öffentliche Adresse erreichbar ist.
|
||||
<p>Führen Sie Ihre Moodle-Installation unbedingt mit der richtigen Adresse durch, weil es andernfalls zu Problemen kommen könnte.';
|
||||
$string['pathssubdataroot'] = '<p>Sie benötigen einen Platz, wo Moodle hochgeladene Dateien abspeichern kann. </p><p>Dieses Verzeichnis muss Lese- und Schreibrechte für das Nutzerkonto besitzen, mit dem Ihr Webservers läuft (üblicherweise \'nobody\', \'apache\' oder \'www-data).</p><p> Außerdem sollte das Verzeichnis nicht direkt aus dem Internet erreichbar sein. </p><p>Das Intallationsskript wird versuchen, ein solches Verzeichnis zu erstellen, falls es nicht existiert.</p>';
|
||||
$string['pathssubdirroot'] = '<p>Vollständiger Pfad der Moodle-Installation.</p>';
|
||||
$string['pathssubwwwroot'] = '<p>Webadresse, die ein Nutzer im Browser eingibt, um auf Moodle zuzugreifen.</p><p> Es ist nicht möglich, über unterschiedliche Adressen auf Moodle zuzugreifen. Sollte Ihre Website mehrere öffentliche Adressen verwenden, so müssen Sie eine Adresse festlegen und für die übrigen Adressen dauerhafte Weiterleitungen dorthin einrichten.</p>
|
||||
<p>Falls Ihre Website gleichzeitig im Intranet und im Internet erreichbar ist, so tragen Sie die öffentliche Adresse ein. Konfigurieren Sie den DNS so, dass Moodle auch aus dem Intranet über die öffentliche Adresse erreichbar ist.</p>
|
||||
<p>Führen Sie Ihre Moodle-Installation unbedingt mit der richtigen Adresse durch, weil es andernfalls zu Problemen kommen könnte.</p>';
|
||||
$string['pathsunsecuredataroot'] = 'Der Speicherort des Verzeichnisses \'dataroot\' ist unsicher';
|
||||
$string['pathswrongadmindir'] = 'Das Admin-Verzeichnis existiert nicht';
|
||||
$string['phpextension'] = 'PHP-Extension {$a}';
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user