Merge branch 'MDL-70482_310_STABLE' of https://github.com/sebmehr/moodle into MOODLE_310_STABLE

This commit is contained in:
Adrian Greeve
2021-01-06 11:23:47 +08:00
99 changed files with 1755 additions and 858 deletions
@@ -10,16 +10,12 @@ Feature: Manage data storage purposes
And I open the action menu in "region-main" "region"
And I choose "Purposes" in the open action menu
And I press "Add purpose"
And I set the field "Name" to "Purpose 1"
And I set the field "Description" to "Purpose 1 description"
And I click on ".form-autocomplete-downarrow" "css_element" in the "Lawful bases" "form_row"
And I click on "Contract (GDPR Art. 6.1(b))" "list_item"
And I click on "Legal obligation (GDPR Art 6.1(c))" "list_item"
And I press the escape key
And I click on ".form-autocomplete-downarrow" "css_element" in the "Sensitive personal data processing reasons" "form_row"
And I click on "Explicit consent (GDPR Art. 9.2(a))" "list_item"
And I press the escape key
And I set the field "retentionperiodnumber" to "2"
And I set the following fields to these values:
| Name | Purpose 1 |
| Description | Purpose 1 description |
| Lawful bases | Contract (GDPR Art. 6.1(b)),Legal obligation (GDPR Art 6.1(c)) |
| Sensitive personal data processing reasons | Explicit consent (GDPR Art. 9.2(a)) |
| retentionperiodnumber | 2 |
When I press "Save"
Then I should see "Purpose 1" in the "List of data purposes" "table"
And I should see "Contract (GDPR Art. 6.1(b))" in the "Purpose 1" "table_row"
@@ -31,14 +27,13 @@ Feature: Manage data storage purposes
Scenario: Update a data storage purpose
Given I open the action menu in "Purpose 1" "table_row"
And I choose "Edit" in the open action menu
And I set the field "Name" to "Purpose 1 edited"
And I set the field "Description" to "Purpose 1 description edited"
And I click on "Legal obligation (GDPR Art 6.1(c))" "text" in the ".form-autocomplete-selection" "css_element"
And I click on ".form-autocomplete-downarrow" "css_element" in the "Lawful bases" "form_row"
And I click on "Vital interests (GDPR Art. 6.1(d))" "list_item"
And I press the escape key
And I set the field "retentionperiodnumber" to "3"
And I click on "protected" "checkbox"
And I set the following fields to these values:
| Name | Purpose 1 edited |
| Description | Purpose 1 description edited |
| Lawful bases | Contract (GDPR Art. 6.1(b)), Vital interests (GDPR Art. 6.1(d)) |
| Sensitive personal data processing reasons | Explicit consent (GDPR Art. 9.2(a)) |
| retentionperiodnumber | 3 |
| protected | 1 |
When I press "Save changes"
Then I should see "Purpose 1 edited" in the "List of data purposes" "table"
And I should see "Purpose 1 description edited" in the "Purpose 1 edited" "table_row"
+1 -3
View File
@@ -53,9 +53,7 @@ Feature: Manage plearning plan
And I follow "Home"
And I navigate to "Competencies > Learning plan templates" in site administration
And I click on ".template-cohorts" "css_element" in the "Science template cohort" "table_row"
And I click on ".form-autocomplete-downarrow" "css_element"
And I click on "cohort plan" item in the autocomplete list
And I press the escape key
And I set the field "Select cohorts to sync" to "cohort plan"
When I click on "Add cohorts" "button"
Then I should see "2 learning plans were created."
And I follow "Learning plan templates"
+1 -1
View File
@@ -1,2 +1,2 @@
define ("tool_templatelibrary/search",["jquery","core/ajax","core/log","core/notification","core/templates","core/config"],function(a,b,c,d,e,f){var g=function(b){e.render("tool_templatelibrary/search_results",{templates:b}).done(function(b,c){e.replaceNode(a("[data-region=\"searchresults\"]"),b,c)}).fail(d.exception)},h=function(c){var e=a("[data-field=\"component\"]").val(),f=a("[data-region=\"list-templates\"] [data-region=\"input\"]").val();if(""!==f){a("[data-region=\"list-templates\"] [data-action=\"clearsearch\"]").removeClass("d-none")}else{a("[data-region=\"list-templates\"] [data-action=\"clearsearch\"]").addClass("d-none")}document.location.hash=f;b.call([{methodname:"tool_templatelibrary_list_templates",args:{component:e,search:f,themename:c},done:g,fail:d.exception}],!0,!1)},i=null,j=function(a,b){if(null!==i){window.clearTimeout(i)}i=window.setTimeout(function(){a();i=null},b)},k=function(){j(h.bind(this,f.theme),400)};a("[data-region=\"list-templates\"]").on("change","[data-field=\"component\"]",k);a("[data-region=\"list-templates\"]").on("input","[data-region=\"input\"]",k);a("[data-action=\"clearsearch\"]").on("click",function(){a("[data-region=\"input\"]").val("");h(f.theme);a(this).addClass("d-none")});a("[data-region=\"input\"]").val(document.location.hash.replace("#",""));h(f.theme);return{}});
define ("tool_templatelibrary/search",["jquery","core/ajax","core/log","core/notification","core/templates","core/config"],function(a,b,c,d,e,f){var g=function(b){e.render("tool_templatelibrary/search_results",{templates:b}).done(function(b,c){e.replaceNode(a("[data-region=\"searchresults\"]"),b,c)}).fail(d.exception)},h=function(c){var e=a("[data-field=\"component\"]").val(),f=a("[data-region=\"list-templates\"] [data-region=\"input\"]").val();if(""!==f){a("[data-region=\"list-templates\"] [data-action=\"clearsearch\"]").removeClass("d-none")}else{a("[data-region=\"list-templates\"] [data-action=\"clearsearch\"]").addClass("d-none")}b.call([{methodname:"tool_templatelibrary_list_templates",args:{component:e,search:f,themename:c},done:g,fail:d.exception}],!0,!1)},i=null,j=function(a,b){if(null!==i){window.clearTimeout(i)}i=window.setTimeout(function(){a();i=null},b)},k=function(){j(h.bind(this,f.theme),400)};a("[data-region=\"list-templates\"]").on("change","[data-field=\"component\"]",k);a("[data-region=\"list-templates\"]").on("input","[data-region=\"input\"]",k);a("[data-action=\"clearsearch\"]").on("click",function(){a("[data-region=\"input\"]").val("");h(f.theme);a(this).addClass("d-none")});h(f.theme);return{}});
//# sourceMappingURL=search.min.js.map
File diff suppressed because one or more lines are too long
+1 -3
View File
@@ -52,9 +52,8 @@ define(['jquery', 'core/ajax', 'core/log', 'core/notification', 'core/templates'
} else {
$('[data-region="list-templates"] [data-action="clearsearch"]').addClass('d-none');
}
// Trigger the search.
document.location.hash = searchStr;
// Trigger the search.
ajax.call([
{methodname: 'tool_templatelibrary_list_templates',
args: {component: componentStr, search: searchStr, themename: themename},
@@ -96,7 +95,6 @@ define(['jquery', 'core/ajax', 'core/log', 'core/notification', 'core/templates'
$(this).addClass('d-none');
});
$('[data-region="input"]').val(document.location.hash.replace('#', ''));
refreshSearch(config.theme);
return {};
});
@@ -38,6 +38,22 @@ use tool_templatelibrary\api;
*/
class list_templates_page implements renderable, templatable {
/** @var string $component The currently selected component */
protected $component;
/** @var string $search The current search */
protected $search;
/**
* Template page constructor
*
* @param string $component
* @param string $search
*/
public function __construct(string $component = '', string $search = '') {
$this->component = $component;
$this->search = $search;
}
/**
* Export this data so it can be used as the context for a mustache template.
*
@@ -46,6 +62,7 @@ class list_templates_page implements renderable, templatable {
public function export_for_template(renderer_base $output) {
$data = new stdClass();
$data->allcomponents = array();
$data->search = $this->search;
$fulltemplatenames = api::list_templates();
$pluginmanager = core_plugin_manager::instance();
$components = array();
@@ -59,6 +76,7 @@ class list_templates_page implements renderable, templatable {
foreach ($components as $component) {
$info = new stdClass();
$info->component = $component;
$info->selected = ($component === $this->component);
if (strpos($component, 'core') === 0) {
$info->name = get_string('coresubsystem', 'tool_templatelibrary', $component);
} else {
@@ -72,10 +72,10 @@
{{/label}}
{{$element}}
<select id="selectcomponent" class="form-control" data-field="component">
<select id="selectcomponent" name="component" class="form-control" data-field="component">
<option value="">{{#str}}all, tool_templatelibrary{{/str}}</option>
{{#allcomponents}}
<option value="{{component}}">{{name}}</option>
<option value="{{component}}" {{#selected}}selected{{/selected}}>{{name}}</option>
{{/allcomponents}}
</select>
{{/element}}
@@ -84,10 +84,10 @@
{{< core_form/element-template }}
{{$element}}
{{< core/search_input_auto }}
{{$label}}{{{ searchstring }}}{{/label}}
{{$placeholder}}{{#str}}
search, core
{{/str}}{{/placeholder}}
{{$label}}
{{#str}} search, tool_templatelibrary {{/str}}
{{/label}}
{{$value}}{{ search }}{{/value}}
{{/ core/search_input_auto }}
{{/element}}
{{/ core_form/element-template }}
+1 -4
View File
@@ -55,10 +55,7 @@ Feature: Manage analytics models
And I choose "Create model" in the open action menu
And I set the field "Enabled" to "Enable"
And I select "__core_course__analytics__target__course_completion" from the "target" singleselect
And I open the autocomplete suggestions list
And I click on "Read actions amount" item in the autocomplete list
And I open the autocomplete suggestions list
And I click on "Any write action in the course" item in the autocomplete list
And I set the field "Indicators" to "Read actions amount, Any write action in the course"
And I select "__core__analytics__time_splitting__single_range" from the "timesplitting" singleselect
And I press "Save changes"
Then I should see "No predictions available yet" in the "Students at risk of not meeting the course completion conditions" "table_row"
@@ -82,16 +82,17 @@ class restore_quiz_decode_testcase extends \core_privacy\tests\provider_testcase
$newcm = duplicate_module($course, get_fast_modinfo($course)->get_cm($quiz->cmid));
$sql = "SELECT qa.answer
$sql = "SELECT qa.id, qa.answer
FROM {quiz} q
LEFT JOIN {quiz_slots} qs ON qs.quizid = q.id
LEFT JOIN {question_answers} qa ON qa.question = qs.questionid
WHERE q.id = :quizid";
$params = array('quizid' => $newcm->instance);
$answers = $DB->get_fieldset_sql($sql, $params);
$this->assertEquals($CFG->wwwroot . '/course/view.php?id=' . $course->id, $answers[0]);
$this->assertEquals($CFG->wwwroot . '/mod/quiz/view.php?id=' . $quiz->cmid, $answers[1]);
$this->assertEquals($CFG->wwwroot . '/grade/report/index.php?id=' . $quiz->cmid, $answers[2]);
$this->assertEquals($CFG->wwwroot . '/mod/quiz/index.php?id=' . $quiz->cmid, $answers[3]);
$answers = $DB->get_records_sql_menu($sql, $params);
$this->assertEquals($CFG->wwwroot . '/course/view.php?id=' . $course->id, $answers[$firstanswer->id]);
$this->assertEquals($CFG->wwwroot . '/mod/quiz/view.php?id=' . $quiz->cmid, $answers[$secondanswer->id]);
$this->assertEquals($CFG->wwwroot . '/grade/report/index.php?id=' . $quiz->cmid, $answers[$thirdanswer->id]);
$this->assertEquals($CFG->wwwroot . '/mod/quiz/index.php?id=' . $quiz->cmid, $answers[$fourthanswer->id]);
}
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+7 -8
View File
@@ -543,11 +543,9 @@ function(
// Filter out all pagination options which are too large for the amount of courses user is enrolled in.
var totalCourseCount = parseInt(root.find(Selectors.courseView.region).attr('data-totalcoursecount'), 10);
if (totalCourseCount) {
itemsPerPage = itemsPerPage.filter(function(pagingOption) {
return pagingOption.value < totalCourseCount;
});
}
itemsPerPage = itemsPerPage.filter(function(pagingOption) {
return pagingOption.value < totalCourseCount || pagingOption.value === 0;
});
var filters = getFilterValues(root);
var config = $.extend({}, DEFAULT_PAGED_CONTENT_CONFIG);
@@ -602,7 +600,8 @@ function(
pageCourses = $.merge(loadedPages[currentPage].courses, courses.slice(0, nextPageStart));
}
} else {
nextPageStart = pageData.limit;
// When the page limit is zero, there is only one page of courses, no start for next page.
nextPageStart = pageData.limit || false;
pageCourses = (pageData.limit > 0) ? courses.slice(0, pageData.limit) : courses;
}
@@ -611,8 +610,8 @@ function(
courses: pageCourses
};
// Set up the next page
var remainingCourses = nextPageStart ? courses.slice(nextPageStart, courses.length) : [];
// Set up the next page (if there is more than one page).
var remainingCourses = nextPageStart !== false ? courses.slice(nextPageStart, courses.length) : [];
if (remainingCourses.length) {
loadedPages[currentPage + 1] = {
courses: remainingCourses
+3 -2
View File
@@ -202,8 +202,9 @@ class main implements renderable, templatable {
// Check and remember the given view.
$this->view = $view ? $view : BLOCK_MYOVERVIEW_VIEW_CARD;
// Check and remember the given page size.
if ($paging == BLOCK_MYOVERVIEW_PAGING_ALL) {
// Check and remember the given page size, `null` indicates no page size set
// while a `0` indicates a paging size of `All`.
if (!is_null($paging) && $paging == BLOCK_MYOVERVIEW_PAGING_ALL) {
$this->paging = BLOCK_MYOVERVIEW_PAGING_ALL;
} else {
$this->paging = $paging ? $paging : BLOCK_MYOVERVIEW_PAGING_12;
@@ -7,30 +7,30 @@ Feature: The my overview block allows users to persistence of their page limits
| student1 | Student | X | student1@example.com | S1 |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
| Course 2 | C2 | 0 |
| Course 3 | C3 | 0 |
| Course 4 | C4 | 0 |
| Course 5 | C5 | 0 |
| Course 6 | C6 | 0 |
| Course 7 | C7 | 0 |
| Course 8 | C8 | 0 |
| Course 9 | C9 | 0 |
| Course 1 | C01 | 0 |
| Course 2 | C02 | 0 |
| Course 3 | C03 | 0 |
| Course 4 | C04 | 0 |
| Course 5 | C05 | 0 |
| Course 6 | C06 | 0 |
| Course 7 | C07 | 0 |
| Course 8 | C08 | 0 |
| Course 9 | C09 | 0 |
| Course 10 | C10 | 0 |
| Course 11 | C11 | 0 |
| Course 12 | C12 | 0 |
| Course 13 | C13 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
| student1 | C2 | student |
| student1 | C3 | student |
| student1 | C4 | student |
| student1 | C5 | student |
| student1 | C6 | student |
| student1 | C7 | student |
| student1 | C8 | student |
| student1 | C9 | student |
| student1 | C01 | student |
| student1 | C02 | student |
| student1 | C03 | student |
| student1 | C04 | student |
| student1 | C05 | student |
| student1 | C06 | student |
| student1 | C07 | student |
| student1 | C08 | student |
| student1 | C09 | student |
| student1 | C10 | student |
| student1 | C11 | student |
| student1 | C12 | student |
@@ -38,8 +38,8 @@ Feature: The my overview block allows users to persistence of their page limits
Scenario: Toggle the page limit between page reloads
Given I log in as "student1"
When I click on "[data-toggle='dropdown']" "css_element" in the "Course overview" "block"
And I click on "All" "link"
When I click on "[data-action='limit-toggle']" "css_element" in the "Course overview" "block"
And I click on "All" "link" in the ".dropdown-menu.show" "css_element"
Then I should see "Course 13"
And I reload the page
Then I should see "Course 13"
@@ -47,8 +47,8 @@ Feature: The my overview block allows users to persistence of their page limits
Scenario: Toggle the page limit between grouping changes
Given I log in as "student1"
When I click on "[data-toggle='dropdown']" "css_element" in the "Course overview" "block"
And I click on "All" "link"
When I click on "[data-action='limit-toggle']" "css_element" in the "Course overview" "block"
And I click on "All" "link" in the ".dropdown-menu.show" "css_element"
And I click on "All (except removed from view)" "button" in the "Course overview" "block"
And I click on "In progress" "link" in the "Course overview" "block"
Then I should see "Course 13"
@@ -0,0 +1,191 @@
@block @block_myoverview @javascript
Feature: My overview block pagination
Background:
Given the following "users" exist:
| username | firstname | lastname | email | idnumber |
| student1 | Student | X | student1@example.com | S1 |
And the following "courses" exist:
| fullname | shortname | category |
| Course 01 | C1 | 0 |
| Course 02 | C2 | 0 |
| Course 03 | C3 | 0 |
| Course 04 | C4 | 0 |
| Course 05 | C5 | 0 |
| Course 06 | C6 | 0 |
| Course 07 | C7 | 0 |
| Course 08 | C8 | 0 |
| Course 09 | C9 | 0 |
| Course 10 | C10 | 0 |
| Course 11 | C11 | 0 |
| Course 12 | C12 | 0 |
| Course 13 | C13 | 0 |
| Course 14 | C14 | 0 |
| Course 15 | C15 | 0 |
| Course 16 | C16 | 0 |
| Course 17 | C17 | 0 |
| Course 18 | C18 | 0 |
| Course 19 | C19 | 0 |
| Course 20 | C20 | 0 |
| Course 21 | C21 | 0 |
| Course 22 | C22 | 0 |
| Course 23 | C23 | 0 |
| Course 24 | C24 | 0 |
| Course 25 | C25 | 0 |
Scenario: The pagination controls should be hidden if I am not enrolled in any courses
When I log in as "student1"
Then I should see "No courses" in the "Course overview" "block"
And I should not see "Show" in the "Course overview" "block"
And ".block_myoverview .dropdown-menu.show" "css_element" should not be visible
And ".block_myoverview [data-control='next']" "css_element" should not be visible
And ".block_myoverview [data-control='previous']" "css_element" should not be visible
And I log out
Scenario: The pagination controls should be hidden if I am enrolled in 12 courses or less
Given the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
| student1 | C2 | student |
| student1 | C3 | student |
| student1 | C4 | student |
| student1 | C5 | student |
| student1 | C6 | student |
| student1 | C7 | student |
| student1 | C8 | student |
| student1 | C9 | student |
| student1 | C10 | student |
| student1 | C11 | student |
| student1 | C12 | student |
When I log in as "student1"
Then I should not see "Show" in the "Course overview" "block"
And ".block_myoverview .dropdown-menu.show" "css_element" should not be visible
And ".block_myoverview [data-control='next']" "css_element" should not be visible
And ".block_myoverview [data-control='previous']" "css_element" should not be visible
And I log out
Scenario: The default pagination should be 12 courses
Given the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
| student1 | C2 | student |
| student1 | C3 | student |
| student1 | C4 | student |
| student1 | C5 | student |
| student1 | C6 | student |
| student1 | C7 | student |
| student1 | C8 | student |
| student1 | C9 | student |
| student1 | C10 | student |
| student1 | C11 | student |
| student1 | C12 | student |
| student1 | C13 | student |
When I log in as "student1"
Then I should see "12" in the "[data-action='limit-toggle']" "css_element"
And I log out
Scenario: I should only see pagination limit options less than total number of enrolled courses
Given the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
| student1 | C2 | student |
| student1 | C3 | student |
| student1 | C4 | student |
| student1 | C5 | student |
| student1 | C6 | student |
| student1 | C7 | student |
| student1 | C8 | student |
| student1 | C9 | student |
| student1 | C10 | student |
| student1 | C11 | student |
| student1 | C12 | student |
| student1 | C13 | student |
And I log in as "student1"
When I click on "[data-action='limit-toggle']" "css_element" in the "Course overview" "block"
Then I should see "All" in the ".dropdown-menu.show" "css_element"
And I should see "12" in the ".dropdown-menu.show" "css_element"
And ".block_myoverview [data-control='next']" "css_element" should be visible
And ".block_myoverview [data-control='previous']" "css_element" should be visible
But I should not see "24" in the ".block_myoverview .dropdown-menu.show" "css_element"
And I log out
Scenario: Previous page button should be disabled when on the first page of courses
Given the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
| student1 | C2 | student |
| student1 | C3 | student |
| student1 | C4 | student |
| student1 | C5 | student |
| student1 | C6 | student |
| student1 | C7 | student |
| student1 | C8 | student |
| student1 | C9 | student |
| student1 | C10 | student |
| student1 | C11 | student |
| student1 | C12 | student |
| student1 | C13 | student |
When I log in as "student1"
Then the "class" attribute of ".block_myoverview [data-control='previous']" "css_element" should contain "disabled"
And I log out
Scenario: Next page button should be disabled when on the last page of courses
Given the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
| student1 | C2 | student |
| student1 | C3 | student |
| student1 | C4 | student |
| student1 | C5 | student |
| student1 | C6 | student |
| student1 | C7 | student |
| student1 | C8 | student |
| student1 | C9 | student |
| student1 | C10 | student |
| student1 | C11 | student |
| student1 | C12 | student |
| student1 | C13 | student |
When I log in as "student1"
And I click on "[data-control='next']" "css_element" in the "Course overview" "block"
And I wait until ".block_myoverview [data-control='next']" "css_element" exists
Then the "class" attribute of ".block_myoverview [data-control='next']" "css_element" should contain "disabled"
And I log out
Scenario: Next and previous page buttons should both be enabled when not on last or first page of courses
Given the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
| student1 | C2 | student |
| student1 | C3 | student |
| student1 | C4 | student |
| student1 | C5 | student |
| student1 | C6 | student |
| student1 | C7 | student |
| student1 | C8 | student |
| student1 | C9 | student |
| student1 | C10 | student |
| student1 | C11 | student |
| student1 | C12 | student |
| student1 | C13 | student |
| student1 | C14 | student |
| student1 | C15 | student |
| student1 | C16 | student |
| student1 | C17 | student |
| student1 | C18 | student |
| student1 | C19 | student |
| student1 | C20 | student |
| student1 | C21 | student |
| student1 | C22 | student |
| student1 | C23 | student |
| student1 | C24 | student |
| student1 | C25 | student |
When I log in as "student1"
And I click on "[data-control='next']" "css_element" in the "Course overview" "block"
And I wait until ".block_myoverview [data-control='next']" "css_element" exists
Then the "class" attribute of ".block_myoverview [data-control='next']" "css_element" should not contain "disabled"
And the "class" attribute of ".block_myoverview [data-control='previous']" "css_element" should not contain "disabled"
And I should see "Course 13" in the "Course overview" "block"
And I should see "Course 24" in the "Course overview" "block"
But I should not see "Course 12" in the "Course overview" "block"
And I should not see "Course 25" in the "Course overview" "block"
And I log out
@@ -52,9 +52,9 @@
<a href="{{url}}"
title={{#quote}}{{{name}}}{{/quote}}
aria-label='{{#str}} ariaeventlistitem, block_timeline, { "name": {{#quote}}{{{name}}}{{/quote}}, "course": {{#quote}}{{{course.fullnamedisplay}}}{{/quote}}, "date": "{{#userdate}} {{timesort}}, {{#str}} strftimedatetime, core_langconfig {{/str}} {{/userdate}}" } {{/str}}'
><h6 class="event-name text-truncate mb-0">{{#quote}}{{{name}}}{{/quote}}</h6></a>
><h6 class="event-name text-truncate mb-0">{{{name}}}</h6></a>
{{#course.fullnamedisplay}}
<small class="text-muted text-truncate mb-0">{{#quote}}{{{course.fullnamedisplay}}}{{/quote}}</small>
<small class="text-muted text-truncate mb-0">{{{course.fullnamedisplay}}}</small>
{{/course.fullnamedisplay}}
{{#action.actionable}}
<h6 class="mb-0 pt-2">
+5 -1
View File
@@ -116,7 +116,11 @@ class calendar_event_exporter extends event_exporter_base {
$values['islastday'] = false;
$today = $this->related['type']->timestamp_to_date_array($this->related['today']);
$values['popupname'] = $this->event->get_name();
if ($hascourse) {
$values['popupname'] = external_format_string($this->event->get_name(), \context_course::instance($course->id), true);
} else {
$values['popupname'] = external_format_string($this->event->get_name(), \context_system::instance(), true);
}
$times = $this->event->get_times();
if ($duration = $times->get_duration()) {
@@ -102,8 +102,13 @@ class course_handler extends \core_customfield\handler {
has_capability('moodle/course:changelockedcustomfields', $context));
} else {
$context = $this->get_parent_context();
return (!$field->get_configdata_property('locked') ||
guess_if_creator_will_have_course_capability('moodle/course:changelockedcustomfields', $context));
if ($context->contextlevel == CONTEXT_SYSTEM) {
return (!$field->get_configdata_property('locked') ||
has_capability('moodle/course:changelockedcustomfields', $context));
} else {
return (!$field->get_configdata_property('locked') ||
guess_if_creator_will_have_course_capability('moodle/course:changelockedcustomfields', $context));
}
}
}
+2 -2
View File
@@ -38,7 +38,7 @@ Feature: Tagging courses
And I expand all fieldsets
Then "Mathematics" "autocomplete_suggestions" should exist
And I set the following fields to these values:
| Tags | Algebra |
| Tags | Mathematics, Algebra |
And I press "Save and display"
And I am on "Course 2" course homepage
And I navigate to "Edit settings" in current page administration
@@ -75,7 +75,7 @@ Feature: Tagging courses
And I navigate to "Course tags" in current page administration
Then I should see "Mathematics" in the ".form-autocomplete-selection" "css_element"
And I set the following fields to these values:
| Tags | Algebra |
| Tags | Mathematics, Algebra |
And I press "Save changes"
And I am on "Course 2" course homepage
And I navigate to "Course tags" in current page administration
+5 -3
View File
@@ -31,9 +31,11 @@ Feature: Rename roles within a course
And "Learner" "button" should exist
And I navigate to course participants
And I set the field "type" in the "Filter 1" "fieldset" to "Roles"
And I click on ".form-autocomplete-downarrow" "css_element" in the "Filter 1" "fieldset"
And I should see "Tutor (Non-editing teacher)" in the ".form-autocomplete-suggestions" "css_element"
And I open the autocomplete suggestions list in the "Filter 1" "fieldset"
And I should see "Learner (Student)" in the ".form-autocomplete-suggestions" "css_element"
And I press the escape key
And I set the field "Type or select..." in the "Filter 1" "fieldset" to "Tutor (Non-editing teacher)"
And I click on "Student 1's role assignments" "link"
And I click on ".form-autocomplete-downarrow" "css_element" in the "Student 1" "table_row"
And "Tutor (Non-editing teacher)" "autocomplete_suggestions" should exist
@@ -53,6 +55,6 @@ Feature: Rename roles within a course
And "Learner" "button" should not exist
And I navigate to course participants
And I set the field "type" in the "Filter 1" "fieldset" to "Roles"
And I click on ".form-autocomplete-downarrow" "css_element" in the "Filter 1" "fieldset"
And I open the autocomplete suggestions list in the "Filter 1" "fieldset"
And I should see "Non-editing teacher" in the ".form-autocomplete-suggestions" "css_element"
And I should see "Student" in the ".form-autocomplete-suggestions" "css_element"
+4
View File
@@ -45,6 +45,10 @@ class enrol_fee_plugin extends enrol_plugin {
$currencies[$c] = new lang_string($c, 'core_currencies');
}
uasort($currencies, function($a, $b) {
return strcmp($a, $b);
});
return $currencies;
}
+15 -16
View File
@@ -130,18 +130,17 @@ Feature: Teacher can search and enrol users one by one into the course
Scenario: Searching for a non-existing user
Given I navigate to course participants
And I press "Enrol users"
And I set the field "Select users" to "qwertyuiop"
And I click on ".form-autocomplete-downarrow" "css_element" in the "Select users" "form_row"
And I click on "Select users" "field"
And I type "qwertyuiop"
Then I should see "No suggestions"
@javascript
Scenario: If there are less than 100 matching users, all are displayed for selection
Given I navigate to course participants
And I press "Enrol users"
When I set the field "Select users" to "example.com"
And I click on ".form-autocomplete-downarrow" "css_element" in the "Select users" "form_row"
And I click on "Student 099" item in the autocomplete list
Then I should see "Student 099"
When I click on "Select users" "field"
And I type "example.com"
Then "Student 099" "autocomplete_suggestions" should exist
@javascript
Scenario: If there are more than 100 matching users, inform there are too many.
@@ -151,8 +150,8 @@ Feature: Teacher can search and enrol users one by one into the course
| student101 | Student | 101 | student101@example.com |
And I navigate to course participants
And I press "Enrol users"
When I set the field "Select users" to "example.com"
And I click on ".form-autocomplete-downarrow" "css_element" in the "Select users" "form_row"
When I click on "Select users" "field"
And I type "example.com"
Then I should see "Too many users (>100) to show"
@javascript
@@ -161,8 +160,8 @@ Feature: Teacher can search and enrol users one by one into the course
| maxusersperpage | 5 |
And I navigate to course participants
And I press "Enrol users"
When I set the field "Select users" to "student00"
And I click on ".form-autocomplete-downarrow" "css_element" in the "Select users" "form_row"
When I click on "Select users" "field"
And I type "student00"
Then I should see "Too many users (>5) to show"
@javascript
@@ -177,18 +176,18 @@ Feature: Teacher can search and enrol users one by one into the course
When I am on "Course 001" course homepage
Then I navigate to course participants
And I press "Enrol users"
When I set the field "Select users" to "student100@example.com"
And I click on ".form-autocomplete-downarrow" "css_element" in the "Select users" "form_row"
And I click on "Select users" "field"
And I type "student100@example.com"
Then I should see "student100@example.com, CITY1, GB, 1234567892, 1234567893, ABC1, ABC2"
# Remove identity field in setting User policies
And the following config values are set as admin:
| showuseridentity | idnumber,email,phone1,phone2,department,institution |
When I am on "Course 001" course homepage
And I am on "Course 001" course homepage
And I navigate to course participants
And I press "Enrol users"
When I set the field "Select users" to "student100@example.com"
And I click on ".form-autocomplete-downarrow" "css_element" in the "Select users" "form_row"
Then I should see "student100@example.com, 1234567892, 1234567893, ABC1, ABC2"
And I click on "Select users" "field"
And I type "student100@example.com"
And I should see "student100@example.com, 1234567892, 1234567893, ABC1, ABC2"
# The following tests are commented out as a result of MDL-66339.
# @javascript
+1 -1
View File
@@ -101,7 +101,7 @@
}
.gradingform_rubric .criterion .description {
width: 150px;
min-width: 150px;
font-weight: bold;
}
+2 -5
View File
@@ -50,15 +50,12 @@ Feature: Organize students into groups
And the "members" select box should not contain "Student 1 (student1@example.com)"
And I navigate to course participants
And I set the field "type" in the "Filter 1" "fieldset" to "Groups"
And I click on ".form-autocomplete-downarrow" "css_element" in the "Filter 1" "fieldset"
And I click on "Group 1" "list_item"
And I set the field "Type or select..." in the "Filter 1" "fieldset" to "Group 1"
And I click on "Apply filters" "button"
And I should see "Student 0"
And I should see "Student 1"
And I should not see "Student 2"
And I click on "Group 1" "autocomplete_selection"
And I click on ".form-autocomplete-downarrow" "css_element" in the "Filter 1" "fieldset"
And I click on "Group 2" "list_item"
And I set the field "Type or select..." in the "Filter 1" "fieldset" to "Group 2"
And I click on "Apply filters" "button"
And I should see "Student 2"
And I should see "Student 3"
+2 -4
View File
@@ -46,8 +46,7 @@ Feature: The description of a group can be viewed by students and teachers
And I should see "Description for Group A"
And ".groupinfobox" "css_element" should exist
And I set the field "type" in the "Filter 1" "fieldset" to "Groups"
And I click on ".form-autocomplete-downarrow" "css_element" in the "Filter 1" "fieldset"
And I click on "Group B" "list_item"
And I set the field "Type or select..." in the "Filter 1" "fieldset" to "Group B"
And I click on "Apply filters" "button"
And I click on "Student 2" "link" in the "participants" "table"
And I click on "Group B" "link"
@@ -96,8 +95,7 @@ Feature: The description of a group can be viewed by students and teachers
And I should see "Description for Group A"
And ".groupinfobox" "css_element" should exist
And I set the field "type" in the "Filter 1" "fieldset" to "Groups"
And I click on ".form-autocomplete-downarrow" "css_element" in the "Filter 1" "fieldset"
And I click on "Group B" "list_item"
And I set the field "Type or select..." in the "Filter 1" "fieldset" to "Group B"
And I click on "Apply filters" "button"
And I click on "Student 2" "link" in the "participants" "table"
And I click on "Group B" "link"
+1
View File
@@ -29,5 +29,6 @@
defined('MOODLE_INTERNAL') || die();
$string['parentlanguage'] = '';
$string['thisdirection'] = 'ltr';
$string['thislanguage'] = 'Español - Internacional';
+43
View File
@@ -0,0 +1,43 @@
<?php
// This file is part of Moodle - https://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 <https://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['clianswerno'] = 'Жок';
$string['cliansweryes'] = 'Ооба';
$string['cliincorrectvalueerror'] = 'Ката, «{$a->option}» үчүн «{$a->value}» туура эмес маани';
$string['cliincorrectvalueretry'] = 'Туура эмес маани, кайрадан аракет кылып көрүңүз';
$string['clitypevalue'] = 'Маанисин киргизгиле';
$string['clitypevaluedefault'] = 'Маанисин киргизгиле, алгач белгиленген ({$a}) маанисин колдонуу үчүн Enter баскычын баскыла';
$string['cliunknowoption'] = 'Таанылбаган параметрлер {$a}
Сураныч, --help параметрин колдонуңуз';
$string['cliyesnoprompt'] = 'У киргизиңиз (Ооба дегенди билдирет) же n киргизиңиз (Жок дегенди билдирет)';
$string['environmentrequireinstall'] = 'Орнотуу жана күйгүзүү зарыл';
$string['environmentrequireversion'] = '{$a->needed} версиясы талап кылынат, сиз колдонгон версия {$a->current}';
+21 -15
View File
@@ -29,18 +29,24 @@
defined('MOODLE_INTERNAL') || die();
$string['cannotcreatelangdir'] = 'Не може да се креира lang директориумот.';
$string['cannotcreatetempdir'] = 'Не може да се креира temp директориумот.';
$string['cannotdownloadcomponents'] = 'Не може да се преземат компонентите.';
$string['cannotdownloadzipfile'] = 'Не може да се преземи ZIP датотеката.';
$string['cannotfindcomponent'] = 'Не може да се најде компонентата.';
$string['cannotsavemd5file'] = 'Не може да се сними md5 датотеката.';
$string['cannotsavezipfile'] = 'Не може да се сними ZIP датотеката.';
$string['cannotunzipfile'] = 'Не може да се отпакува датотеката.';
$string['componentisuptodate'] = 'Компонентата е од последната верзија';
$string['downloadedfilecheckfailed'] = 'Проверката на преземената датотека не заврши успешно.';
$string['invalidmd5'] = 'невалидна md5';
$string['missingrequiredfield'] = 'Некои задолжителни полиња недостасуваат';
$string['wrongdestpath'] = 'Погрешна дестинација.';
$string['wrongsourcebase'] = 'Погрешна изворна адреса.';
$string['wrongzipfilename'] = 'Погрешна ZIP датотека.';
$string['cannotcreatedboninstall'] = '<p>Не може да се креира базата со податоци.</p>
<p>Посочената база со податоци не постои и корисникот нема овластувања да ја креира базата со податоци.</p>
<p>Администраторот на сајтот треба да ја верификува конфигурацијата на базата со податоци.</p>';
$string['cannotcreatelangdir'] = 'Lang именик не може да се креира';
$string['cannotcreatetempdir'] = 'Не може да се креира привремена папка';
$string['cannotdownloadcomponents'] = 'Не можете да ги симнете компонентите';
$string['cannotdownloadzipfile'] = 'Не можете да спуштите ZIP фајл';
$string['cannotfindcomponent'] = 'Компонентата не е пронајдена';
$string['cannotsavemd5file'] = 'Не може да се сними md5 фајлот';
$string['cannotsavezipfile'] = 'Не може да се сними ZIP фајл';
$string['cannotunzipfile'] = 'Не можеше да се одзипува фајлот';
$string['componentisuptodate'] = 'Компонентата е ажурирана.';
$string['dmlexceptiononinstall'] = '<p>Настана грешка во базата на податоци [{$a->errorcode}].<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'] = 'Погрешно име на ZIP фајл';
+1 -1
View File
@@ -33,4 +33,4 @@ $string['language'] = 'Јазик';
$string['moodlelogo'] = 'Moodle лого';
$string['next'] = 'Следно';
$string['previous'] = 'Претходно';
$string['reload'] = 'Вчитај повторно';
$string['reload'] = 'Вчитај одново';
+2 -2
View File
@@ -32,7 +32,7 @@ defined('MOODLE_INTERNAL') || die();
$string['admindirname'] = 'Adminmappe';
$string['availablelangs'] = 'Tilgjengelige språkpakker';
$string['chooselanguagehead'] = 'Velg et språk';
$string['chooselanguagesub'] = 'Velg språk (bare for INSTALLASJONEN). Du vil kunne velge språk for nettsted og bruker på et skjermbilde senere.';
$string['chooselanguagesub'] = 'Velg et språk for installasjonen. Dette språket vil også bli brukt som standardspråk for nettstedet, men det kan endres senere.';
$string['clialreadyconfigured'] = 'Konfigurasjonsfilen config.php finnes allerede. Vennligst bruk admin/cli/install_database.php hvis du vil installere Moodle på denne portalen.';
$string['clialreadyinstalled'] = 'Filen config.php eksisterer allerede. Vennligst bruk admin/cli/install_database.php hvis du vil oppgradere Moodle på denne portalen.';
$string['cliinstallheader'] = 'Moodle {$a} kommandolinje installasjonsprogram';
@@ -47,7 +47,7 @@ $string['environmenthead'] = 'Sjekker "omgivelsene" dine...';
$string['environmentsub2'] = 'Hver Moodle-utgivelse har minimumskrav til PHP versjon og et sett med obligatoriske PHP-tillegg. En full sjekk av omgivelser gjøres før hver installering og oppgradering. Vær vennlig å kontakte serveradministrator hvis du ikke vet hvordan du skal installere nye versjoner eller slå på PHP-tillegg.';
$string['errorsinenvironment'] = 'Omgivelseskontroll feilet!';
$string['installation'] = 'Installasjon';
$string['langdownloaderror'] = 'Beklageligvis ble ikke språkpakken "{$a}" installert. Installasjonsprosessen vil fortsette på Engelsk.';
$string['langdownloaderror'] = 'Beklageligvis kunne ikke språkpakken "{$a}" lastes ned. Installasjonsprosessen vil fortsette på Engelsk.';
$string['memorylimithelp'] = '<p>PHP minnegrensen for serveren din er nå satt til {$a}.</p>
<p>Dette kan skape minneproblemer for Moodle senere, spesielt hvis du har mange moduler tillatt og/eller mange brukere.</p>
<p>Vi anbefaler at du konfigurer PHP med en høyere grense enn mulig, for eksepmel 40M. Det er flere måter å gjøre dette på.:</p>
+2 -2
View File
@@ -49,7 +49,7 @@ Verificarea completă a mediului se face înainte de fiecare instalare și upgra
$string['errorsinenvironment'] = 'Verificarea mediului eșuată!';
$string['installation'] = 'Instalare';
$string['langdownloaderror'] = 'Din păcate, limba "{$a}" nu a putut fi descărcată. Procesul de instalare va continua în limba engleză.';
$string['memorylimithelp'] = '<p> Limita de memorie PHP pentru serverul dvs. este setată în prezent la {$ a}. </p>
$string['memorylimithelp'] = '<p> Limita de memorie PHP pentru serverul dvs. este setată în prezent la {$a}. </p>
<p> Acest lucru poate determina Moodle să aibă probleme de memorie mai târziu, în special
dacă aveți o mulțime de module activate și / sau o mulțime de utilizatori. </p>
@@ -90,7 +90,7 @@ $string['pathswrongadmindir'] = 'Directorul admin nu există';
$string['phpextension'] = 'extensie PHP {$a}';
$string['phpversion'] = 'Versiune PHP';
$string['phpversionhelp'] = '<p> Moodle necesită o versiune PHP de cel puțin 5.6.5 sau 7.1 (7.0.x are unele limitări ale motorului). </p>
<p> În prezent executați versiunea {$ a}. </p>
<p> În prezent executați versiunea {$a}. </p>
<p> Trebuie să faceți upgrade PHP sau să vă mutați la o gazdă cu o versiune mai nouă de PHP. </p>';
$string['welcomep10'] = '{$a->installername} ({$a->installerversion})';
$string['welcomep20'] = 'Vedeți această pagină deoarece ați instalat și lansat cu succes pachetul <strong>{$a->packname} {$a->packversion}</strong> în computerul dumneavoastră. Felicitări!';
+1
View File
@@ -29,5 +29,6 @@
defined('MOODLE_INTERNAL') || die();
$string['parentlanguage'] = '';
$string['thisdirection'] = 'ltr';
$string['thislanguage'] = 'Română';
+1 -1
View File
@@ -31,4 +31,4 @@ defined('MOODLE_INTERNAL') || die();
$string['parentlanguage'] = 'ro';
$string['thisdirection'] = 'ltr';
$string['thislanguage'] = 'Română';
$string['thislanguage'] = 'Română Workplace';
+2 -2
View File
@@ -39,5 +39,5 @@ $string['cliunknowoption'] = 'Ej identifierade alternativ:
{$a}
Vänligen använd --help alternativet.';
$string['cliyesnoprompt'] = 'skriv in y (betyder ja) eller n (betyder nej)';
$string['environmentrequireinstall'] = 'är nödvändig att installera/aktivera';
$string['environmentrequireversion'] = 'version {$a->needed} är nödvändig och du använder {$a->current}';
$string['environmentrequireinstall'] = 'måste vara installerat och aktiverat';
$string['environmentrequireversion'] = 'version {$a->needed} krävs och du använder {$a->current}';
+1 -1
View File
@@ -45,7 +45,7 @@ $string['dmlexceptiononinstall'] = '<p>Ett databasfel har inträffat [{$a->error
$string['downloadedfilecheckfailed'] = 'Det gick inte att kontrollera den nedladdade filen';
$string['invalidmd5'] = 'Kontrollvariabeln var felaktig - försök igen';
$string['missingrequiredfield'] = 'Det saknas några obligatoriska fält';
$string['remotedownloaderror'] = 'Nedladdningen av en komponent till din server misslyckades, var snäll och verifiera inställningar för proxy. <br /><br />Du måste ladda ner <a href="{$a->url}">{$a->url}</a> filen manuellt, kopiera den till "{$a->dest}" på din server och packa upp den där.';
$string['remotedownloaderror'] = 'Nedladdningen av en komponent till din server misslyckades. Vänligen verifiera inställningar för proxy. <br /><br />Du måste ladda ner <a href="{$a->url}">{$a->url}</a> filen manuellt, kopiera den till "{$a->dest}" på din server och packa upp den där.';
$string['wrongdestpath'] = 'Fel sökväg';
$string['wrongsourcebase'] = 'Fel bas-URL till källan';
$string['wrongzipfilename'] = 'Fel namn på ZIP-filen';
+2 -2
View File
@@ -32,8 +32,8 @@ defined('MOODLE_INTERNAL') || die();
$string['admindirname'] = 'Katalog/mapp för administration';
$string['availablelangs'] = 'Tillgängliga språkpaket';
$string['chooselanguagehead'] = 'Välj ett språk';
$string['chooselanguagesub'] = 'Var snäll och välj ett språk för installationen. Du kommer att ha möjlighet att välja språk för webbplatsen och användarna på en senare skärm.';
$string['clialreadyinstalled'] = 'Filen config.php finns redan, var snäll och använd admin/cli/upgrade.php om Du vill uppgradera Din webbplats.';
$string['chooselanguagesub'] = 'Vänligen välj ett språk för installationen. Du kommer att ha möjlighet att välja språk för webbplatsen och användarna på en senare skärm.';
$string['clialreadyinstalled'] = 'Filen config.php finns redan. Vänligen använd admin/cli/upgrade.php om Du vill uppgradera Din webbplats.';
$string['cliinstallheader'] = 'Installationsprogram av typ kommandorad {$a} för Moodle ';
$string['databasehost'] = 'Värd för databas';
$string['databasename'] = 'Namn på databas';
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,2 +1,2 @@
define ("core/paged_content_factory",["jquery","core/templates","core/notification","core/paged_content","core/paged_content_events","core/pubsub","core/ajax"],function(a,b,c,d,e,f,g){var h={PAGED_CONTENT:"core/paged_content"},j={ITEMS_PER_PAGE_SINGLE:25,ITEMS_PER_PAGE_ARRAY:[25,50,100,0],MAX_PAGES:3},k=function(){return{pagingbar:!1,pagingdropdown:!1,skipjs:!0,ignorecontrolwhileloading:!0,controlplacementbottom:!1}},l=function(){return{showitemsperpageselector:!1,itemsperpage:35,previous:!0,next:!0,activepagenumber:1,hidecontrolonsinglepage:!0,pages:[]}},m=function(a,b){var c=1;if(0<a){var d=a%b;if(d){a-=d;c=a/b+1}else{c=a/b}}return c},n=function(b,c){if(null===c){c=j.ITEMS_PER_PAGE_SINGLE}if(a.isArray(c)){c=c[0]}var d=l();d.itemsperpage=c;for(var e=m(b,c),f=1,g;f<=e;f++){g={number:f,page:""+f};if(1==f){g.active=!0}d.pages.push(g)}d.barsize=10;return d},o=function(b){if(a.isArray(b)){var c=b.map(function(a){if("number"==typeof a){return{value:a,active:!1}}else{return a}}),d=c.filter(function(a){return a.active});if(!d.length){c[0].active=!0}return c}else{return b}},p=function(b){if(null===b){b=j.ITEMS_PER_PAGE_ARRAY}var c=l();c.itemsperpage=o(b);c.showitemsperpageselector=a.isArray(b)&&1<b.length;return c},q=function(a,b){if(a){return n(a,b)}else{return p(b)}},r=function(b,c){if(null===b){b=j.ITEMS_PER_PAGE_SINGLE}if(a.isArray(b)){return{options:b}}var d={options:[]},e=0,f=0,g=j.MAX_PAGES;if(c.hasOwnProperty("maxPages")){g=c.maxPages}for(var h=1,k;h<=g;h++){k=0;if(2>=h){k=b;f=b}else{f=2*f;k=f}e+=k;var l={itemcount:k,content:e};if(1==h){l.active=!0}d.options.push(l)}return d},s=function(a,b,c){var d=k();if(c.hasOwnProperty("ignoreControlWhileLoading")){d.ignorecontrolwhileloading=c.ignoreControlWhileLoading}if(c.hasOwnProperty("controlPlacementBottom")){d.controlplacementbottom=c.controlPlacementBottom}if(c.hasOwnProperty("hideControlOnSinglePage")){d.hidecontrolonsinglepage=c.hideControlOnSinglePage}if(c.hasOwnProperty("ariaLabels")){d.arialabels=c.ariaLabels}if(c.hasOwnProperty("dropdown")&&c.dropdown){d.pagingdropdown=r(b,c)}else{d.pagingbar=q(a,b)}return d},t=function(e,f,g,i){i=i||{};var j=a.Deferred(),k=s(e,f,i);b.render(h.PAGED_CONTENT,k).then(function(b,c){b=a(b);var e=b.attr("id");if(i.hasOwnProperty("eventNamespace")){e=i.eventNamespace}var f=b;d.init(f,g,e);v(e,i);j.resolve(b,c)}).fail(function(a){j.reject(a)}).fail(c.exception);return j.promise()},u=function(a){return function callback(b){g.call([{methodname:"core_user_update_user_preferences",args:{preferences:[{type:a,value:b}]}}])}},v=function(a,b){if(b.hasOwnProperty("persistentLimitKey")){f.subscribe(a+e.SET_ITEMS_PER_PAGE_LIMIT,u(b.persistentLimitKey))}};return{create:function create(a,b){return t(null,null,a,b)},createWithLimit:function createWithLimit(a,b,c){return t(null,a,b,c)},createWithTotalAndLimit:t,createFromStaticList:function createFromStaticList(a,b,c,d){if("undefined"==typeof d){d={}}var e=a.length;return t(e,b,function(b){var d=[];b.forEach(function(b){var c=b.offset,f=b.limit?c+b.limit:e,g=a.slice(c,f);d.push(g)});return c(d)},d)},createFromAjax:t,resetLastPageNumber:function resetLastPageNumber(a,b){f.publish(a+e.ALL_ITEMS_LOADED,b)}}});
define ("core/paged_content_factory",["jquery","core/templates","core/notification","core/paged_content","core/paged_content_events","core/pubsub","core/ajax"],function(a,b,c,d,e,f,g){var h={PAGED_CONTENT:"core/paged_content"},j={ITEMS_PER_PAGE_SINGLE:25,ITEMS_PER_PAGE_ARRAY:[25,50,100,0],MAX_PAGES:3},k=function(){return{pagingbar:!1,pagingdropdown:!1,skipjs:!0,ignorecontrolwhileloading:!0,controlplacementbottom:!1}},l=function(){return{showitemsperpageselector:!1,itemsperpage:35,previous:!0,next:!0,activepagenumber:1,hidecontrolonsinglepage:!0,pages:[]}},m=function(a,b){var c=1;if(0<a){var d=a%b;if(d){a-=d;c=a/b+1}else{c=a/b}}return c},n=function(b,c){if(null===c){c=j.ITEMS_PER_PAGE_SINGLE}if(a.isArray(c)){c=c[0]}var d=l();d.itemsperpage=c;for(var e=m(b,c),f=1,g;f<=e;f++){g={number:f,page:""+f};if(1==f){g.active=!0}d.pages.push(g)}d.barsize=10;return d},o=function(b){var c=[];if(a.isArray(b)){c=b.map(function(a){if("number"==typeof a){return{value:a,active:!1}}else{return a}});var d=c.filter(function(a){return a.active});if(!d.length){c[0].active=!0}}else{c=[{value:b,active:!0}]}return c},p=function(b){if(null===b){b=j.ITEMS_PER_PAGE_ARRAY}var c=l();c.itemsperpage=o(b);c.showitemsperpageselector=a.isArray(b)&&1<b.length;return c},q=function(a,b){if(a){return n(a,b)}else{return p(b)}},r=function(b,c){if(null===b){b=j.ITEMS_PER_PAGE_SINGLE}if(a.isArray(b)){return{options:b}}var d={options:[]},e=0,f=0,g=j.MAX_PAGES;if(c.hasOwnProperty("maxPages")){g=c.maxPages}for(var h=1,k;h<=g;h++){k=0;if(2>=h){k=b;f=b}else{f=2*f;k=f}e+=k;var l={itemcount:k,content:e};if(1==h){l.active=!0}d.options.push(l)}return d},s=function(a,b,c){var d=k();if(c.hasOwnProperty("ignoreControlWhileLoading")){d.ignorecontrolwhileloading=c.ignoreControlWhileLoading}if(c.hasOwnProperty("controlPlacementBottom")){d.controlplacementbottom=c.controlPlacementBottom}if(c.hasOwnProperty("hideControlOnSinglePage")){d.hidecontrolonsinglepage=c.hideControlOnSinglePage}if(c.hasOwnProperty("ariaLabels")){d.arialabels=c.ariaLabels}if(c.hasOwnProperty("dropdown")&&c.dropdown){d.pagingdropdown=r(b,c)}else{d.pagingbar=q(a,b)}return d},t=function(e,f,g,i){i=i||{};var j=a.Deferred(),k=s(e,f,i);b.render(h.PAGED_CONTENT,k).then(function(b,c){b=a(b);var e=b.attr("id");if(i.hasOwnProperty("eventNamespace")){e=i.eventNamespace}var f=b;d.init(f,g,e);v(e,i);j.resolve(b,c)}).fail(function(a){j.reject(a)}).fail(c.exception);return j.promise()},u=function(a){return function callback(b){g.call([{methodname:"core_user_update_user_preferences",args:{preferences:[{type:a,value:b}]}}])}},v=function(a,b){if(b.hasOwnProperty("persistentLimitKey")){f.subscribe(a+e.SET_ITEMS_PER_PAGE_LIMIT,u(b.persistentLimitKey))}};return{create:function create(a,b){return t(null,null,a,b)},createWithLimit:function createWithLimit(a,b,c){return t(null,a,b,c)},createWithTotalAndLimit:t,createFromStaticList:function createFromStaticList(a,b,c,d){if("undefined"==typeof d){d={}}var e=a.length;return t(e,b,function(b){var d=[];b.forEach(function(b){var c=b.offset,f=b.limit?c+b.limit:e,g=a.slice(c,f);d.push(g)});return c(d)},d)},createFromAjax:t,resetLastPageNumber:function resetLastPageNumber(a,b){f.publish(a+e.ALL_ITEMS_LOADED,b)}}});
//# sourceMappingURL=paged_content_factory.min.js.map
File diff suppressed because one or more lines are too long
+3
View File
@@ -1173,6 +1173,9 @@ function($, log, str, templates, notification, LoadingIcon, Aria) {
originalSelect.hide();
var container = originalSelect.parent();
// Ensure that the data-fieldtype is set for behat.
$(input).find('input').attr('data-fieldtype', 'autocomplete');
container.append(layout);
container.find('[data-region="form_autocomplete-input"]').replaceWith(input);
container.find('[data-region="form_autocomplete-suggestions"]').replaceWith(suggestions);
+7 -4
View File
@@ -166,9 +166,11 @@ function(
* @return {int|array}
*/
var buildItemsPerPagePagingBarContext = function(itemsPerPage) {
var context = [];
if ($.isArray(itemsPerPage)) {
// Convert the array into a format accepted by the template.
var context = itemsPerPage.map(function(num) {
context = itemsPerPage.map(function(num) {
if (typeof num === 'number') {
// If the item is just a plain number then convert it into
// an object with value and active keys.
@@ -190,11 +192,12 @@ function(
if (!activeItems.length) {
context[0].active = true;
}
return context;
} else {
return itemsPerPage;
// Convert the integer into a format accepted by the template.
context = [{value: itemsPerPage, active: true}];
}
return context;
};
/**
+4 -1
View File
@@ -101,7 +101,6 @@ class behat_field_manager {
* @return behat_form_field
*/
public static function get_field_instance($type, NodeElement $fieldnode, Session $session) {
global $CFG;
// If the field is not part of a moodleform, we should still try to find out
@@ -152,6 +151,10 @@ class behat_field_manager {
$type = $fieldnode->getAttribute('type');
switch ($type) {
case 'text':
if ($fieldtype = $fieldnode->getAttribute('data-fieldtype')) {
return self::normalise_fieldtype($fieldtype);
}
return 'text';
case 'password':
case 'email':
case 'file':
@@ -48,43 +48,75 @@ class behat_form_autocomplete extends behat_form_text {
throw new coding_exception('Setting the value of an autocomplete field requires javascript.');
}
// Set the value of the autocomplete's input.
// If this autocomplete offers suggestions then these should be fetched by setting the value and waiting for the
// JS to finish fetching those suggestions.
// Clear all current selections.
$rootnode = $this->field->getParent()->getParent();
$selections = $rootnode->findAll('css', '.form-autocomplete-selection [role=option]');
foreach (array_reverse($selections) as $selection) {
$selection->click();
$this->wait_for_pending_js();
}
$istagelement = $this->field->hasAttribute('data-tags') && $this->field->getAttribute('data-tags');
$allowscreation = $this->field->hasAttribute('data-tags') && !empty($this->field->getAttribute('data-tags'));
$hasmultiple = $this->field->hasAttribute('data-multiple') && !empty($this->field->getAttribute('data-multiple'));
if ($istagelement && false !== strpos($value, ',')) {
// Commas have a special meaning as a value separator in 'tag' autocomplete elements.
if ($hasmultiple && false !== strpos($value, ',')) {
// Commas have a special meaning as a value separator in 'multiple' autocomplete elements.
// To handle this we break the value up by comma, and enter it in chunks.
$values = explode(',', $value);
while ($value = array_shift($values)) {
$this->set_value($value);
$this->add_value(trim($value), $allowscreation);
}
} else {
$this->field->setValue($value);
$this->wait_for_pending_js();
// If the autocomplete found suggestions, then it will have:
// 1) marked itself as expanded; and
// 2) have an aria-selected suggestion in the list.
$expanded = $this->field->getAttribute('aria-expanded');
$suggestion = $this->field->getParent()->find('css', '.form-autocomplete-suggestions > [aria-selected="true"]');
if ($expanded && null !== $suggestion) {
// A suggestion was found.
// Click on the first item in the list.
$suggestion->click();
} else {
// Press the return key to create a new tag.
behat_base::type_keys($this->session, [behat_keys::ENTER]);
}
$this->wait_for_pending_js();
// Press the escape to close the autocomplete suggestions list.
behat_base::type_keys($this->session, [behat_keys::ESCAPE]);
$this->wait_for_pending_js();
$this->add_value(trim($value), $allowscreation);
}
}
/**
* Add a value to the autocomplete.
*
* @param string $value
* @param bool $allowscreation
*/
protected function add_value(string $value, bool $allowscreation): void {
$value = trim($value);
// Click into the field.
$this->field->click();
// Remove any existing text.
do {
behat_base::type_keys($this->session, [behat_keys::BACKSPACE, behat_keys::DELETE]);
} while (strlen($this->field->getValue()) > 0);
$this->wait_for_pending_js();
// Type in the new value.
behat_base::type_keys($this->session, str_split($value));
$this->wait_for_pending_js();
// If the autocomplete found suggestions, then it will have:
// 1) marked itself as expanded; and
// 2) have an aria-selected suggestion in the list.
$expanded = $this->field->getAttribute('aria-expanded');
$suggestion = $this->field->getParent()->getParent()->find('css', '.form-autocomplete-suggestions > [aria-selected="true"]');
if ($expanded && null !== $suggestion) {
// A suggestion was found.
// Click on the first item in the list.
$suggestion->click();
} else if ($allowscreation) {
// Press the return key to create a new entry.
behat_base::type_keys($this->session, [behat_keys::ENTER]);
} else {
throw new \InvalidArgumentException(
"Unable to find '{$value}' in the list of options, and unable to create a new option"
);
}
$this->wait_for_pending_js();
// Press the escape to close the autocomplete suggestions list.
behat_base::type_keys($this->session, [behat_keys::ESCAPE]);
$this->wait_for_pending_js();
}
}
+1 -1
View File
@@ -473,7 +473,7 @@ class client extends \oauth2_client {
// Store the access token and, if provided by the server, the new refresh token.
$this->store_token($receivedtokens['access_token']);
if (isset($receivedtokens['refreshtoken'])) {
if (isset($receivedtokens['refresh_token'])) {
$systemaccount->set('refreshtoken', $receivedtokens['refresh_token']->token);
$systemaccount->update();
}
+3 -3
View File
@@ -182,7 +182,7 @@ function cron_run_adhoc_tasks(int $timenow, $keepalive = 0, $checklimits = true)
try {
$task = \core\task\manager::get_next_adhoc_task(time(), $checklimits);
} catch (Exception $e) {
} catch (\Throwable $e) {
if ($adhoclock) {
// Release the adhoc task runner lock.
$adhoclock->release();
@@ -260,7 +260,7 @@ function cron_run_inner_scheduled_task(\core\task\task_base $task) {
}
mtrace('Scheduled task complete: ' . $fullname);
\core\task\manager::scheduled_task_complete($task);
} catch (Exception $e) {
} catch (\Throwable $e) {
if ($DB && $DB->is_transaction_started()) {
error_log('Database transaction aborted automatically in ' . get_class($task));
$DB->force_transaction_rollback();
@@ -348,7 +348,7 @@ function cron_run_inner_adhoc_task(\core\task\adhoc_task $task) {
}
mtrace("Adhoc task complete: " . get_class($task));
\core\task\manager::adhoc_task_complete($task);
} catch (Exception $e) {
} catch (\Throwable $e) {
if ($DB && $DB->is_transaction_started()) {
error_log('Database transaction aborted automatically in ' . get_class($task));
$DB->force_transaction_rollback();
+1 -1
View File
@@ -1795,7 +1795,7 @@ abstract class moodle_database {
* If the return ID isn't required, then this just reports success as true/false.
* $data is an object containing needed data
* @param string $table The database table to be inserted into
* @param object $dataobject A data object with values for one or more fields in the record
* @param object|array $dataobject A data object with values for one or more fields in the record
* @param bool $returnid Should the id of the newly created record entry be returned? If this option is not requested then true/false is returned.
* @param bool $bulk Set to true is multiple inserts are expected
* @return bool|int true or new id
+1 -1
View File
@@ -1374,7 +1374,7 @@ class mysqli_native_moodle_database extends moodle_database {
* If the return ID isn't required, then this just reports success as true/false.
* $data is an object containing needed data
* @param string $table The database table to be inserted into
* @param object $data A data object with values for one or more fields in the record
* @param object|array $dataobject A data object with values for one or more fields in the record
* @param bool $returnid Should the id of the newly created record entry be returned? If this option is not requested then true/false is returned.
* @return bool|int true or new id
* @throws dml_exception A DML specific exception is thrown for any errors.
+1 -1
View File
@@ -1284,7 +1284,7 @@ class oci_native_moodle_database extends moodle_database {
* If the return ID isn't required, then this just reports success as true/false.
* $data is an object containing needed data
* @param string $table The database table to be inserted into
* @param object $data A data object with values for one or more fields in the record
* @param object|array $dataobject A data object with values for one or more fields in the record
* @param bool $returnid Should the id of the newly created record entry be returned? If this option is not requested then true/false is returned.
* @return bool|int true or new id
* @throws dml_exception A DML specific exception is thrown for any errors.
+1 -1
View File
@@ -390,7 +390,7 @@ abstract class pdo_moodle_database extends moodle_database {
* If the return ID isn't required, then this just reports success as true/false.
* $data is an object containing needed data
* @param string $table The database table to be inserted into
* @param object $data A data object with values for one or more fields in the record
* @param object|array $dataobject A data object with values for one or more fields in the record
* @param bool $returnid Should the id of the newly created record entry be returned? If this option is not requested then true/false is returned.
* @param bool $bulk true means repeated inserts expected
* @return bool|int true or new id
+1 -1
View File
@@ -1044,7 +1044,7 @@ class pgsql_native_moodle_database extends moodle_database {
* If the return ID isn't required, then this just reports success as true/false.
* $data is an object containing needed data
* @param string $table The database table to be inserted into
* @param object $data A data object with values for one or more fields in the record
* @param object|array $dataobject A data object with values for one or more fields in the record
* @param bool $returnid Should the id of the newly created record entry be returned? If this option is not requested then true/false is returned.
* @return bool|int true or new id
* @throws dml_exception A DML specific exception is thrown for any errors.
+1 -1
View File
@@ -1118,7 +1118,7 @@ class sqlsrv_native_moodle_database extends moodle_database {
* If the return ID isn't required, then this just reports success as true/false.
* $data is an object containing needed data
* @param string $table The database table to be inserted into
* @param object $data A data object with values for one or more fields in the record
* @param object|array $dataobject A data object with values for one or more fields in the record
* @param bool $returnid Should the id of the newly created record entry be returned? If this option is not requested then true/false is returned.
* @return bool|int true or new id
* @throws dml_exception A DML specific exception is thrown for any errors.
+1 -1
View File
@@ -249,7 +249,7 @@ abstract class test_moodle_database extends moodle_database {
/**
* Default implementation, throws Exception
* @param string $table
* @param StdObject $dataobject
* @param object|array $dataobject
* @param bool $returnid
* @param bool $bulk
* @return bool|int true or new id
@@ -38,14 +38,14 @@
{{{label}}}
{{/text}}
</label>
<span class="ml-2 d-flex align-items-center align-self-start">
<div class="ml-2 d-flex align-items-center align-self-start">
{{#required}}
<div class="text-danger" title="{{#str}}required{{/str}}">
{{#pix}}req, core, {{#str}}required{{/str}}{{/pix}}
</div>
{{/required}}
{{{helpbutton}}}
</span>
</div>
</div>
<div class="form-control-feedback invalid-feedback" id="{{element.iderror}}" {{#error}} style="display: block;"{{/error}}>
{{{error}}}
+2 -2
View File
@@ -38,14 +38,14 @@
{{{label}}}
{{/text}}
</label>
<span class="ml-2 d-flex align-items-center align-self-start">
<div class="ml-2 d-flex align-items-center align-self-start">
{{#required}}
<div class="text-danger" title="{{#str}}required{{/str}}">
{{#pix}}req, core, {{#str}}required{{/str}}{{/pix}}
</div>
{{/required}}
{{{helpbutton}}}
</span>
</div>
</div>
<div class="form-control-feedback invalid-feedback" id="{{element.iderror}}" {{#error}} style="display: block;"{{/error}}>
{{{error}}}
+2 -2
View File
@@ -38,14 +38,14 @@
{{{label}}}
{{/text}}
</label>
<span class="ml-2 d-flex align-items-center align-self-start">
<div class="ml-2 d-flex align-items-center align-self-start">
{{#required}}
<div class="text-danger" title="{{#str}}required{{/str}}">
{{#pix}}req, core, {{#str}}required{{/str}}{{/pix}}
</div>
{{/required}}
{{{helpbutton}}}
</span>
</div>
</div>
<div class="form-control-feedback invalid-feedback" id="{{element.iderror}}" {{#error}} style="display: block;"{{/error}}>
{{{error}}}
+2 -2
View File
@@ -56,14 +56,14 @@
</span>
{{/element.staticlabel}}
{{/ label }}{{/ label}}
<span class="ml-1 ml-md-auto d-flex align-items-center align-self-start">
<div class="ml-1 ml-md-auto d-flex align-items-center align-self-start">
{{#required}}
<div class="text-danger" title="{{#str}}required{{/str}}">
{{#pix}}req, core, {{#str}}required{{/str}}{{/pix}}
</div>
{{/required}}
{{{helpbutton}}}
</span>
</div>
</div>
<div class="col-md-9 form-inline align-items-start felement" data-fieldtype="{{element.type}}">
{{$ element }}
+24 -2
View File
@@ -37,13 +37,35 @@
}}
{{#showSuggestions}}
<div class="d-md-inline-block mr-md-2 position-relative">
<input type="text" id="{{inputId}}" class="form-control" list="{{suggestionsId}}" placeholder="{{placeholder}}" role="combobox" aria-expanded="false" autocomplete="off" autocorrect="off" autocapitalize="off" aria-autocomplete="list" aria-owns="{{suggestionsId}} {{selectionId}}" {{#tags}}data-tags="1"{{/tags}}/>
<input type="text"{{!
}} id="{{inputId}}"{{!
}} class="form-control"{{!
}} list="{{suggestionsId}}"{{!
}} placeholder="{{placeholder}}"{{!
}} role="combobox"{{!
}} aria-expanded="false"{{!
}} autocomplete="off"{{!
}} autocorrect="off"{{!
}} autocapitalize="off"{{!
}} aria-autocomplete="list"{{!
}} aria-owns="{{suggestionsId}} {{selectionId}}"{{!
}} {{#tags}}data-tags="1"{{/tags}}{{!
}} {{#multiple}}data-multiple="multiple"{{/multiple}}{{!
}}>
<span class="form-autocomplete-downarrow position-absolute p-1" id="{{downArrowId}}">&#x25BC;</span>
</div>
{{/showSuggestions}}
{{^showSuggestions}}
<div class="d-md-inline-block mr-md-2">
<input type="text" id="{{inputId}}" class="form-control" placeholder="{{placeholder}}" role="textbox" aria-owns="{{selectionId}}" {{#tags}}data-tags="1"{{/tags}}/>
<input type="text"{{!
}} id="{{inputId}}"{{!
}} class="form-control"{{!
}} placeholder="{{placeholder}}"{{!
}} role="textbox"{{!
}} aria-owns="{{selectionId}}"{{!
}} {{#tags}}data-tags="1"{{/tags}}{{!
}} {{#multiple}}data-multiple="multiple"{{/multiple}}{{!
}}>
</div>
{{/showSuggestions}}
@@ -120,16 +120,11 @@
{{^activepagenumber}}
data-active-page-number="1"
{{/activepagenumber}}
{{#showitemsperpageselector}}
{{#itemsperpage}}
{{#active}}
data-items-per-page="{{value}}"
{{/active}}
{{/itemsperpage}}
{{/showitemsperpageselector}}
{{^showitemsperpageselector}}
data-items-per-page="{{itemsperpage}}"
{{/showitemsperpageselector}}
{{#itemsperpage}}
{{#active}}
data-items-per-page="{{value}}"
{{/active}}
{{/itemsperpage}}
{{#arialabels.paginationnav}}
aria-label="{{.}}"
{{/arialabels.paginationnav}}
+1
View File
@@ -49,6 +49,7 @@
class="form-control withclear"
placeholder="{{$placeholder}}{{#str}} search, core {{/str}}{{/placeholder}}"
name="search"
value="{{$value}}{{/value}}"
autocomplete="off"
>
<button
+3 -3
View File
@@ -78,7 +78,7 @@
aria-expanded="false"
aria-controls="searchform-navbar"
>
<i class="icon fa fa-search fa-fw " aria-hidden="true"></i>
{{#pix}} a/search, core {{/pix}}
<span class="sr-only">{{#str}} togglesearch {{/str}}</span>
</a>
</div>
@@ -95,7 +95,7 @@ function(
var container = $('#searchinput-navbar-' + uniqid);
var opensearch = container.find('[data-action="opensearch"]');
var input = container.find('[data-region="input"]');
var submit = container.find('[data-action="submit"');
var submit = container.find('[data-action="submit"]');
submit.on('click', function(e) {
if (input.val() === '') {
@@ -113,4 +113,4 @@ function(
input.focus();
});
});
{{/js}}
{{/js}}
+8 -5
View File
@@ -693,18 +693,21 @@ class behat_forms extends behat_base {
$xpathtarget = "//ul[@class='form-autocomplete-suggestions']//*[contains(concat('|', string(.), '|'),'|" . $item . "|')]";
$this->execute('behat_general::i_click_on', [$xpathtarget, 'xpath_element']);
$this->execute('behat_general::i_press_key_in_element', ['13', 'body', 'xpath_element']);
}
/**
* Open the auto-complete suggestions list (Assuming there is only one on the page.).
*
* @Given /^I open the autocomplete suggestions list$/
* @Given I open the autocomplete suggestions list
* @Given I open the autocomplete suggestions list in the :container :containertype
*/
public function i_open_the_autocomplete_suggestions_list() {
public function i_open_the_autocomplete_suggestions_list($container = null, $containertype = null) {
$csstarget = ".form-autocomplete-downarrow";
$this->execute('behat_general::i_click_on', [$csstarget, 'css_element']);
if ($container && $containertype) {
$this->execute('behat_general::i_click_on', [$csstarget, 'css_element', $container, $containertype]);
} else {
$this->execute('behat_general::i_click_on', [$csstarget, 'css_element']);
}
}
/**
+10 -1
View File
@@ -328,7 +328,6 @@ EOF;
// The `before_subsequent_scenario_start_session` function will restart the session instead.
return;
}
self::$firstjavascriptscenarioseen = true;
$docsurl = behat_command::DOCS_URL;
$driverexceptionmsg = <<<EOF
@@ -475,6 +474,16 @@ EOF;
$this->resize_window('medium');
}
/**
* Mark the first Javascript Scenario as have been seen.
*
* @BeforeScenario
* @param BeforeScenarioScope $scope scope passed by event fired before scenario.
*/
public function mark_first_js_scenario_as_seen(BeforeScenarioScope $scope) {
self::$firstjavascriptscenarioseen = true;
}
/**
* Hook to open the site root before the first step in the suite.
* Yes, this is in a strange location and should be in the BeforeScenario hook, but failures in the test setUp lead
+1 -1
View File
@@ -17,7 +17,7 @@
define('NO_MOODLE_COOKIES', true); // Session not used here.
require('../../../config.php');
require('../lib.php');
require_once('../lib.php');
$chatsid = required_param('chat_sid', PARAM_ALPHANUM);
$chatlasttime = optional_param('chat_lasttime', 0, PARAM_INT);
+1 -1
View File
@@ -34,7 +34,7 @@ define('NO_MOODLE_COOKIES', true); // Session not used here.
define('NO_OUTPUT_BUFFERING', true);
require('../../../config.php');
require('../lib.php');
require_once('../lib.php');
// We are going to run for a long time.
// Avoid being terminated by php.
+1 -1
View File
@@ -17,7 +17,7 @@
define('NO_MOODLE_COOKIES', true); // Session not used here.
require('../../../config.php');
require('../lib.php');
require_once('../lib.php');
$chatsid = required_param('chat_sid', PARAM_ALPHANUM);
+53 -29
View File
@@ -31,25 +31,31 @@ class mod_data_export_form extends moodleform {
}
function definition() {
global $CFG;
$mform =& $this->_form;
$mform->addElement('header', 'notice', get_string('chooseexportformat', 'data'));
$choices = csv_import_reader::get_delimiter_list();
$key = array_search(';', $choices);
if (! $key === FALSE) {
// array $choices contains the semicolon -> drop it (because its encrypted form also contains a semicolon):
unset($choices[$key]);
}
$mform->addElement('header', 'exportformat', get_string('chooseexportformat', 'data'));
$optionattrs = ['class' => 'mt-1 mb-1'];
// Export format type radio group.
$typesarray = array();
$str = get_string('csvwithselecteddelimiter', 'data');
$typesarray[] = $mform->createElement('radio', 'exporttype', null, $str . '&nbsp;', 'csv');
$typesarray[] = $mform->createElement('select', 'delimiter_name', null, $choices);
//temporarily commenting out Excel export option. See MDL-19864
$typesarray[] = $mform->createElement('radio', 'exporttype', null, get_string('csvwithselecteddelimiter', 'data'), 'csv',
$optionattrs);
// Temporarily commenting out Excel export option. See MDL-19864.
//$typesarray[] = $mform->createElement('radio', 'exporttype', null, get_string('excel', 'data'), 'xls');
$typesarray[] = $mform->createElement('radio', 'exporttype', null, get_string('ods', 'data'), 'ods');
$mform->addGroup($typesarray, 'exportar', '', array(''), false);
$typesarray[] = $mform->createElement('radio', 'exporttype', null, get_string('ods', 'data'), 'ods', $optionattrs);
$mform->addGroup($typesarray, 'exportar', get_string('exportformat', 'data'), null, false);
$mform->addRule('exportar', null, 'required');
$mform->setDefault('exporttype', 'csv');
// CSV delimiter list.
$choices = csv_import_reader::get_delimiter_list();
$key = array_search(';', $choices);
if ($key !== false) {
// Array $choices contains the semicolon -> drop it (because its encrypted form also contains a semicolon):
unset($choices[$key]);
}
$mform->addElement('select', 'delimiter_name', get_string('fielddelimiter', 'data'), $choices);
$mform->hideIf('delimiter_name', 'exporttype', 'neq', 'csv');
if (array_key_exists('cfg', $choices)) {
$mform->setDefault('delimiter_name', 'cfg');
} else if (get_string('listsep', 'langconfig') == ';') {
@@ -57,38 +63,56 @@ class mod_data_export_form extends moodleform {
} else {
$mform->setDefault('delimiter_name', 'comma');
}
$mform->addElement('header', 'notice', get_string('chooseexportfields', 'data'));
// Fields to be exported.
$mform->addElement('header', 'exportfieldsheader', get_string('chooseexportfields', 'data'));
$mform->setExpanded('exportfieldsheader');
$numfieldsthatcanbeselected = 0;
foreach($this->_datafields as $field) {
if($field->text_export_supported()) {
$exportfields = [];
$unsupportedfields = [];
foreach ($this->_datafields as $field) {
$label = get_string('fieldnametype', 'data', (object)['name' => $field->field->name, 'type' => $field->name()]);
if ($field->text_export_supported()) {
$numfieldsthatcanbeselected++;
$html = '<div title="' . s($field->field->description) . '" ' .
'class="d-inline-block">' . $field->field->name . '</div>';
$name = ' (' . $field->name() . ')';
$mform->addElement('advcheckbox', 'field_' . $field->field->id, $html, $name, array('group' => 1));
$exportfields[] = $mform->createElement('advcheckbox', 'field_' . $field->field->id, '', $label,
array_merge(['group' => 1], $optionattrs));
$mform->setDefault('field_' . $field->field->id, 1);
} else {
$a = new stdClass();
$a->fieldtype = $field->name();
$str = get_string('unsupportedexport', 'data', $a);
$mform->addElement('static', 'unsupported' . $field->field->id, $field->field->name, $str);
$unsupportedfields[] = $label;
}
}
$mform->addGroup($exportfields, 'exportfields', get_string('selectfields', 'data'), ['<br>'], false);
if ($numfieldsthatcanbeselected > 1) {
$this->add_checkbox_controller(1, null, null, 1);
}
// List fields that cannot be exported.
if (!empty($unsupportedfields)) {
$unsupportedfieldslist = html_writer::tag('p', get_string('unsupportedfieldslist', 'data'), ['class' => 'mt-1']);
$unsupportedfieldslist .= html_writer::alist($unsupportedfields);
$mform->addElement('static', 'unsupportedfields', get_string('unsupportedfields', 'data'), $unsupportedfieldslist);
}
// Export options.
$mform->addElement('header', 'exportoptionsheader', get_string('exportoptions', 'data'));
$mform->setExpanded('exportoptionsheader');
$exportoptions = [];
if (core_tag_tag::is_enabled('mod_data', 'data_records')) {
$mform->addElement('checkbox', 'exporttags', get_string('includetags', 'data'));
$exportoptions[] = $mform->createElement('checkbox', 'exporttags', get_string('includetags', 'data'), '', $optionattrs);
$mform->setDefault('exporttags', 1);
}
$context = context_module::instance($this->_cm->id);
if (has_capability('mod/data:exportuserinfo', $context)) {
$mform->addElement('checkbox', 'exportuser', get_string('includeuserdetails', 'data'));
$exportoptions[] = $mform->createElement('checkbox', 'exportuser', get_string('includeuserdetails', 'data'), '',
$optionattrs);
}
$mform->addElement('checkbox', 'exporttime', get_string('includetime', 'data'));
$exportoptions[] = $mform->createElement('checkbox', 'exporttime', get_string('includetime', 'data'), '', $optionattrs);
if ($this->_data->approval) {
$mform->addElement('checkbox', 'exportapproval', get_string('includeapproval', 'data'));
$exportoptions[] = $mform->createElement('checkbox', 'exportapproval', get_string('includeapproval', 'data'), '',
$optionattrs);
}
$mform->addGroup($exportoptions, 'exportoptions', get_string('selectexportoptions', 'data'), ['<br>'], false);
$this->add_action_buttons(true, get_string('exportentries', 'data'));
}
+11 -4
View File
@@ -83,7 +83,7 @@ $string['csvfailed'] = 'Unable to read the raw data from the CSV file';
$string['csvfile'] = 'CSV file';
$string['csvimport'] = 'CSV file import';
$string['csvimport_help'] = 'Entries may be imported via a plain text file with a list of field names as the first line, then the data, with one record per line.';
$string['csvwithselecteddelimiter'] = '<acronym title="Comma Separated Values">CSV</acronym> text with selected delimiter:';
$string['csvwithselecteddelimiter'] = '<abbr title="Comma Separated Values">CSV</abbr>';
$string['data:addinstance'] = 'Add a new database';
$string['data:approve'] = 'Approve and undo approved entries';
$string['data:comment'] = 'Write comments';
@@ -153,6 +153,8 @@ $string['exportaszip'] = 'Export as zip';
$string['exportaszip_help'] = 'The export as zip feature allows you to save the templates and fields as a preset zip for download. The zip may then be imported to another course.';
$string['exportedtozip'] = 'Exported to temporary zip...';
$string['exportentries'] = 'Export entries';
$string['exportformat'] = 'Export format';
$string['exportoptions'] = 'Export options';
$string['exportownentries'] = 'Export your own entries only? ({$a->mine}/{$a->all})';
$string['failedpresetdelete'] = 'Error deleting a preset!';
$string['fieldadded'] = 'Field added';
@@ -169,6 +171,7 @@ $string['fieldmappings'] = 'Field mappings';
$string['fieldmappings_help'] = 'This menu allows you to keep the data from the existing database. To preserve the data in a field, you must map it to a new field, where the data will appear. Any field can also be left blank, with no information copied into it. Any old field not mapped to a new one will be lost and all its data removed.
You can only map fields of the same type, so each drop-down menu will have different fields in it. Also, you must be careful not to try and map one old field to more than one new field.';
$string['fieldname'] = 'Field name';
$string['fieldnametype'] = '{$a->name} ({$a->type})';
$string['fieldnotmatched'] = 'The following fields in your file are not known in this database: {$a}';
$string['fieldoptions'] = 'Options (one per line)';
$string['fields'] = 'Fields';
@@ -195,8 +198,8 @@ $string['headerlisttemplate'] = 'Defines browsing interface for multiple entries
$string['headerrsstemplate'] = 'Defines appearance of entries in RSS feeds';
$string['headersingletemplate'] = 'Defines browsing interface for a single entry';
$string['checkbox'] = 'Checkbox';
$string['chooseexportfields'] = 'Choose the fields you wish to export:';
$string['chooseexportformat'] = 'Choose the format you wish to export to:';
$string['chooseexportfields'] = 'Choose the fields you wish to export';
$string['chooseexportformat'] = 'Choose the format you wish to export to';
$string['chooseorupload'] = 'Choose file';
$string['expired'] = 'Sorry, this activity closed on {$a} and is no longer available';
$string['importentries'] = 'Import entries';
@@ -290,7 +293,7 @@ $string['number'] = 'Number';
$string['numberrssarticles'] = 'Entries in the RSS feed';
$string['numnotapproved'] = 'Pending';
$string['numrecords'] = '{$a} entries';
$string['ods'] = '<acronym title="OpenDocument Spreadsheet">ODS</acronym> (OpenOffice)';
$string['ods'] = '<abbr title="OpenDocument Spreadsheet">ODS</abbr>&nbsp;(OpenOffice)';
$string['openafterclose'] = 'You have specified an open date after the close date';
$string['optionaldescription'] = 'Short description (optional)';
$string['optionalfilename'] = 'Filename (optional)';
@@ -366,6 +369,8 @@ $string['search'] = 'Search';
$string['search:activity'] = 'Database - activity information';
$string['search:entry'] = 'Database - entries';
$string['selectedrequired'] = 'All selected required';
$string['selectfields'] = 'Select fields';
$string['selectexportoptions'] = 'Select export options';
$string['showall'] = 'Show all entries';
$string['single'] = 'View single';
$string['singletemplate'] = 'Single template';
@@ -387,6 +392,8 @@ $string['todatabase'] = 'to this database.';
$string['type'] = 'Field type';
$string['undefinedprocessactionmethod'] = 'No action method defined in Data_Preset to handle action "{$a}".';
$string['unsupportedexport'] = '({$a->fieldtype}) cannot be exported.';
$string['unsupportedfields'] = 'Unsupported fields';
$string['unsupportedfieldslist'] = 'The following fields cannot be exported:';
$string['updatefield'] = 'Update an existing field';
$string['uploadfile'] = 'Upload file';
$string['uploadrecords'] = 'Upload entries from a file';
@@ -94,7 +94,6 @@ Feature: Users can view and search database entries
| Test field name | Student original entry tagged |
| Test field 2 name | Student original entry tagged 2 |
And I set the field with xpath "//div[@class='datatagcontrol']//input[@type='text']" to "Tag1"
And I click on "[data-value='Tag1']" "css_element"
And I press "Save and view"
And I should see "Student original entry"
And I should see "Tag1" in the "div.tag_list" "css_element"
@@ -136,8 +136,7 @@ Feature: Mapping courses in a feedback
And I am on site homepage
And I follow "Course feedback"
And I follow "Map feedback to courses"
And I set the field "Courses" to "Course 2"
And I set the field "Courses" to "Course 3"
And I set the field "Courses" to "Course 2, Course 3"
And I press "Save changes"
And I should see "Course mapping has been changed"
And I log out
@@ -224,7 +223,6 @@ Feature: Mapping courses in a feedback
And I should see "1 (33.33 %)" in the "option d" "table_row"
And I should see "2 (66.67 %)" in the "option e" "table_row"
And I should see "0" in the "option f" "table_row"
And I log out
Scenario: Site feedback deletion hides feedback block completely
When I log in as "manager"
@@ -239,4 +237,3 @@ Feature: Mapping courses in a feedback
Then "Feedback" "block" should not exist
And I am on "Course 1" course homepage
And "Feedback" "block" should not exist
And I log out
@@ -130,7 +130,6 @@ Feature: The forum search allows users to perform advanced searches for forum po
And I press "Search forums"
And I should see "Advanced search"
And I set the field "Is tagged with" to "SearchedTag"
And I press the enter key
When I press "Search forums"
Then I should see "My subject"
And I should not see "Your subjective"
+1 -1
View File
@@ -1,2 +1,2 @@
function asyncGeneratorStep(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function _asyncToGenerator(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var h=a.apply(b,c);function f(a){asyncGeneratorStep(h,d,e,f,g,"next",a)}function g(a){asyncGeneratorStep(h,d,e,f,g,"throw",a)}f(void 0)})}}define ("mod_lti/contentitem",["jquery","core/notification","core/str","core/templates","mod_lti/form-field","core/modal_factory","core/modal_events"],function(a,b,c,d,e,f,g){var h,i,j=[new e("name",e.TYPES.TEXT,!1,""),new e("introeditor",e.TYPES.EDITOR,!1,""),new e("toolurl",e.TYPES.TEXT,!0,""),new e("securetoolurl",e.TYPES.TEXT,!0,""),new e("instructorchoiceacceptgrades",e.TYPES.CHECKBOX,!0,!0),new e("instructorchoicesendname",e.TYPES.CHECKBOX,!0,!0),new e("instructorchoicesendemailaddr",e.TYPES.CHECKBOX,!0,!0),new e("instructorcustomparameters",e.TYPES.TEXT,!0,""),new e("icon",e.TYPES.TEXT,!0,""),new e("secureicon",e.TYPES.TEXT,!0,""),new e("launchcontainer",e.TYPES.SELECT,!0,0),new e("grade_modgrade_point",e.TYPES.TEXT,!1,""),new e("lineitemresourceid",e.TYPES.TEXT,!0,""),new e("lineitemtag",e.TYPES.TEXT,!0,"")],k=function(a){a.setAttribute("hidden","true");a.setAttribute("aria-hidden","true");a.setAttribute("tab-index","-1")},l=function(a){a.removeAttribute("hidden");a.setAttribute("aria-hidden","false");a.setAttribute("tab-index","1")},m=function(){var a=_asyncToGenerator(regeneratorRuntime.mark(function a(b){var c,e,f,g,h,i,j;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:c=document.querySelector("#region-main-box form");e=c.querySelector("[data-attribute=\"dynamic-import\"]");f=c.querySelector("#fgroup_id_buttonar");g=c.querySelector("#id_submitbutton");Array.from(c.children).forEach(k);k(g);a.next=8;return d.renderForPromise("mod_lti/tool_deeplinking_results",{items:b});case 8:h=a.sent;i=h.html;j=h.js;a.next=13;return d.replaceNodeContents(e,i,j);case 13:l(e);l(f);case 15:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}(),n=function(a){var b={};["name","toolurl","securetoolurl","instructorcustomparameters","icon","secureicon","launchcontainer"].forEach(function(c){b[c]=a[c]||""});b["introeditor[text]"]=a.introeditor?a.introeditor.text:"";b["introeditor[format]"]=a.introeditor?a.introeditor.format:"";if(1===a.instructorchoiceacceptgrades){b.instructorchoiceacceptgrades="1";b["grade[modgrade_point]"]=a.grade_modgrade_point||"100"}else{b.instructorchoiceacceptgrades="0"}return b};window.processContentItemReturnData=function(a){if(h){h.hide()}if(a.multiple){for(var b in j){j[b].setFieldValue("name"===j[b].name?"item":null)}var c=[];a.multiple.forEach(function(a){c.push(n(a))});m(a.multiple);var f=document.querySelector("#id_submitbutton2");f.onclick=function(a){a.preventDefault();f.disabled=!0;var b=new FormData(document.querySelector("form.mform")),d=function(){document.querySelector("#id_cancel").click()};c.reduce(function postVariant(a,c){Object.entries(c).forEach(function(a){return b.set(a[0],a[1])});var d=new URLSearchParams(b),e=function(){return fetch(document.location.pathname,{method:"post",body:d})};return a.then(e).catch(e)},Promise.resolve()).then(d).catch(d)}}else{for(b in j){var d=j[b],e=null;if("undefined"!=typeof a[d.name]){e=a[d.name]}d.setFieldValue(e)}d.setFieldValue(e)}if(i){i(a)}};return{init:function init(a,e,j){i=j;var k=d.render("mod_lti/contentitem",{url:a,postData:e});if(h){h.setBody(k);h.show();return}c.get_string("selectcontent","lti").then(function(a){return f.create({title:a,body:k,large:!0})}).then(function(a){h=a;a.getRoot().on(g.hidden,function(){a.setBody("");b.fetchNotifications()});a.show()}).catch(b.exception)}}});
function asyncGeneratorStep(a,b,c,d,e,f,g){try{var h=a[f](g),i=h.value}catch(a){c(a);return}if(h.done){b(i)}else{Promise.resolve(i).then(d,e)}}function _asyncToGenerator(a){return function(){var b=this,c=arguments;return new Promise(function(d,e){var h=a.apply(b,c);function f(a){asyncGeneratorStep(h,d,e,f,g,"next",a)}function g(a){asyncGeneratorStep(h,d,e,f,g,"throw",a)}f(void 0)})}}define ("mod_lti/contentitem",["jquery","core/notification","core/str","core/templates","mod_lti/form-field","core/modal_factory","core/modal_events"],function(a,b,c,d,e,f,g){var h,i,j=[new e("name",e.TYPES.TEXT,!1,""),new e("introeditor",e.TYPES.EDITOR,!1,""),new e("toolurl",e.TYPES.TEXT,!0,""),new e("securetoolurl",e.TYPES.TEXT,!0,""),new e("instructorchoiceacceptgrades",e.TYPES.CHECKBOX,!0,!0),new e("instructorchoicesendname",e.TYPES.CHECKBOX,!0,!0),new e("instructorchoicesendemailaddr",e.TYPES.CHECKBOX,!0,!0),new e("instructorcustomparameters",e.TYPES.TEXT,!0,""),new e("icon",e.TYPES.TEXT,!0,""),new e("secureicon",e.TYPES.TEXT,!0,""),new e("launchcontainer",e.TYPES.SELECT,!0,0),new e("grade_modgrade_point",e.TYPES.TEXT,!1,""),new e("lineitemresourceid",e.TYPES.TEXT,!0,""),new e("lineitemtag",e.TYPES.TEXT,!0,"")],k=function(a){a.setAttribute("hidden","true");a.setAttribute("aria-hidden","true");a.setAttribute("tab-index","-1")},l=function(a){a.removeAttribute("hidden");a.setAttribute("aria-hidden","false");a.setAttribute("tab-index","1")},m=function(){var a=_asyncToGenerator(regeneratorRuntime.mark(function a(b){var c,e,f,g,h,i,j;return regeneratorRuntime.wrap(function(a){while(1){switch(a.prev=a.next){case 0:c=document.querySelector("#region-main-box form");e=c.querySelector("[data-attribute=\"dynamic-import\"]");f=c.querySelector("#fgroup_id_buttonar");g=c.querySelector("#id_submitbutton");Array.from(c.children).forEach(k);k(g);a.next=8;return d.renderForPromise("mod_lti/tool_deeplinking_results",{items:b});case 8:h=a.sent;i=h.html;j=h.js;a.next=13;return d.replaceNodeContents(e,i,j);case 13:l(e);l(f);case 15:case"end":return a.stop();}}},a)}));return function(){return a.apply(this,arguments)}}(),n=function(a){var b={};["name","toolurl","securetoolurl","instructorcustomparameters","icon","secureicon","launchcontainer","lineitemresourceid","lineitemtag"].forEach(function(c){b[c]=a[c]||""});b["introeditor[text]"]=a.introeditor?a.introeditor.text:"";b["introeditor[format]"]=a.introeditor?a.introeditor.format:"";if(1===a.instructorchoiceacceptgrades){b.instructorchoiceacceptgrades="1";b["grade[modgrade_point]"]=a.grade_modgrade_point||"100"}else{b.instructorchoiceacceptgrades="0"}return b};window.processContentItemReturnData=function(a){if(h){h.hide()}if(a.multiple){for(var b in j){j[b].setFieldValue("name"===j[b].name?"item":null)}var c=[];a.multiple.forEach(function(a){c.push(n(a))});m(a.multiple);var f=document.querySelector("#id_submitbutton2");f.onclick=function(a){a.preventDefault();f.disabled=!0;var b=new FormData(document.querySelector("form.mform")),d=function(){document.querySelector("#id_cancel").click()};c.reduce(function postVariant(a,c){Object.entries(c).forEach(function(a){return b.set(a[0],a[1])});var d=new URLSearchParams(b),e=function(){return fetch(document.location.pathname,{method:"post",body:d})};return a.then(e).catch(e)},Promise.resolve()).then(d).catch(d)}}else{for(b in j){var d=j[b],e=null;if("undefined"!=typeof a[d.name]){e=a[d.name]}d.setFieldValue(e)}d.setFieldValue(e)}if(i){i(a)}};return{init:function init(a,e,j){i=j;var k=d.render("mod_lti/contentitem",{url:a,postData:e});if(h){h.setBody(k);h.show();return}c.get_string("selectcontent","lti").then(function(a){return f.create({title:a,body:k,large:!0})}).then(function(a){h=a;a.getRoot().on(g.hidden,function(){a.setBody("");b.fetchNotifications()});a.show()}).catch(b.exception)}}});
//# sourceMappingURL=contentitem.min.js.map
File diff suppressed because one or more lines are too long
+2 -1
View File
@@ -163,7 +163,8 @@ define(
*/
var configToVariant = (config) => {
const variant = {};
['name', 'toolurl', 'securetoolurl', 'instructorcustomparameters', 'icon', 'secureicon', 'launchcontainer'].forEach(
['name', 'toolurl', 'securetoolurl', 'instructorcustomparameters', 'icon', 'secureicon',
'launchcontainer', 'lineitemresourceid', 'lineitemtag'].forEach(
function(name) {
variant[name] = config[name] || '';
}
@@ -101,14 +101,23 @@ class registration_helper {
$ltitoolconfiguration = self::get_parameter($registrationpayload,
'https://purl.imsglobal.org/spec/lti-tool-configuration', true);
$domain = self::get_parameter($ltitoolconfiguration, 'domain', true);
$targetlinkuri = self::get_parameter($ltitoolconfiguration, 'target_link_uri', true);
$domain = self::get_parameter($ltitoolconfiguration, 'domain', false);
$targetlinkuri = self::get_parameter($ltitoolconfiguration, 'target_link_uri', false);
$customparameters = self::get_parameter($ltitoolconfiguration, 'custom_parameters', false);
$scopes = explode(" ", self::get_parameter($registrationpayload, 'scope', false) ?? '');
$claims = self::get_parameter($ltitoolconfiguration, 'claims', false);
$messages = $ltitoolconfiguration['messages'] ?? [];
$description = self::get_parameter($ltitoolconfiguration, 'description', false);
// Validate domain and target link.
if (empty($domain)) {
throw new registration_exception('missing_domain', 400);
}
$targetlinkuri = $targetlinkuri ?: 'https://'.$domain;
if ($domain !== lti_get_domain_from_url($targetlinkuri)) {
throw new registration_exception('domain_targetlinkuri_mismatch', 400);
}
// Validate response type.
// According to specification, for this scenario, id_token must be explicitly set.
if (!in_array('id_token', $responsetypes)) {
@@ -245,7 +254,7 @@ class registration_helper {
$registrationresponse['initiate_login_uri'] = $config->lti_initiatelogin;
$registrationresponse['grant_types'] = ['client_credentials', 'implicit'];
$registrationresponse['redirect_uris'] = explode(PHP_EOL, $config->lti_redirectionuris);
$registrationresponse['application_type'] = ['web'];
$registrationresponse['application_type'] = 'web';
$registrationresponse['token_endpoint_auth_method'] = 'private_key_jwt';
$registrationresponse['client_name'] = $config->lti_typename;
$registrationresponse['logo_uri'] = $config->lti_icon ?? '';
@@ -288,12 +297,12 @@ class registration_helper {
$registrationresponse['scope'] = implode(' ', $scopesresponse);
$claimsresponse = ['sub', 'iss'];
if ($config->lti_sendname = LTI_SETTING_ALWAYS) {
if ($config->lti_sendname == LTI_SETTING_ALWAYS) {
$claimsresponse[] = 'name';
$claimsresponse[] = 'family_name';
$claimsresponse[] = 'middle_name';
$claimsresponse[] = 'given_name';
}
if ($config->lti_sendemailaddr = LTI_SETTING_ALWAYS) {
if ($config->lti_sendemailaddr == LTI_SETTING_ALWAYS) {
$claimsresponse[] = 'email';
}
$lticonfigurationresponse['claims'] = $claimsresponse;
-2
View File
@@ -1525,8 +1525,6 @@ function content_item_to_form(object $tool, object $typeconfig, object $item) :
}
$config->instructorcustomparameters = implode("\n", $customparameters);
}
// Including a JSON version of the form data to support adding many items in one submit.
$config->contentitemjson = json_encode($item);
return $config;
}
+1 -1
View File
@@ -51,7 +51,7 @@ $conf = [
'https://purl.imsglobal.org/spec/lti-platform-configuration' => [
'product_family_code' => 'moodle',
'version' => $CFG->release,
'messages_supported' => ['LtiResourceLink', 'LtiDeepLinkingRequest'],
'messages_supported' => ['LtiResourceLinkRequest', 'LtiDeepLinkingRequest'],
'placements' => ['AddContentMenu'],
'variables' => array_keys(lti_get_capabilities())
]
+76
View File
@@ -162,6 +162,8 @@ EOD;
$this->assertEquals(LTI_SETTING_DELEGATE, $config->lti_acceptgrades);
$this->assertEquals(1, $config->ltiservice_memberships);
$this->assertEquals(0, $config->ltiservice_toolsettings);
$this->assertEquals('client.example.org', $config->lti_tooldomain);
$this->assertEquals('https://client.example.org/lti', $config->lti_toolurl);
$this->assertEquals(LTI_SETTING_ALWAYS, $config->lti_sendname);
$this->assertEquals(LTI_SETTING_ALWAYS, $config->lti_sendemailaddr);
$this->assertEquals(1, $config->lti_contentitem);
@@ -236,6 +238,51 @@ EOD;
registration_helper::registration_to_config($registration, 'TheClientId');
}
/**
* Validation Test: no domain nor targetlinkuri is rejected.
*/
public function test_validation_missing_domain_targetlinkuri() {
$registration = json_decode($this->registrationminimaljson, true);
$this->expectException(registration_exception::class);
$this->expectExceptionCode(400);
unset($registration['https://purl.imsglobal.org/spec/lti-tool-configuration']['domain']);
unset($registration['https://purl.imsglobal.org/spec/lti-tool-configuration']['target_link_uri']);
registration_helper::registration_to_config($registration, 'TheClientId');
}
/**
* Validation Test: mismatch between domain and targetlinkuri is rejected.
*/
public function test_validation_domain_targetlinkuri_match() {
$registration = json_decode($this->registrationminimaljson, true);
$this->expectException(registration_exception::class);
$this->expectExceptionCode(400);
$registration['https://purl.imsglobal.org/spec/lti-tool-configuration']['domain'] = 'not.the.right.domain';
registration_helper::registration_to_config($registration, 'TheClientId');
}
/**
* Validation Test: domain is required.
*/
public function test_validation_domain_targetlinkuri_onlylink() {
$registration = json_decode($this->registrationminimaljson, true);
unset($registration['https://purl.imsglobal.org/spec/lti-tool-configuration']['domain']);
$this->expectException(registration_exception::class);
$this->expectExceptionCode(400);
$config = registration_helper::registration_to_config($registration, 'TheClientId');
}
/**
* Validation Test: base url (targetlinkuri) is built from domain if not present.
*/
public function test_validation_domain_targetlinkuri_onlydomain() {
$registration = json_decode($this->registrationminimaljson, true);
unset($registration['https://purl.imsglobal.org/spec/lti-tool-configuration']['target_link_uri']);
$config = registration_helper::registration_to_config($registration, 'TheClientId');
$this->assertEquals('client.example.org', $config->lti_tooldomain);
$this->assertEquals('https://client.example.org', $config->lti_toolurl);
}
/**
* Test the transformation from lti config to OpenId LTI Client Registration response.
*/
@@ -265,5 +312,34 @@ EOD;
$dlmsg = $lti['messages'][0];
$this->assertEquals($dlmsgorig['type'], $dlmsg['type']);
$this->assertEquals($dlmsgorig['target_link_uri'], $dlmsg['target_link_uri']);
$this->assertTrue(in_array('iss', $lti['claims']));
$this->assertTrue(in_array('sub', $lti['claims']));
$this->assertTrue(in_array('email', $lti['claims']));
$this->assertTrue(in_array('family_name', $lti['claims']));
$this->assertTrue(in_array('given_name', $lti['claims']));
$this->assertTrue(in_array('name', $lti['claims']));
}
/**
* Test the transformation from lti config to OpenId LTI Client Registration response for the minimal version.
*/
public function test_config_to_registration_minimal() {
$orig = json_decode($this->registrationminimaljson, true);
$reg = registration_helper::config_to_registration(registration_helper::registration_to_config($orig, 'clid'), 12);
$this->assertEquals('clid', $reg['client_id']);
$this->assertEquals($orig['response_types'], $reg['response_types']);
$this->assertEquals($orig['initiate_login_uri'], $reg['initiate_login_uri']);
$this->assertEquals($orig['redirect_uris'], $reg['redirect_uris']);
$this->assertEquals($orig['jwks_uri'], $reg['jwks_uri']);
$this->assertEquals('', $reg['scope']);
$ltiorig = $orig['https://purl.imsglobal.org/spec/lti-tool-configuration'];
$lti = $reg['https://purl.imsglobal.org/spec/lti-tool-configuration'];
$this->assertTrue(in_array('iss', $lti['claims']));
$this->assertTrue(in_array('sub', $lti['claims']));
$this->assertFalse(in_array('email', $lti['claims']));
$this->assertFalse(in_array('family_name', $lti['claims']));
$this->assertFalse(in_array('given_name', $lti['claims']));
$this->assertFalse(in_array('name', $lti['claims']));
}
}
+17 -4
View File
@@ -2002,7 +2002,7 @@ class quiz_attempt {
// Transition to the appropriate state.
switch ($this->quizobj->get_quiz()->overduehandling) {
case 'autosubmit':
$this->process_finish($timestamp, false);
$this->process_finish($timestamp, false, $studentisonline ? $timestamp : $timeclose);
return;
case 'graceperiod':
@@ -2160,7 +2160,20 @@ class quiz_attempt {
$transaction->allow_commit();
}
public function process_finish($timestamp, $processsubmitted) {
/**
* Submit the attempt.
*
* The separate $timefinish argument should be used when the quiz attempt
* is being processed asynchronously (for example when cron is submitting
* attempts where the time has expired).
*
* @param int $timestamp the time to record as last modified time.
* @param bool $processsubmitted if true, and question responses in the current
* POST request are stored to be graded, before the attempt is finished.
* @param ?int $timefinish if set, use this as the finish time for the attempt.
* (otherwise use $timestamp as the finish time as well).
*/
public function process_finish($timestamp, $processsubmitted, $timefinish = null) {
global $DB;
$transaction = $DB->start_delegated_transaction();
@@ -2173,7 +2186,7 @@ class quiz_attempt {
question_engine::save_questions_usage_by_activity($this->quba);
$this->attempt->timemodified = $timestamp;
$this->attempt->timefinish = $timestamp;
$this->attempt->timefinish = $timefinish ?? $timestamp;
$this->attempt->sumgrades = $this->quba->get_total_mark();
$this->attempt->state = self::FINISHED;
$this->attempt->timecheckstate = null;
@@ -2440,7 +2453,7 @@ class quiz_attempt {
if ($becomingabandoned) {
$this->process_abandon($timenow, true);
} else {
$this->process_finish($timenow, !$toolate);
$this->process_finish($timenow, !$toolate, $toolate ? $timeclose : $timenow);
}
} catch (question_out_of_sequence_exception $e) {
-3
View File
@@ -81,9 +81,6 @@ class mod_quiz_attempt_testcase extends advanced_testcase {
return quiz_attempt::create($attempt->id);
}
/**
* Test the functions quiz_update_open_attempts() and get_list_of_overdue_attempts()
*/
public function test_attempt_url() {
$attempt = $this->create_quiz_and_attempt_with_layout('1,2,0,3,4,0,5,6,0');
+361 -207
View File
@@ -37,8 +37,10 @@ require_once($CFG->dirroot.'/group/lib.php');
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mod_quiz_attempt_overdue_testcase extends advanced_testcase {
/**
* Test the functions quiz_update_open_attempts() and get_list_of_overdue_attempts()
* Test the functions quiz_update_open_attempts(), get_list_of_overdue_attempts() and
* update_overdue_attempts().
*/
public function test_bulk_update_functions() {
global $DB,$CFG;
@@ -53,245 +55,342 @@ class mod_quiz_attempt_overdue_testcase extends advanced_testcase {
$course = $this->getDataGenerator()->create_course();
$user1 = $this->getDataGenerator()->create_user();
$studentrole = $DB->get_record('role', array('shortname'=>'student'));
$studentrole = $DB->get_record('role', ['shortname' => 'student']);
$this->assertNotEmpty($studentrole);
$this->assertTrue(enrol_try_internal_enrol($course->id, $user1->id, $studentrole->id));
$group1 = $this->getDataGenerator()->create_group(array('courseid'=>$course->id));
$group2 = $this->getDataGenerator()->create_group(array('courseid'=>$course->id));
$group3 = $this->getDataGenerator()->create_group(array('courseid'=>$course->id));
$group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
$group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
$group3 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
$this->assertTrue(groups_add_member($group1, $user1));
$this->assertTrue(groups_add_member($group2, $user1));
$uniqueid = 0;
$usertimes = array();
$usertimes = [];
$quiz_generator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
/** @var mod_quiz_generator $quizgenerator */
$quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
// Basic quiz settings
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>600));
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
$usertimes[$attemptid] = array('timeclose'=>1200, 'timelimit'=>600, 'message'=>'Test1A');
$quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
$usertimes[$attemptid] = ['timeclose' => 1200, 'timelimit' => 600, 'message' => 'Test1A', 'time1000state' => 'finished'];
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>1800));
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
$usertimes[$attemptid] = array('timeclose'=>1200, 'timelimit'=>1800, 'message'=>'Test1B');
$quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 1800]);
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
$usertimes[$attemptid] = ['timeclose' => 1200, 'timelimit' => 1800, 'message' => 'Test1B', 'time1000state' => 'inprogress'];
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>0));
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
$usertimes[$attemptid] = array('timeclose'=>1200, 'timelimit'=>0, 'message'=>'Test1C');
$quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 0]);
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
$usertimes[$attemptid] = ['timeclose' => 1200, 'timelimit' => 0, 'message' => 'Test1C', 'time1000state' => 'inprogress'];
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>0, 'timelimit'=>600));
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
$usertimes[$attemptid] = array('timeclose'=>0, 'timelimit'=>600, 'message'=>'Test1D');
$quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 0, 'timelimit' => 600]);
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
$usertimes[$attemptid] = ['timeclose' => 0, 'timelimit' => 600, 'message' => 'Test1D', 'time1000state' => 'finished'];
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>0, 'timelimit'=>0));
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
$usertimes[$attemptid] = array('timeclose'=>0, 'timelimit'=>0, 'message'=>'Test1E');
$quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 0, 'timelimit' => 0]);
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
$usertimes[$attemptid] = ['timeclose' => 0, 'timelimit' => 0, 'message' => 'Test1E', 'time1000state' => 'inprogress'];
// Group overrides
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>0));
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>1300, 'timelimit'=>null));
$usertimes[$attemptid] = array('timeclose'=>1300, 'timelimit'=>0, 'message'=>'Test2A');
$quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 0]);
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
$DB->insert_record('quiz_overrides',
['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => null]);
$usertimes[$attemptid] = ['timeclose' => 1300, 'timelimit' => 0, 'message' => 'Test2A', 'time1000state' => 'inprogress'];
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>0));
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>1100, 'timelimit'=>null));
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
$usertimes[$attemptid] = array('timeclose'=>1100, 'timelimit'=>0, 'message'=>'Test2B');
$quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 0]);
$DB->insert_record('quiz_overrides',
['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1100, 'timelimit' => null]);
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
$usertimes[$attemptid] = ['timeclose' => 1100, 'timelimit' => 0, 'message' => 'Test2B', 'time1000state' => 'inprogress'];
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>0, 'timelimit'=>600));
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>null, 'timelimit'=>700));
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
$usertimes[$attemptid] = array('timeclose'=>0, 'timelimit'=>700, 'message'=>'Test2C');
$quiz = $quizgenerator->create_instance(
['course' => $course->id, 'timeclose' => 0, 'timelimit' => 600, 'overduehandling' => 'autoabandon']);
$DB->insert_record('quiz_overrides',
['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => null, 'timelimit' => 700]);
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
$usertimes[$attemptid] = ['timeclose' => 0, 'timelimit' => 700, 'message' => 'Test2C', 'time1000state' => 'abandoned'];
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>0, 'timelimit'=>600));
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>null, 'timelimit'=>500));
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
$usertimes[$attemptid] = array('timeclose'=>0, 'timelimit'=>500, 'message'=>'Test2D');
$quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 0, 'timelimit' => 600]);
$DB->insert_record('quiz_overrides',
['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => null, 'timelimit' => 500]);
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
$usertimes[$attemptid] = ['timeclose' => 0, 'timelimit' => 500, 'message' => 'Test2D', 'time1000state' => 'finished'];
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>0, 'timelimit'=>600));
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>null, 'timelimit'=>0));
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
$usertimes[$attemptid] = array('timeclose'=>0, 'timelimit'=>0, '', 'message'=>'Test2E');
$quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 0, 'timelimit' => 600]);
$DB->insert_record('quiz_overrides',
['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => null, 'timelimit' => 0]);
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
$usertimes[$attemptid] = ['timeclose' => 0, 'timelimit' => 0, 'message' => 'Test2E', 'time1000state' => 'inprogress'];
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>600));
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>1300, 'timelimit'=>500));
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
$usertimes[$attemptid] = array('timeclose'=>1300, 'timelimit'=>500, '', 'message'=>'Test2F');
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>1000, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++, 'attempt'=>1));
$usertimes[$attemptid] = array('timeclose'=>1300, 'timelimit'=>500, '', 'message'=>'Test2G');
$quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
$DB->insert_record('quiz_overrides',
['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => 500]);
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
$usertimes[$attemptid] = ['timeclose' => 1300, 'timelimit' => 500, 'message' => 'Test2F', 'time1000state' => 'finished'];
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
$usertimes[$attemptid] = ['timeclose' => 1300, 'timelimit' => 500, 'message' => 'Test2G', 'time1000state' => 'inprogress'];
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>600));
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group3->id, 'timeclose'=>1300, 'timelimit'=>500)); // user not in group
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
$usertimes[$attemptid] = array('timeclose'=>1200, 'timelimit'=>600, '', 'message'=>'Test2H');
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>1000, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++, 'attempt'=>1));
$usertimes[$attemptid] = array('timeclose'=>1200, 'timelimit'=>600, '', 'message'=>'Test2I');
$quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
$DB->insert_record('quiz_overrides',
['quiz' => $quiz->id, 'groupid' => $group3->id, 'timeclose' => 1300, 'timelimit' => 500]); // User not in group.
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
$usertimes[$attemptid] = ['timeclose' => 1200, 'timelimit' => 600, 'message' => 'Test2H', 'time1000state' => 'finished'];
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
$usertimes[$attemptid] = ['timeclose' => 1200, 'timelimit' => 600, 'message' => 'Test2I', 'time1000state' => 'inprogress'];
// Multiple group overrides
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>600));
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>1300, 'timelimit'=>501));
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group2->id, 'timeclose'=>1301, 'timelimit'=>500));
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
$usertimes[$attemptid] = array('timeclose'=>1301, 'timelimit'=>501, '', 'message'=>'Test3A');
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>1000, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++, 'attempt'=>1));
$usertimes[$attemptid] = array('timeclose'=>1301, 'timelimit'=>501, '', 'message'=>'Test3B');
$quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
$DB->insert_record('quiz_overrides',
['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => 501]);
$DB->insert_record('quiz_overrides',
['quiz' => $quiz->id, 'groupid' => $group2->id, 'timeclose' => 1301, 'timelimit' => 500]);
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
$usertimes[$attemptid] = ['timeclose' => 1301, 'timelimit' => 501, 'message' => 'Test3A', 'time1000state' => 'finished'];
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
$usertimes[$attemptid] = ['timeclose' => 1301, 'timelimit' => 501, 'message' => 'Test3B', 'time1000state' => 'inprogress'];
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>600));
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>1301, 'timelimit'=>500));
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group2->id, 'timeclose'=>1300, 'timelimit'=>501));
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
$usertimes[$attemptid] = array('timeclose'=>1301, 'timelimit'=>501, '', 'message'=>'Test3C');
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>1000, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++, 'attempt'=>1));
$usertimes[$attemptid] = array('timeclose'=>1301, 'timelimit'=>501, '', 'message'=>'Test3D');
$quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
$DB->insert_record('quiz_overrides',
['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1301, 'timelimit' => 500]);
$DB->insert_record('quiz_overrides',
['quiz' => $quiz->id, 'groupid' => $group2->id, 'timeclose' => 1300, 'timelimit' => 501]);
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
$usertimes[$attemptid] = ['timeclose' => 1301, 'timelimit' => 501, 'message' => 'Test3C', 'time1000state' => 'finished'];
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
$usertimes[$attemptid] = ['timeclose' => 1301, 'timelimit' => 501, 'message' => 'Test3D', 'time1000state' => 'inprogress'];
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>600));
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>1301, 'timelimit'=>500));
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group2->id, 'timeclose'=>1300, 'timelimit'=>501));
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group3->id, 'timeclose'=>1500, 'timelimit'=>1000)); // user not in group
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
$usertimes[$attemptid] = array('timeclose'=>1301, 'timelimit'=>501, '', 'message'=>'Test3E');
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>1000, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++, 'attempt'=>1));
$usertimes[$attemptid] = array('timeclose'=>1301, 'timelimit'=>501, '', 'message'=>'Test3F');
$quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600,
'overduehandling' => 'autoabandon']);
$DB->insert_record('quiz_overrides',
['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1301, 'timelimit' => 500]);
$DB->insert_record('quiz_overrides',
['quiz' => $quiz->id, 'groupid' => $group2->id, 'timeclose' => 1300, 'timelimit' => 501]);
$DB->insert_record('quiz_overrides',
['quiz' => $quiz->id, 'groupid' => $group3->id, 'timeclose' => 1500, 'timelimit' => 1000]); // User not in group.
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
$usertimes[$attemptid] = ['timeclose' => 1301, 'timelimit' => 501, 'message' => 'Test3E', 'time1000state' => 'abandoned'];
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
$usertimes[$attemptid] = ['timeclose' => 1301, 'timelimit' => 501, 'message' => 'Test3F', 'time1000state' => 'inprogress'];
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>600));
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>1300, 'timelimit'=>500));
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group2->id, 'timeclose'=>null, 'timelimit'=>501));
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
$usertimes[$attemptid] = array('timeclose'=>1300, 'timelimit'=>501, '', 'message'=>'Test3G');
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>1000, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++, 'attempt'=>1));
$usertimes[$attemptid] = array('timeclose'=>1300, 'timelimit'=>501, '', 'message'=>'Test3H');
$quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
$DB->insert_record('quiz_overrides',
['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => 500]);
$DB->insert_record('quiz_overrides',
['quiz' => $quiz->id, 'groupid' => $group2->id, 'timeclose' => null, 'timelimit' => 501]);
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
$usertimes[$attemptid] = ['timeclose' => 1300, 'timelimit' => 501, 'message' => 'Test3G', 'time1000state' => 'finished'];
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
$usertimes[$attemptid] = ['timeclose' => 1300, 'timelimit' => 501, 'message' => 'Test3H', 'time1000state' => 'inprogress'];
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>600));
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>1300, 'timelimit'=>500));
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group2->id, 'timeclose'=>1301, 'timelimit'=>null));
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
$usertimes[$attemptid] = array('timeclose'=>1301, 'timelimit'=>500, '', 'message'=>'Test3I');
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>1000, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++, 'attempt'=>1));
$usertimes[$attemptid] = array('timeclose'=>1301, 'timelimit'=>500, '', 'message'=>'Test3J');
$quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
$DB->insert_record('quiz_overrides',
['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => 500]);
$DB->insert_record('quiz_overrides',
['quiz' => $quiz->id, 'groupid' => $group2->id, 'timeclose' => 1301, 'timelimit' => null]);
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
$usertimes[$attemptid] = ['timeclose' => 1301, 'timelimit' => 500, 'message' => 'Test3I', 'time1000state' => 'finished'];
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
$usertimes[$attemptid] = ['timeclose' => 1301, 'timelimit' => 500, 'message' => 'Test3J', 'time1000state' => 'inprogress'];
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>600));
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>1300, 'timelimit'=>500));
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group2->id, 'timeclose'=>1301, 'timelimit'=>0));
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
$usertimes[$attemptid] = array('timeclose'=>1301, 'timelimit'=>0, '', 'message'=>'Test3K');
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>1000, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++, 'attempt'=>1));
$usertimes[$attemptid] = array('timeclose'=>1301, 'timelimit'=>0, '', 'message'=>'Test3L');
$quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
$DB->insert_record('quiz_overrides',
['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => 500]);
$DB->insert_record('quiz_overrides',
['quiz' => $quiz->id, 'groupid' => $group2->id, 'timeclose' => 1301, 'timelimit' => 0]);
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
$usertimes[$attemptid] = ['timeclose' => 1301, 'timelimit' => 0, 'message' => 'Test3K', 'time1000state' => 'inprogress'];
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
$usertimes[$attemptid] = ['timeclose' => 1301, 'timelimit' => 0, 'message' => 'Test3L', 'time1000state' => 'inprogress'];
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>600));
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>1300, 'timelimit'=>500));
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group2->id, 'timeclose'=>0, 'timelimit'=>501));
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
$usertimes[$attemptid] = array('timeclose'=>0, 'timelimit'=>501, '', 'message'=>'Test3M');
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>1000, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++, 'attempt'=>1));
$usertimes[$attemptid] = array('timeclose'=>0, 'timelimit'=>501, '', 'message'=>'Test3N');
$quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
$DB->insert_record('quiz_overrides',
['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => 500]);
$DB->insert_record('quiz_overrides',
['quiz' => $quiz->id, 'groupid' => $group2->id, 'timeclose' => 0, 'timelimit' => 501]);
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
$usertimes[$attemptid] = ['timeclose' => 0, 'timelimit' => 501, 'message' => 'Test3M', 'time1000state' => 'finished'];
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
$usertimes[$attemptid] = ['timeclose' => 0, 'timelimit' => 501, 'message' => 'Test3N', 'time1000state' => 'inprogress'];
// User overrides
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>600));
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>1300, 'timelimit'=>700));
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'timeclose'=>1201, 'timelimit'=>601));
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
$usertimes[$attemptid] = array('timeclose'=>1201, 'timelimit'=>601, '', 'message'=>'Test4A');
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>1000, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++, 'attempt'=>1));
$usertimes[$attemptid] = array('timeclose'=>1201, 'timelimit'=>601, '', 'message'=>'Test4B');
$quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
$DB->insert_record('quiz_overrides',
['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => 700]);
$DB->insert_record('quiz_overrides',
['quiz' => $quiz->id, 'userid' => $user1->id, 'timeclose' => 1201, 'timelimit' => 601]);
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
$usertimes[$attemptid] = ['timeclose' => 1201, 'timelimit' => 601, 'message' => 'Test4A', 'time1000state' => 'finished'];
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
$usertimes[$attemptid] = ['timeclose' => 1201, 'timelimit' => 601, 'message' => 'Test4B', 'time1000state' => 'inprogress'];
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>600));
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>1300, 'timelimit'=>700));
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'timeclose'=>0, 'timelimit'=>601));
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
$usertimes[$attemptid] = array('timeclose'=>0, 'timelimit'=>601, '', 'message'=>'Test4C');
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>1000, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++, 'attempt'=>1));
$usertimes[$attemptid] = array('timeclose'=>0, 'timelimit'=>601, '', 'message'=>'Test4D');
$quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
$DB->insert_record('quiz_overrides',
['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => 700]);
$DB->insert_record('quiz_overrides',
['quiz' => $quiz->id, 'userid' => $user1->id, 'timeclose' => 0, 'timelimit' => 601]);
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
$usertimes[$attemptid] = ['timeclose' => 0, 'timelimit' => 601, 'message' => 'Test4C', 'time1000state' => 'finished'];
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
$usertimes[$attemptid] = ['timeclose' => 0, 'timelimit' => 601, 'message' => 'Test4D', 'time1000state' => 'inprogress'];
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>600));
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>1300, 'timelimit'=>700));
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'timeclose'=>1201, 'timelimit'=>0));
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
$usertimes[$attemptid] = array('timeclose'=>1201, 'timelimit'=>0, '', 'message'=>'Test4E');
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>1000, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++, 'attempt'=>1));
$usertimes[$attemptid] = array('timeclose'=>1201, 'timelimit'=>0, '', 'message'=>'Test4F');
$quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
$DB->insert_record('quiz_overrides',
['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => 700]);
$DB->insert_record('quiz_overrides',
['quiz' => $quiz->id, 'userid' => $user1->id, 'timeclose' => 1201, 'timelimit' => 0]);
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
$usertimes[$attemptid] = ['timeclose' => 1201, 'timelimit' => 0, 'message' => 'Test4E', 'time1000state' => 'inprogress'];
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
$usertimes[$attemptid] = ['timeclose' => 1201, 'timelimit' => 0, 'message' => 'Test4F', 'time1000state' => 'inprogress'];
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>600));
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>1300, 'timelimit'=>700));
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'timeclose'=>null, 'timelimit'=>601));
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
$usertimes[$attemptid] = array('timeclose'=>1300, 'timelimit'=>601, '', 'message'=>'Test4G');
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>1000, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++, 'attempt'=>1));
$usertimes[$attemptid] = array('timeclose'=>1300, 'timelimit'=>601, '', 'message'=>'Test4H');
$quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600,
'overduehandling' => 'autoabandon']);
$DB->insert_record('quiz_overrides',
['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => 700]);
$DB->insert_record('quiz_overrides',
['quiz' => $quiz->id, 'userid' => $user1->id, 'timeclose' => null, 'timelimit' => 601]);
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
$usertimes[$attemptid] = ['timeclose' => 1300, 'timelimit' => 601, 'message' => 'Test4G', 'time1000state' => 'abandoned'];
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
$usertimes[$attemptid] = ['timeclose' => 1300, 'timelimit' => 601, 'message' => 'Test4H', 'time1000state' => 'inprogress'];
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>600));
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>null, 'timelimit'=>700));
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'timeclose'=>null, 'timelimit'=>601));
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
$usertimes[$attemptid] = array('timeclose'=>1200, 'timelimit'=>601, '', 'message'=>'Test4I');
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>1000, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++, 'attempt'=>1));
$usertimes[$attemptid] = array('timeclose'=>1200, 'timelimit'=>601, '', 'message'=>'Test4J');
$quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
$DB->insert_record('quiz_overrides',
['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => null, 'timelimit' => 700]);
$DB->insert_record('quiz_overrides',
['quiz' => $quiz->id, 'userid' => $user1->id, 'timeclose' => null, 'timelimit' => 601]);
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
$usertimes[$attemptid] = ['timeclose' => 1200, 'timelimit' => 601, 'message' => 'Test4I', 'time1000state' => 'finished'];
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
$usertimes[$attemptid] = ['timeclose' => 1200, 'timelimit' => 601, 'message' => 'Test4J', 'time1000state' => 'inprogress'];
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>600));
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>1300, 'timelimit'=>700));
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'timeclose'=>1201, 'timelimit'=>null));
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
$usertimes[$attemptid] = array('timeclose'=>1201, 'timelimit'=>700, '', 'message'=>'Test4K');
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>1000, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++, 'attempt'=>1));
$usertimes[$attemptid] = array('timeclose'=>1201, 'timelimit'=>700, '', 'message'=>'Test4L');
$quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
$DB->insert_record('quiz_overrides',
['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => 700]);
$DB->insert_record('quiz_overrides',
['quiz' => $quiz->id, 'userid' => $user1->id, 'timeclose' => 1201, 'timelimit' => null]);
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
$usertimes[$attemptid] = ['timeclose' => 1201, 'timelimit' => 700, 'message' => 'Test4K', 'time1000state' => 'finished'];
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
$usertimes[$attemptid] = ['timeclose' => 1201, 'timelimit' => 700, 'message' => 'Test4L', 'time1000state' => 'inprogress'];
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>600));
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>1300, 'timelimit'=>null));
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'timeclose'=>1201, 'timelimit'=>null));
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
$usertimes[$attemptid] = array('timeclose'=>1201, 'timelimit'=>600, '', 'message'=>'Test4M');
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>1000, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++, 'attempt'=>1));
$usertimes[$attemptid] = array('timeclose'=>1201, 'timelimit'=>600, '', 'message'=>'Test4N');
$quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
$DB->insert_record('quiz_overrides',
['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => null]);
$DB->insert_record('quiz_overrides',
['quiz' => $quiz->id, 'userid' => $user1->id, 'timeclose' => 1201, 'timelimit' => null]);
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
$usertimes[$attemptid] = ['timeclose' => 1201, 'timelimit' => 600, 'message' => 'Test4M', 'time1000state' => 'finished'];
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
$usertimes[$attemptid] = ['timeclose' => 1201, 'timelimit' => 600, 'message' => 'Test4N', 'time1000state' => 'inprogress'];
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>600));
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>1300, 'timelimit'=>700));
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'userid'=>0, 'timeclose'=>1201, 'timelimit'=>601)); // not user
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
$usertimes[$attemptid] = array('timeclose'=>1300, 'timelimit'=>700, '', 'message'=>'Test4O');
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>1000, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++, 'attempt'=>1));
$usertimes[$attemptid] = array('timeclose'=>1300, 'timelimit'=>700, '', 'message'=>'Test4P');
$quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600]);
$DB->insert_record('quiz_overrides',
['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => 700]);
$DB->insert_record('quiz_overrides',
['quiz' => $quiz->id, 'userid' => 0, 'timeclose' => 1201, 'timelimit' => 601]); // Not user.
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
$usertimes[$attemptid] = ['timeclose' => 1300, 'timelimit' => 700, 'message' => 'Test4O', 'time1000state' => 'finished'];
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 1000, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz), 'attempt' => 1]);
$usertimes[$attemptid] = ['timeclose' => 1300, 'timelimit' => 700, 'message' => 'Test4P', 'time1000state' => 'inprogress'];
// Attempt state overdue
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>600, 'overduehandling'=>'graceperiod', 'graceperiod'=>250));
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'overdue', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
$usertimes[$attemptid] = array('timeclose'=>1200, 'timelimit'=>600, '', 'message'=>'Test5A');
$quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 600,
'overduehandling' => 'graceperiod', 'graceperiod' => 250]);
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'overdue',
'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
$usertimes[$attemptid] = ['timeclose' => 1200, 'timelimit' => 600, 'message' => 'Test5A', 'time1000state' => 'overdue'];
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>0, 'timelimit'=>600, 'overduehandling'=>'graceperiod', 'graceperiod'=>250));
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'overdue', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
$usertimes[$attemptid] = array('timeclose'=>0, 'timelimit'=>600, '', 'message'=>'Test5B');
$quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 0, 'timelimit' => 600,
'overduehandling' => 'graceperiod', 'graceperiod' => 250]);
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'overdue',
'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
$usertimes[$attemptid] = ['timeclose' => 0, 'timelimit' => 600, 'message' => 'Test5B', 'time1000state' => 'overdue'];
// Compute expected end time for each attempt.
foreach ($usertimes as $attemptid => $times) {
$attempt = $DB->get_record('quiz_attempts', ['id' => $attemptid], '*', MUST_EXIST);
if ($times['timeclose'] > 0 && $times['timelimit'] > 0) {
$usertimes[$attemptid]['timedue'] = min($times['timeclose'], $attempt->timestart + $times['timelimit']);
} else if ($times['timeclose'] > 0) {
$usertimes[$attemptid]['timedue'] = $times['timeclose'];
} else if ($times['timelimit'] > 0) {
$usertimes[$attemptid]['timedue'] = $attempt->timestart + $times['timelimit'];
}
}
//
// Test quiz_update_open_attempts()
// Test quiz_update_open_attempts().
//
quiz_update_open_attempts(array('courseid'=>$course->id));
foreach ($usertimes as $attemptid=>$times) {
$attempt = $DB->get_record('quiz_attempts', array('id'=>$attemptid));
$this->assertTrue(false !== $attempt, $times['message']);
quiz_update_open_attempts(['courseid' => $course->id]);
foreach ($usertimes as $attemptid => $times) {
$attempt = $DB->get_record('quiz_attempts', ['id' => $attemptid], '*', MUST_EXIST);
if ($attempt->state == 'overdue') {
$graceperiod = $DB->get_field('quiz', 'graceperiod', array('id'=>$attempt->quiz));
$graceperiod = $DB->get_field('quiz', 'graceperiod', ['id' => $attempt->quiz]);
} else {
$graceperiod = 0;
}
if ($times['timeclose'] > 0 and $times['timelimit'] > 0) {
$this->assertEquals(min($times['timeclose'], $attempt->timestart + $times['timelimit']) + $graceperiod, $attempt->timecheckstate, $times['message']);
} else if ($times['timeclose'] > 0) {
$this->assertEquals($times['timeclose'] + $graceperiod, $attempt->timecheckstate <= $times['timeclose'], $times['message']);
} else if ($times['timelimit'] > 0) {
$this->assertEquals($attempt->timestart + $times['timelimit'] + $graceperiod, $attempt->timecheckstate, $times['message']);
if (isset($times['timedue'])) {
$this->assertEquals($times['timedue'] + $graceperiod, $attempt->timecheckstate, $times['message']);
} else {
$this->assertNull($attempt->timecheckstate, $times['message']);
}
}
//
// Test get_list_of_overdue_attempts()
// Test get_list_of_overdue_attempts().
//
$overduehander = new mod_quiz_overdue_attempt_updater();
@@ -317,13 +416,64 @@ class mod_quiz_attempt_overdue_testcase extends advanced_testcase {
$attempts->close();
$this->assertEquals(0, $count);
//
// Test update_overdue_attempts().
//
[$count, $quizcount] = $overduehander->update_overdue_attempts(1000, 940);
$attempts = $DB->get_records('quiz_attempts', null, 'quiz, userid, attempt',
'id, quiz, userid, attempt, state, timestart, timefinish, timecheckstate');
foreach ($attempts as $attempt) {
$this->assertTrue(isset($usertimes[$attempt->id]));
$times = $usertimes[$attempt->id];
$this->assertEquals($times['time1000state'], $attempt->state, $times['message']);
switch ($times['time1000state']) {
case 'finished':
$this->assertEquals($times['timedue'], $attempt->timefinish, $times['message']);
$this->assertNull($attempt->timecheckstate, $times['message']);
break;
case 'overdue':
$this->assertEquals(0, $attempt->timefinish, $times['message']);
$graceperiod = $DB->get_field('quiz', 'graceperiod', ['id' => $attempt->quiz]);
$this->assertEquals($times['timedue'] + $graceperiod, $attempt->timecheckstate, $times['message']);
break;
case 'abandoned':
$this->assertEquals(0, $attempt->timefinish, $times['message']);
$this->assertNull($attempt->timecheckstate, $times['message']);
break;
}
}
$this->assertEquals(19, $count);
$this->assertEquals(19, $quizcount);
}
/**
* Make any old question usage for a quiz.
*
* The attempts used in test_bulk_update_functions must have some
* question usage to store in uniqueid, but they don't have to be
* very realistic.
*
* @param stdClass $quiz
* @return int question usage id.
*/
protected function usage_id(stdClass $quiz): int {
$quba = question_engine::make_questions_usage_by_activity('mod_quiz',
context_module::instance($quiz->cmid));
$quba->set_preferred_behaviour('deferredfeedback');
question_engine::save_questions_usage_by_activity($quba);
return $quba->get_id();
}
/**
* Test the group event handlers
*/
public function test_group_event_handlers() {
global $DB,$CFG;
global $DB;
$this->resetAfterTest();
@@ -333,59 +483,61 @@ class mod_quiz_attempt_overdue_testcase extends advanced_testcase {
$course = $this->getDataGenerator()->create_course();
$user1 = $this->getDataGenerator()->create_user();
$studentrole = $DB->get_record('role', array('shortname'=>'student'));
$studentrole = $DB->get_record('role', ['shortname' => 'student']);
$this->assertNotEmpty($studentrole);
$this->assertTrue(enrol_try_internal_enrol($course->id, $user1->id, $studentrole->id));
$group1 = $this->getDataGenerator()->create_group(array('courseid'=>$course->id));
$group2 = $this->getDataGenerator()->create_group(array('courseid'=>$course->id));
$group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
$group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
$this->assertTrue(groups_add_member($group1, $user1));
$this->assertTrue(groups_add_member($group2, $user1));
$uniqueid = 0;
/** @var mod_quiz_generator $quizgenerator */
$quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
$quiz_generator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
$quiz = $quiz_generator->create_instance(array('course'=>$course->id, 'timeclose'=>1200, 'timelimit'=>0));
$quiz = $quizgenerator->create_instance(['course' => $course->id, 'timeclose' => 1200, 'timelimit' => 0]);
// add a group1 override
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group1->id, 'timeclose'=>1300, 'timelimit'=>null));
$DB->insert_record('quiz_overrides',
['quiz' => $quiz->id, 'groupid' => $group1->id, 'timeclose' => 1300, 'timelimit' => null]);
// add an attempt
$attemptid = $DB->insert_record('quiz_attempts', array('quiz'=>$quiz->id, 'userid'=>$user1->id, 'state'=>'inprogress', 'timestart'=>100, 'timecheckstate'=>0, 'layout'=>'', 'uniqueid'=>$uniqueid++));
$attemptid = $DB->insert_record('quiz_attempts', ['quiz' => $quiz->id, 'userid' => $user1->id, 'state' => 'inprogress',
'timestart' => 100, 'timecheckstate' => 0, 'layout' => '', 'uniqueid' => $this->usage_id($quiz)]);
// update timecheckstate
quiz_update_open_attempts(array('quizid'=>$quiz->id));
$this->assertEquals(1300, $DB->get_field('quiz_attempts', 'timecheckstate', array('id'=>$attemptid)));
quiz_update_open_attempts(['quizid' => $quiz->id]);
$this->assertEquals(1300, $DB->get_field('quiz_attempts', 'timecheckstate', ['id' => $attemptid]));
// remove from group
$this->assertTrue(groups_remove_member($group1, $user1));
$this->assertEquals(1200, $DB->get_field('quiz_attempts', 'timecheckstate', array('id'=>$attemptid)));
$this->assertEquals(1200, $DB->get_field('quiz_attempts', 'timecheckstate', ['id' => $attemptid]));
// add back to group
$this->assertTrue(groups_add_member($group1, $user1));
$this->assertEquals(1300, $DB->get_field('quiz_attempts', 'timecheckstate', array('id'=>$attemptid)));
$this->assertEquals(1300, $DB->get_field('quiz_attempts', 'timecheckstate', ['id' => $attemptid]));
// delete group
groups_delete_group($group1);
$this->assertEquals(1200, $DB->get_field('quiz_attempts', 'timecheckstate', array('id'=>$attemptid)));
$this->assertEquals(0, $DB->count_records('quiz_overrides', array('quiz'=>$quiz->id)));
$this->assertEquals(1200, $DB->get_field('quiz_attempts', 'timecheckstate', ['id' => $attemptid]));
$this->assertEquals(0, $DB->count_records('quiz_overrides', ['quiz' => $quiz->id]));
// add a group2 override
$DB->insert_record('quiz_overrides', array('quiz'=>$quiz->id, 'groupid'=>$group2->id, 'timeclose'=>1400, 'timelimit'=>null));
quiz_update_open_attempts(array('quizid'=>$quiz->id));
$this->assertEquals(1400, $DB->get_field('quiz_attempts', 'timecheckstate', array('id'=>$attemptid)));
$DB->insert_record('quiz_overrides',
['quiz' => $quiz->id, 'groupid' => $group2->id, 'timeclose' => 1400, 'timelimit' => null]);
quiz_update_open_attempts(['quizid' => $quiz->id]);
$this->assertEquals(1400, $DB->get_field('quiz_attempts', 'timecheckstate', ['id' => $attemptid]));
// delete user1 from all groups
groups_delete_group_members($course->id, $user1->id);
$this->assertEquals(1200, $DB->get_field('quiz_attempts', 'timecheckstate', array('id'=>$attemptid)));
$this->assertEquals(1200, $DB->get_field('quiz_attempts', 'timecheckstate', ['id' => $attemptid]));
// add back to group2
$this->assertTrue(groups_add_member($group2, $user1));
$this->assertEquals(1400, $DB->get_field('quiz_attempts', 'timecheckstate', array('id'=>$attemptid)));
$this->assertEquals(1400, $DB->get_field('quiz_attempts', 'timecheckstate', ['id' => $attemptid]));
// delete everyone from all groups
groups_delete_group_members($course->id);
$this->assertEquals(1200, $DB->get_field('quiz_attempts', 'timecheckstate', array('id'=>$attemptid)));
$this->assertEquals(1200, $DB->get_field('quiz_attempts', 'timecheckstate', ['id' => $attemptid]));
}
/**
@@ -400,14 +552,16 @@ class mod_quiz_attempt_overdue_testcase extends advanced_testcase {
$user1 = $this->getDataGenerator()->create_user();
$student = $this->getDataGenerator()->create_user();
$this->getDataGenerator()->enrol_user($student->id, $course->id, 'student');
/** @var mod_quiz_generator $quizgenerator */
$quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
/** @var core_question_generator $questiongenerator */
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
$quiz = $quizgenerator->create_instance(array('course' => $course->id, 'questionsperpage' => 0, 'grade' => 100.0,
'sumgrades' => 2));
$quiz = $quizgenerator->create_instance(['course' => $course->id, 'questionsperpage' => 0, 'grade' => 100.0,
'sumgrades' => 2]);
// Create questions.
$cat = $questiongenerator->create_question_category();
$saq = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id));
$numq = $questiongenerator->create_question('numerical', null, array('category' => $cat->id));
$saq = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]);
$numq = $questiongenerator->create_question('numerical', null, ['category' => $cat->id]);
// Add them to the quiz.
quiz_add_quiz_question($saq->id, $quiz);
quiz_add_quiz_question($numq->id, $quiz);
@@ -416,7 +570,7 @@ class mod_quiz_attempt_overdue_testcase extends advanced_testcase {
$quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
$timenow = time();
// Create an attempt.
$attempt = quiz_create_attempt($quizobj, 1, false, $timenow, false, $user1->id);
$attempt = quiz_create_attempt($quizobj, 1, null, $timenow, false, $user1->id);
quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow);
quiz_attempt_save_started($quizobj, $quba, $attempt);
$result = quiz_create_attempt_handling_errors($attempt->id, $quiz->cmid);
+1 -1
View File
@@ -75,7 +75,7 @@ class mod_quiz_generator extends testing_module_generator {
'timecreated' => time(),
'timemodified' => time(),
'timelimit' => 0,
'overduehandling' => 'autoabandon',
'overduehandling' => 'autosubmit',
'graceperiod' => 86400,
'quizpassword' => '',
'subnet' => '',
+1 -1
View File
@@ -49,7 +49,7 @@ class helper {
/** @var \paygw_paypal\gateway $classname */
$classname = '\paygw_' . $plugin . '\gateway';
$currencies += component_class_callback($classname, 'get_supported_currencies', [], []);
$currencies = array_merge($currencies, component_class_callback($classname, 'get_supported_currencies', [], []));
}
$currencies = array_unique($currencies);
+1 -1
View File
@@ -432,7 +432,7 @@ abstract class question_engine {
public static function get_all_response_file_areas() {
$variables = array();
foreach (question_bank::get_all_qtypes() as $qtype) {
$variables += $qtype->response_file_areas();
$variables = array_merge($variables, $qtype->response_file_areas());
}
$areas = array();
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+48 -15
View File
@@ -473,8 +473,10 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f
var drag = $(draghome);
var placeHolder = drag.clone();
placeHolder.removeClass();
placeHolder.addClass('marker choice' +
thisQ.getChoiceNoFromElement(drag) + ' dragno' + thisQ.getDragNo(drag) + ' dragplaceholder');
placeHolder.addClass('marker');
placeHolder.addClass('choice' + thisQ.getChoiceNoFromElement(drag));
placeHolder.addClass(thisQ.getDragNoClass(drag, false));
placeHolder.addClass('dragplaceholder');
drag.before(placeHolder);
});
};
@@ -489,6 +491,26 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f
return this.getClassnameNumericSuffix(drag, 'dragno');
};
/**
* Get the drag number prefix of a drag.
*
* @param {jQuery} drag the drag.
* @param {Boolean} includeSelector include the CSS selector prefix or not.
* @return {String} Class name
*/
DragDropMarkersQuestion.prototype.getDragNoClass = function(drag, includeSelector) {
var className = 'dragno' + this.getDragNo(drag);
if (this.isInfiniteDrag(drag)) {
className = 'infinite';
}
if (includeSelector) {
return '.' + className;
}
return className;
};
/**
* Get drag clone for a given drag.
*
@@ -497,7 +519,7 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f
*/
DragDropMarkersQuestion.prototype.getDragClone = function(drag) {
return this.getRoot().find('.draghomes' + ' span.marker' +
'.choice' + this.getChoiceNoFromElement(drag) + '.dragno' + this.getDragNo(drag) + '.dragplaceholder');
'.choice' + this.getChoiceNoFromElement(drag) + this.getDragNoClass(drag, true) + '.dragplaceholder');
};
/**
@@ -555,20 +577,21 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f
var inputNode = this.getInput(drag),
noOfDrags = Number(this.getClassnameNumericSuffix(inputNode, 'noofdrags')),
displayedDragsInDropArea = this.getRoot().find('div.droparea .marker.choice' +
this.getChoiceNoFromElement(drag) + '.dragno' + this.getDragNo(drag)).length,
this.getChoiceNoFromElement(drag) + this.getDragNoClass(drag, true)).length,
displayedDragsInDragHomes = this.getRoot().find('div.draghomes .marker.choice' +
this.getChoiceNoFromElement(drag) + '.dragno' + this.getDragNo(drag)).not('.dragplaceholder').length;
this.getChoiceNoFromElement(drag) + this.getDragNoClass(drag, true)).not('.dragplaceholder').length;
if (displayedDragsInDropArea < noOfDrags && displayedDragsInDragHomes === 0) {
var dragclone = drag.clone();
dragclone.addClass('unneeded')
if ((this.isInfiniteDrag(drag) ||
!this.isInfiniteDrag(drag) && displayedDragsInDropArea < noOfDrags) && displayedDragsInDragHomes === 0) {
var dragClone = drag.clone();
dragClone.addClass('unneeded')
.css('top', '')
.css('left', '')
.css('transform', '');
this.getDragClone(drag)
.removeClass('active')
.after(dragclone);
questionManager.addEventHandlersToMarker(dragclone);
.after(dragClone);
questionManager.addEventHandlersToMarker(dragClone);
}
};
@@ -578,11 +601,12 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f
* @param {jQuery} drag the item to place.
*/
DragDropMarkersQuestion.prototype.removeDragIfNeeded = function(drag) {
var displayeddrags = this.getRoot().find('div.draghomes .marker.choice' +
this.getChoiceNoFromElement(drag) + '.dragno' + this.getDragNo(drag)).not('.dragplaceholder').length;
if (displayeddrags > 1) {
this.getRoot().find('div.draghomes .marker.choice' +
this.getChoiceNoFromElement(drag) + '.dragno' + this.getDragNo(drag)).not('.dragplaceholder').first().remove();
var dragsInHome = this.getRoot().find('div.draghomes .marker.choice' +
this.getChoiceNoFromElement(drag) + this.getDragNoClass(drag, true)).not('.dragplaceholder');
var displayedDrags = dragsInHome.length;
while (displayedDrags > 1) {
dragsInHome.first().remove();
displayedDrags--;
}
};
@@ -631,6 +655,15 @@ define(['jquery', 'core/dragdrop', 'qtype_ddmarker/shapes', 'core/key_codes'], f
});
};
/**
* Check if the given drag is in infinite mode or not.
*
* @param {jQuery} drag The drag item need to check.
*/
DragDropMarkersQuestion.prototype.isInfiniteDrag = function(drag) {
return drag.hasClass('infinite');
};
/**
* Singleton that tracks all the DragDropToTextQuestions on this page, and deals
* with event dispatching.
+2 -2
View File
@@ -264,7 +264,7 @@ class qtype_essay_format_editor_renderer extends plugin_renderer_base {
$this->class_name() . ' qtype_essay_response'));
$output .= html_writer::tag('div', html_writer::tag('textarea', s($response),
array('id' => $id, 'name' => $inputname, 'rows' => $lines, 'cols' => 60)));
array('id' => $id, 'name' => $inputname, 'rows' => $lines, 'cols' => 60, 'class' => 'form-control')));
$output .= html_writer::start_tag('div');
if (count($formats) == 1) {
@@ -455,7 +455,7 @@ class qtype_essay_format_plain_renderer extends plugin_renderer_base {
* @return string the HTML for the textarea.
*/
protected function textarea($response, $lines, $attributes) {
$attributes['class'] = $this->class_name() . ' qtype_essay_response';
$attributes['class'] = $this->class_name() . ' qtype_essay_response form-control';
$attributes['rows'] = $lines;
$attributes['cols'] = 60;
return html_writer::tag('textarea', s($response), $attributes);
@@ -47,7 +47,6 @@ Feature: See the competencies for an activity
Scenario: Go to the competency breakdown report
When I navigate to "Reports > Competency breakdown" in current page administration
And I set the field "Filter competencies by resource or activity" to "PageName1"
And I press the enter key
Then I should see "Test-Comp1"
And I should not see "Test-Comp2"
And I click on "Not rated" "link"
@@ -55,7 +54,6 @@ Feature: See the competencies for an activity
And I set the field "Rating" to "A"
And I click on "Rate" "button" in the ".competency-grader" "css_element"
And I click on "Close" "button"
And I set the field "Filter competencies by resource or activity" to "No filters applied"
And I press the enter key
And I click on "PageName1" "autocomplete_selection"
And I should see "Test-Comp1"
And I should see "Test-Comp2"
-6
View File
@@ -1258,12 +1258,6 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview {
overflow: auto;
}
.moodle-dialogue-base .moodle-dialogue-fullscreen .closebutton {
width: 28px;
height: 16px;
background-size: 100%;
}
.moodle-dialogue-base .moodle-dialogue-wrap {
background-color: #fff;
border: 1px solid #ccc;
+3 -3
View File
@@ -354,14 +354,14 @@ body.path-question-type {
.que .correctness {
&.correct {
background-color: $success;
@include badge-variant($success);
}
&.partiallycorrect {
background-color: $warning;
@include badge-variant($warning);
}
&.notanswered,
&.incorrect {
background-color: $danger;
@include badge-variant($danger);
}
}
+21 -5
View File
@@ -10694,11 +10694,6 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview {
.moodle-dialogue-base .moodle-dialogue-fullscreen .moodle-dialogue-content {
overflow: auto; }
.moodle-dialogue-base .moodle-dialogue-fullscreen .closebutton {
width: 28px;
height: 16px;
background-size: 100%; }
.moodle-dialogue-base .moodle-dialogue-wrap {
background-color: #fff;
border: 1px solid #ccc; }
@@ -15768,13 +15763,34 @@ body.path-question-type {
margin: 0 0 0.5em; }
.que .correctness.correct {
color: #fff;
background-color: #357a32; }
a.que .correctness.correct:hover, a.que .correctness.correct:focus {
color: #fff;
background-color: #255623; }
a.que .correctness.correct:focus, a.que .correctness.correct.focus {
outline: 0;
box-shadow: 0 0 0 0.2rem rgba(53, 122, 50, 0.5); }
.que .correctness.partiallycorrect {
color: #212529;
background-color: #f0ad4e; }
a.que .correctness.partiallycorrect:hover, a.que .correctness.partiallycorrect:focus {
color: #212529;
background-color: #ec971f; }
a.que .correctness.partiallycorrect:focus, a.que .correctness.partiallycorrect.focus {
outline: 0;
box-shadow: 0 0 0 0.2rem rgba(240, 173, 78, 0.5); }
.que .correctness.notanswered, .que .correctness.incorrect {
color: #fff;
background-color: #ca3120; }
a.que .correctness.notanswered:hover, a.que .correctness.notanswered:focus, .que .correctness.incorrect:hover, .que .correctness.incorrect:focus {
color: #fff;
background-color: #9e2619; }
a.que .correctness.notanswered:focus, a.que .correctness.notanswered.focus, .que .correctness.incorrect:focus, .que .correctness.incorrect.focus {
outline: 0;
box-shadow: 0 0 0 0.2rem rgba(202, 49, 32, 0.5); }
.que .qtext {
margin-bottom: 1.5em; }
+21 -5
View File
@@ -10898,11 +10898,6 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview {
.moodle-dialogue-base .moodle-dialogue-fullscreen .moodle-dialogue-content {
overflow: auto; }
.moodle-dialogue-base .moodle-dialogue-fullscreen .closebutton {
width: 28px;
height: 16px;
background-size: 100%; }
.moodle-dialogue-base .moodle-dialogue-wrap {
background-color: #fff;
border: 1px solid #ccc; }
@@ -15989,13 +15984,34 @@ body.path-question-type {
margin: 0 0 0.5em; }
.que .correctness.correct {
color: #fff;
background-color: #357a32; }
a.que .correctness.correct:hover, a.que .correctness.correct:focus {
color: #fff;
background-color: #255623; }
a.que .correctness.correct:focus, a.que .correctness.correct.focus {
outline: 0;
box-shadow: 0 0 0 0.2rem rgba(53, 122, 50, 0.5); }
.que .correctness.partiallycorrect {
color: #212529;
background-color: #f0ad4e; }
a.que .correctness.partiallycorrect:hover, a.que .correctness.partiallycorrect:focus {
color: #212529;
background-color: #ec971f; }
a.que .correctness.partiallycorrect:focus, a.que .correctness.partiallycorrect.focus {
outline: 0;
box-shadow: 0 0 0 0.2rem rgba(240, 173, 78, 0.5); }
.que .correctness.notanswered, .que .correctness.incorrect {
color: #fff;
background-color: #ca3120; }
a.que .correctness.notanswered:hover, a.que .correctness.notanswered:focus, .que .correctness.incorrect:hover, .que .correctness.incorrect:focus {
color: #fff;
background-color: #9e2619; }
a.que .correctness.notanswered:focus, a.que .correctness.notanswered.focus, .que .correctness.incorrect:focus, .que .correctness.incorrect.focus {
outline: 0;
box-shadow: 0 0 0 0.2rem rgba(202, 49, 32, 0.5); }
.que .qtext {
margin-bottom: 1.5em; }
+3 -5
View File
@@ -25,9 +25,8 @@ Feature: Edit user roles
And I am on "Course 1" course homepage
And I navigate to course participants
And I click on "Student 1's role assignments" "link"
And I click on ".form-autocomplete-downarrow" "css_element" in the "student1" "table_row"
And I click on "Non-editing teacher" item in the autocomplete list
And I press the escape key
And I type "Non-editing teacher"
And I press the enter key
When I click on "Save changes" "link"
Then I should see "Student, Non-editing teacher" in the "Student 1" "table_row"
@@ -37,7 +36,6 @@ Feature: Edit user roles
And I am on "Course 1" course homepage
And I navigate to course participants
And I click on "Student 1's role assignments" "link"
And I click on ".form-autocomplete-selection [aria-selected=true]" "css_element"
And I press the escape key
And I click on "Student" "autocomplete_selection"
When I click on "Save changes" "link"
Then I should see "No roles" in the "Student 1" "table_row"
File diff suppressed because it is too large Load Diff
@@ -76,8 +76,7 @@ Feature: Course participants can be filtered to display all the users
And I navigate to course participants
And I set the field "Match" in the "Filter 1" "fieldset" to "All"
And I set the field "type" in the "Filter 1" "fieldset" to "Roles"
And I click on ".form-autocomplete-downarrow" "css_element" in the "Filter 1" "fieldset"
And I click on "Student" "list_item"
And I set the field "Type or select..." in the "Filter 1" "fieldset" to "Student"
When I click on "Apply filters" "button"
Then I should see "24 participants found"
And I should see "Show all 24"
@@ -94,15 +93,14 @@ Feature: Course participants can be filtered to display all the users
And I navigate to course participants
And I set the field "Match" in the "Filter 1" "fieldset" to "All"
And I set the field "type" in the "Filter 1" "fieldset" to "Roles"
And I click on ".form-autocomplete-downarrow" "css_element" in the "Filter 1" "fieldset"
And I click on "Student" "list_item"
And I set the field "Type or select..." in the "Filter 1" "fieldset" to "Student"
When I click on "Apply filters" "button"
Then I should see "24 participants found"
And I should see "Show all 24"
Then I should see "23 participants found"
And I should see "Show all 23"
And I should not see "Show 20 per page"
And I click on "Show all 24" "link"
And I click on "Show all 23" "link"
And I should see "Show 20 per page"
And I should not see "Show all 24"
And I should not see "Show all 23"
@javascript
Scenario: Apply one value for more than one filter and show all matching users
@@ -113,12 +111,10 @@ Feature: Course participants can be filtered to display all the users
And I set the field "Match" to "All"
And I set the field "Match" in the "Filter 1" "fieldset" to "Any"
And I set the field "type" in the "Filter 1" "fieldset" to "Roles"
And I click on ".form-autocomplete-downarrow" "css_element" in the "Filter 1" "fieldset"
And I click on "Student" "list_item"
And I set the field "Type or select..." in the "Filter 1" "fieldset" to "Student"
And I set the field "Match" in the "Filter 2" "fieldset" to "Any"
And I set the field "type" in the "Filter 2" "fieldset" to "Status"
And I click on ".form-autocomplete-downarrow" "css_element" in the "Filter 2" "fieldset"
And I click on "Active" "list_item"
And I set the field "Type or select..." in the "Filter 2" "fieldset" to "Active"
When I click on "Apply filters" "button"
And I click on "Show all 23" "link"
Then I should see "23 participants found"
+2 -2
View File
@@ -79,6 +79,6 @@ Feature: Users' names are displayed across the site according to the user policy
And I am on "Course 1" course homepage
When I navigate to course participants
And I press "Enrol users"
And I set the field "Select users" to "three@example.com"
And I click on ".form-autocomplete-downarrow" "css_element" in the "Select users" "form_row"
And I click on "Select users" "field"
And I type "three@example.com"
Then I should see "Sarah, Sev, Siobhan, Desforges"
@@ -60,8 +60,7 @@ Feature: View course participants groups
And I should see "Student 1x"
And I should see "Student 2x"
And I set the field "type" in the "Filter 1" "fieldset" to "Groups"
And I click on ".form-autocomplete-downarrow" "css_element" in the "Filter 1" "fieldset"
And I click on "Group B" "list_item"
And I set the field "Type or select..." in the "Filter 1" "fieldset" to "Group B"
And I click on "Apply filters" "button"
And I should see "Student 3x"
And I should see "Student 4x"
+2 -2
View File
@@ -29,9 +29,9 @@
defined('MOODLE_INTERNAL') || die();
$version = 2020110900.04; // 20201109 = branching date YYYYMMDD - do not modify!
$version = 2020110900.07; // 20201109 = branching date YYYYMMDD - do not modify!
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.
$release = '3.10+ (Build: 20201204)';// Human-friendly version name
$release = '3.10+ (Build: 20201224)';// Human-friendly version name
$branch = '310'; // This version's branch.
$maturity = MATURITY_STABLE; // This version's maturity level.