$temp->add(new admin_setting_configduration('sessiontimeout', new lang_string('sessiontimeout', 'admin'),
new lang_string('configsessiontimeout', 'admin'), 8 * 60 * 60));
+ $sessiontimeoutwarning = new admin_setting_configduration('sessiontimeoutwarning',
+ new lang_string('sessiontimeoutwarning', 'admin'),
+ new lang_string('configsessiontimeoutwarning', 'admin'), 20 * 60);
+
+ $sessiontimeoutwarning->set_validate_function(function(int $value): string {
+ global $CFG;
+ // Check sessiontimeoutwarning is less than sessiontimeout.
+ if ($CFG->sessiontimeout <= $value) {
+ return get_string('configsessiontimeoutwarningcheck', 'admin');
+ } else {
+ return '';
+ }
+ });
+
+ $temp->add($sessiontimeoutwarning);
+
$temp->add(new admin_setting_configtext('sessioncookie', new lang_string('sessioncookie', 'admin'),
new lang_string('configsessioncookie', 'admin'), '', PARAM_ALPHANUM));
$temp->add(new admin_setting_configtext('sessioncookiepath', new lang_string('sessioncookiepath', 'admin'),
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"
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"
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"
} 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},
$(this).addClass('d-none');
});
- $('[data-region="input"]').val(document.location.hash.replace('#', ''));
refreshSearch(config.theme);
return {};
});
*/
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.
*
$components[$type]->plugins[$component] = (object) [
'name' => $pluginname,
'component' => $component,
+ 'selected' => ($component === $this->component),
];
}
return (object) [
'allcomponents' => array_values($components),
+ 'search' => $this->search,
];
}
}
{{/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}}
<optgroup label="{{type}}">
{{#plugins}}
- <option value="{{component}}">{{name}}</option>
+ <option value="{{component}}" {{#selected}}selected{{/selected}}>{{name}}</option>
{{/plugins}}
</optgroup>
{{/allcomponents}}
{{< 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 }}
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"
$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()) {
}
}
if ($viewmode === 'default' || $viewmode === 'combined') {
- $class .= ' viewmode-cobmined';
+ $class .= ' viewmode-combined';
} else {
$class .= ' viewmode-'.$viewmode;
}
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
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
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
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"
$currencies[$c] = new lang_string($c, 'core_currencies');
}
+ uasort($currencies, function($a, $b) {
+ return strcmp($a, $b);
+ });
+
return $currencies;
}
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.
| 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
| 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
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
}
.gradingform_rubric .criterion .description {
- width: 150px;
+ min-width: 150px;
font-weight: bold;
}
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"
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"
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"
$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';
$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';
$string['configsessioncookiedomain'] = 'This allows you to change the domain that the Moodle cookies are available from. This is useful for Moodle customisations (e.g. authentication or enrolment plugins) that need to share Moodle session information with a web application on another subdomain. <strong>WARNING: it is strongly recommended to leave this setting at the default (empty) - an incorrect value will prevent all logins to the site.</strong>';
$string['configsessioncookiepath'] = 'If you need to change where browsers send the Moodle cookies, you can change this setting to specify a subdirectory of your web site. Otherwise the default \'/\' should be fine.';
$string['configsessiontimeout'] = 'If people logged in to this site are idle for a long time (without loading pages) then they are automatically logged out (their session is ended). This variable specifies how long this time should be.';
+$string['configsessiontimeoutwarning'] = 'If people logged in to this site are idle for a long time (without loading pages) then they are warned about their session is about to end. This variable specifies how long this time should be.';
+$string['configsessiontimeoutwarningcheck'] = 'Session timeout warning must be less than session timeout';
$string['configshowicalsource'] = 'Show source information for iCal events';
$string['configshowcommentscount'] = 'Show comments count, it will cost one more query when display comments link';
$string['configshowsiteparticipantslist'] = 'All of these site students and site teachers will be listed on the site participants list. Who shall be allowed to see this site participants list?';
$string['sessioncookiepath'] = 'Cookie path';
$string['sessionhandling'] = 'Session handling';
$string['sessiontimeout'] = 'Timeout';
+$string['sessiontimeoutwarning'] = 'Timeout Warning';
$string['settingdependenton'] = 'This setting may be hidden, based on the value of <strong>{$a}</strong>.';
$string['settingfileuploads'] = 'File uploading is required for normal operation, please enable it in PHP configuration.';
$string['settingmemorylimit'] = 'Insufficient memory detected, please set higher memory limit in PHP settings.';
<p>All you need to do is make up a username and password and use it in the form on this page!</p>
<p>If someone else has already chosen your username then you\'ll have to try again using a different username.</p>';
$string['loginto'] = 'Log in to {$a}';
+$string['loginagain'] = 'Log in again';
$string['logout'] = 'Log out';
$string['logoutconfirm'] = 'Do you really want to log out?';
$string['logs'] = 'Logs';
/** @var int default duration unit */
protected $defaultunit;
+ /** @var callable|null Validation function */
+ protected $validatefunction = null;
/**
* Constructor
parent::__construct($name, $visiblename, $description, $defaultsetting);
}
+ /**
+ * Sets a validate function.
+ *
+ * The callback will be passed one parameter, the new setting value, and should return either
+ * an empty string '' if the value is OK, or an error message if not.
+ *
+ * @param callable|null $validatefunction Validate function or null to clear
+ * @since Moodle 3.10
+ */
+ public function set_validate_function(?callable $validatefunction = null) {
+ $this->validatefunction = $validatefunction;
+ }
+
+ /**
+ * Validate the setting. This uses the callback function if provided; subclasses could override
+ * to carry out validation directly in the class.
+ *
+ * @param int $data New value being set
+ * @return string Empty string if valid, or error message text
+ * @since Moodle 3.10
+ */
+ protected function validate_setting(int $data): string {
+ // If validation function is specified, call it now.
+ if ($this->validatefunction) {
+ return call_user_func($this->validatefunction, $data);
+ } else {
+ return '';
+ }
+ }
+
/**
* Returns selectable units.
* @static
return get_string('errorsetting', 'admin');
}
+ // Validate the new setting.
+ $error = $this->validate_setting($seconds);
+ if ($error) {
+ return $error;
+ }
+
$result = $this->config_write($this->name, $seconds);
return ($result ? '' : get_string('errorsetting', 'admin'));
}
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);
var sessionTimeout = false;
// 1/10 of session timeout, max of 10 minutes.
var checkFrequency = Math.min((Config.sessiontimeout / 10), 600) * 1000;
- // 1/5 of sessiontimeout.
- var warningLimit = checkFrequency * 2;
-
+ // Check if sessiontimeoutwarning is set or double the checkFrequency.
+ var warningLimit = (Config.sessiontimeoutwarning > 0) ? (Config.sessiontimeoutwarning * 1000) : (checkFrequency * 2);
+ // First wait is minimum of remaining time or half of the session timeout.
+ var firstWait = (Config.sessiontimeoutwarning > 0) ?
+ Math.min((Config.sessiontimeout - Config.sessiontimeoutwarning) * 1000, checkFrequency * 5) : checkFrequency * 5;
/**
* The session time has expired - we can't extend it now.
+ * @param {Modal} modal
*/
- var timeoutSessionExpired = function() {
+ var timeoutSessionExpired = function(modal) {
sessionTimeout = true;
+ warningDisplayed = false;
+ closeModal(modal);
+ displaySessionExpired();
+ };
+
+ /**
+ * Close modal - this relies on modal object passed from Notification.confirm.
+ *
+ * @param {Modal} modal
+ */
+ var closeModal = function(modal) {
+ modal.destroy();
+ };
+
+ /**
+ * The session time has expired - we can't extend it now.
+ * @return {Promise}
+ */
+ var displaySessionExpired = function() {
+ // Check again if its already extended before displaying session expired popup in case multiple tabs are open.
+ var request = {
+ methodname: 'core_session_time_remaining',
+ args: { }
+ };
+
+ return Ajax.call([request], true, true, true)[0].then(function(args) {
+ if (args.timeremaining * 1000 > warningLimit) {
+ return false;
+ } else {
+ return Str.get_strings([
+ {key: 'sessionexpired', component: 'error'},
+ {key: 'sessionerroruser', component: 'error'},
+ {key: 'loginagain', component: 'moodle'},
+ {key: 'cancel', component: 'moodle'}
+ ]).then(function(strings) {
+ Notification.confirm(
+ strings[0], // Title.
+ strings[1], // Message.
+ strings[2], // Login Again.
+ strings[3], // Cancel.
+ function() {
+ location.reload();
+ return true;
+ }
+ );
+ return true;
+ }).catch(Notification.exception);
+ }
+ });
};
/**
if (sessionTimeout) {
// We timed out before we extended the session.
- return Str.get_strings([
- {key: 'sessionexpired', component: 'error'},
- {key: 'sessionerroruser', component: 'error'}
- ]).then(function(strings) {
- Notification.alert(
- strings[0], // Title.
- strings[1] // Message.
- );
- return true;
- }).fail(Notification.exception);
+ return displaySessionExpired();
} else {
return Ajax.call([request], true, true, false, requestTimeout)[0].then(function() {
if (keepAliveFrequency > 0) {
setTimeout(touchSession, keepAliveFrequency);
}
return true;
- }).fail(function() {
+ }).catch(function() {
Notification.alert('', keepAliveMessage);
});
}
methodname: 'core_session_time_remaining',
args: { }
};
-
sessionTimeout = false;
return Ajax.call([request], true, true, true)[0].then(function(args) {
if (args.userid <= 0) {
return false;
}
- if (args.timeremaining < 0) {
- Str.get_strings([
- {key: 'sessionexpired', component: 'error'},
- {key: 'sessionerroruser', component: 'error'}
- ]).then(function(strings) {
- Notification.alert(
- strings[0], // Title.
- strings[1] // Message.
- );
- return true;
- }).fail(Notification.exception);
-
- } else if (args.timeremaining * 1000 < warningLimit && !warningDisplayed) {
- // If we don't extend the session before the timeout - warn.
- setTimeout(timeoutSessionExpired, args.timeremaining * 1000);
+ if (args.timeremaining <= 0) {
+ return displaySessionExpired();
+ } else if (args.timeremaining * 1000 <= warningLimit && !warningDisplayed) {
warningDisplayed = true;
Str.get_strings([
{key: 'norecentactivity', component: 'moodle'},
{key: 'extendsession', component: 'moodle'},
{key: 'cancel', component: 'moodle'}
]).then(function(strings) {
- Notification.confirm(
+ return Notification.confirm(
strings[0], // Title.
strings[1], // Message.
strings[2], // Extend session.
function() {
touchSession();
warningDisplayed = false;
- // First wait is half the session timeout.
- setTimeout(checkSession, checkFrequency * 5);
+ // First wait is minimum of remaining time or half of the session timeout.
+ setTimeout(checkSession, firstWait);
return true;
},
function() {
- warningDisplayed = false;
+ // User has cancelled notification.
setTimeout(checkSession, checkFrequency);
}
);
- return true;
- }).fail(Notification.exception);
+ }).then(modal => {
+ // If we don't extend the session before the timeout - warn.
+ setTimeout(timeoutSessionExpired, args.timeremaining * 1000, modal);
+ return;
+ }).catch(Notification.exception);
} else {
setTimeout(checkSession, checkFrequency);
}
if (keepAliveFrequency > 0) {
setTimeout(touchSession, keepAliveFrequency);
} else {
- // First wait is half the session timeout.
- setTimeout(checkSession, checkFrequency * 5);
+ // First wait is minimum of remaining time or half of the session timeout.
+ setTimeout(checkSession, firstWait);
}
};
* @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
$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':
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();
+ $this->add_value(trim($value), $allowscreation);
+ }
+ }
- // 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();
+ /**
+ * Add a value to the autocomplete.
+ *
+ * @param string $value
+ * @param bool $allowscreation
+ */
+ protected function add_value(string $value, bool $allowscreation): void {
+ $value = trim($value);
- // Press the escape to close the autocomplete suggestions list.
- behat_base::type_keys($this->session, [behat_keys::ESCAPE]);
- $this->wait_for_pending_js();
+ // 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();
}
}
// 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();
}
$where = "(lastruntime IS NULL OR lastruntime < :timestart1)
AND (nextruntime IS NULL OR nextruntime < :timestart2)
- AND disabled = 0
ORDER BY lastruntime, id ASC";
$params = array('timestart1' => $timestart, 'timestart2' => $timestart);
$records = $DB->get_records_select('task_scheduled', $where, $params);
foreach ($records as $record) {
+ $task = self::scheduled_task_from_record($record);
+ // Safety check in case the task in the DB does not match a real class (maybe something was uninstalled).
+ // Also check to see if task is disabled or enabled after applying overrides.
+ if (!$task || $task->get_disabled()) {
+ continue;
+ }
+
if ($lock = $cronlockfactory->get_lock(($record->classname), 0)) {
$classname = '\\' . $record->classname;
- $task = self::scheduled_task_from_record($record);
- // Safety check in case the task in the DB does not match a real class (maybe something was uninstalled).
- if (!$task) {
- $lock->release();
- continue;
- }
$task->set_lock($lock);
{{{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}}}
{{{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}}}
{{{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}}}
</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 }}
if ($fix) {
$auths = array_unique($auths);
+ $oldauthconfig = implode(',', $auths);
foreach ($auths as $k => $authname) {
- if (!exists_auth_plugin($authname) or in_array($authname, $default)) {
+ $authplugindoesnotexist = !exists_auth_plugin($authname);
+ if ($authplugindoesnotexist || in_array($authname, $default)) {
+ if ($authplugindoesnotexist) {
+ debugging(get_string('authpluginnotfound', 'debug', $authname));
+ }
unset($auths[$k]);
}
}
$newconfig = implode(',', $auths);
if (!isset($CFG->auth) or $newconfig != $CFG->auth) {
+ add_to_config_log('auth', $oldauthconfig, $newconfig, 'core');
set_config('auth', $newconfig);
}
}
public function add_class($class) {
$this->attributes['class'] .= ' '.$class;
}
+
+ /**
+ * Check if the block is a fake block.
+ *
+ * @return boolean
+ */
+ public function is_fake() {
+ return isset($this->attributes['data-block']) && $this->attributes['data-block'] == '_fake';
+ }
}
* Output all the blocks in a particular region.
*
* @param string $region the name of a region on this page.
+ * @param boolean $fakeblocksonly Output fake block only.
* @return string the HTML to be output.
*/
- public function blocks_for_region($region) {
+ public function blocks_for_region($region, $fakeblocksonly = false) {
$blockcontents = $this->page->blocks->get_content_for_region($region, $this);
$lastblock = null;
$zones = array();
foreach ($blockcontents as $bc) {
if ($bc instanceof block_contents) {
+ if ($fakeblocksonly && !$bc->is_fake()) {
+ // Skip rendering real blocks if we only want to show fake blocks.
+ continue;
+ }
$output .= $this->block($bc, $region);
$lastblock = $bc->title;
} else if ($bc instanceof block_move_target) {
- $output .= $this->block_move_target($bc, $zones, $lastblock, $region);
+ if (!$fakeblocksonly) {
+ $output .= $this->block_move_target($bc, $zones, $lastblock, $region);
+ }
} else {
throw new coding_exception('Unexpected type of thing (' . get_class($bc) . ') found in list of block contents.');
}
*
* @since Moodle 2.5.1 2.6
* @param string $region The region to get HTML for.
+ * @param array $classes Wrapping tag classes.
+ * @param string $tag Wrapping tag.
+ * @param boolean $fakeblocksonly Include fake blocks only.
* @return string HTML.
*/
- public function blocks($region, $classes = array(), $tag = 'aside') {
+ public function blocks($region, $classes = array(), $tag = 'aside', $fakeblocksonly = false) {
$displayregion = $this->page->apply_theme_region_manipulations($region);
$classes = (array)$classes;
$classes[] = 'block-region';
'data-droptarget' => '1'
);
if ($this->page->blocks->region_has_content($displayregion, $this)) {
- $content = $this->blocks_for_region($displayregion);
+ $content = $this->blocks_for_region($displayregion, $fakeblocksonly);
} else {
$content = '';
}
* @param string $region
* @param array $classes
* @param string $tag
+ * @param boolean $fakeblocksonly
* @return string
*/
- public function blocks($region, $classes = array(), $tag = 'aside') {
+ public function blocks($region, $classes = array(), $tag = 'aside', $fakeblocksonly = false) {
return '';
}
* Does nothing. The maintenance renderer cannot produce blocks.
*
* @param string $region
+ * @param boolean $fakeblocksonly Output fake block only.
* @return string
*/
- public function blocks_for_region($region) {
+ public function blocks_for_region($region, $fakeblocksonly = false) {
return '';
}
}
$this->M_cfg = array(
- 'wwwroot' => $CFG->wwwroot,
- 'sesskey' => sesskey(),
- 'sessiontimeout' => $CFG->sessiontimeout,
- 'themerev' => theme_get_revision(),
- 'slasharguments' => (int)(!empty($CFG->slasharguments)),
- 'theme' => $page->theme->name,
- 'iconsystemmodule' => $iconsystem->get_amd_name(),
- 'jsrev' => $this->get_jsrev(),
- 'admin' => $CFG->admin,
- 'svgicons' => $page->theme->use_svg_icons(),
- 'usertimezone' => usertimezone(),
- 'contextid' => $contextid,
- 'langrev' => get_string_manager()->get_revision(),
- 'templaterev' => $this->get_templaterev()
+ 'wwwroot' => $CFG->wwwroot,
+ 'sesskey' => sesskey(),
+ 'sessiontimeout' => $CFG->sessiontimeout,
+ 'sessiontimeoutwarning' => $CFG->sessiontimeoutwarning,
+ 'themerev' => theme_get_revision(),
+ 'slasharguments' => (int)(!empty($CFG->slasharguments)),
+ 'theme' => $page->theme->name,
+ 'iconsystemmodule' => $iconsystem->get_amd_name(),
+ 'jsrev' => $this->get_jsrev(),
+ 'admin' => $CFG->admin,
+ 'svgicons' => $page->theme->use_svg_icons(),
+ 'usertimezone' => usertimezone(),
+ 'contextid' => $contextid,
+ 'langrev' => get_string_manager()->get_revision(),
+ 'templaterev' => $this->get_templaterev()
);
if ($CFG->debugdeveloper) {
$this->M_cfg['developerdebug'] = true;
if (empty($CFG->sessiontimeout)) {
$CFG->sessiontimeout = 8 * 60 * 60;
}
+// Set sessiontimeoutwarning 20 minutes.
+if (empty($CFG->sessiontimeoutwarning)) {
+ $CFG->sessiontimeoutwarning = 20 * 60;
+}
\core\session\manager::start();
// Set default content type and encoding, developers are still required to use
}}
{{#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}}">▼</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}}
class="form-control withclear"
placeholder="{{$placeholder}}{{#str}} search, core {{/str}}{{/placeholder}}"
name="search"
+ value="{{$value}}{{/value}}"
autocomplete="off"
>
<button
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() === '') {
input.focus();
});
});
-{{/js}}
\ No newline at end of file
+{{/js}}
$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']);
+ }
}
/**
$this->assertTrue(in_array(['name' => 'class', 'value' => $labelclass], $data->labelattributes));
$this->assertTrue(in_array(['name' => 'style', 'value' => $labelstyle], $data->labelattributes));
}
+
+ /**
+ * Data provider for test_block_contents_is_fake().
+ *
+ * @return array
+ */
+ public function block_contents_is_fake_provider() {
+ return [
+ 'Null' => [null, false],
+ 'Not set' => [false, false],
+ 'Fake' => ['_fake', true],
+ 'Real block' => ['activity_modules', false],
+ ];
+ }
+
+ /**
+ * Test block_contents is_fake() method.
+ *
+ * @dataProvider block_contents_is_fake_provider
+ * @param mixed $value Value for the data-block attribute
+ * @param boolean $expected The expected result
+ */
+ public function test_block_contents_is_fake($value, $expected) {
+ $bc = new block_contents(array());
+ if ($value !== false) {
+ $bc->attributes['data-block'] = $value;
+ }
+ $this->assertEquals($expected, $bc->is_fake());
+ }
}
}
}
+ /**
+ * Check that an overridden task is sent to be processed.
+ */
+ public function test_scheduled_task_overridden_task_can_run(): void {
+ global $CFG, $DB;
+
+ $this->resetAfterTest();
+
+ // Delete all existing scheduled tasks.
+ $DB->delete_records('task_scheduled');
+
+ // Add overrides to the config.
+ $CFG->scheduled_tasks = [
+ '\core\task\scheduled_test_task' => [
+ 'disabled' => 1
+ ],
+ '\core\task\scheduled_test2_task' => [
+ 'disabled' => 0
+ ],
+ ];
+
+ // A task that runs once per hour.
+ $record = new stdClass();
+ $record->component = 'test_scheduled_task';
+ $record->classname = '\core\task\scheduled_test_task';
+ $record->disabled = 0;
+ $DB->insert_record('task_scheduled', $record);
+
+ // And disabled test.
+ $record->classname = '\core\task\scheduled_test2_task';
+ $record->disabled = 1;
+ $DB->insert_record('task_scheduled', $record);
+
+ $now = time();
+
+ $scheduledtask = \core\task\manager::get_next_scheduled_task($now);
+ $this->assertInstanceOf('\core\task\scheduled_test2_task', $scheduledtask);
+ $scheduledtask->execute();
+ \core\task\manager::scheduled_task_complete($scheduledtask);
+ }
+
/**
* Assert that the specified tasks are equal.
*
}
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 . ' ', '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') == ';') {
} 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'));
}
$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';
$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';
$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';
$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';
$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> (OpenOffice)';
$string['openafterclose'] = 'You have specified an open date after the close date';
$string['optionaldescription'] = 'Short description (optional)';
$string['optionalfilename'] = 'Filename (optional)';
$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';
$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';
$string['viewtodate'] = 'Read only to';
$string['viewtodatevalidation'] = 'The read only to date cannot be before the read only from date.';
$string['wrongdataid'] = 'Wrong data id provided';
+
+// Deprecated since Moodle 3.11.
+$string['unsupportedexport'] = '({$a->fieldtype}) cannot be exported.';
+unsupportedexport,mod_data
| 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"
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
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"
Then "Feedback" "block" should not exist
And I am on "Course 1" course homepage
And "Feedback" "block" should not exist
- And I log out
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"
$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)) {
$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 ?? '';
$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;
FROM {lti_types}
WHERE coursevisible $coursevisiblesql
AND ($coursecond)
- AND state = :active";
+ AND state = :active
+ ORDER BY name ASC";
return $DB->get_records_sql($query,
array('siteid' => $SITE->id, 'courseid' => $courseid, 'active' => LTI_TOOL_STATE_CONFIGURED) + $coursevisparams);
'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())
]
$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);
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.
*/
$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']));
+ }
+
}
/** @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);
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);
});
};
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.
*
*/
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');
};
/**
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);
}
};
* @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--;
}
};
});
};
+ /**
+ * 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.
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"
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"
// Embeded pages, like iframe/object embeded in moodleform - it needs as much space as possible.
'embedded' => array(
'file' => 'embedded.php',
- 'regions' => array()
+ 'regions' => array('side-pre'),
+ 'defaultregion' => 'side-pre',
),
// Used during upgrade and install, and for the 'This site is undergoing maintenance' message.
// This must not have any blocks, links, or API calls that would lead to database or cache interaction.
defined('MOODLE_INTERNAL') || die();
+$fakeblockshtml = $OUTPUT->blocks('side-pre', array(), 'aside', true);
+$hasfakeblocks = strpos($fakeblockshtml, 'data-block="_fake"') !== false;
+
$templatecontext = [
- 'sitename' => format_string($SITE->shortname, true, ['context' => context_course::instance(SITEID), "escape" => false]),
- 'output' => $OUTPUT
+ 'output' => $OUTPUT,
+ 'hasfakeblocks' => $hasfakeblocks,
+ 'fakeblocks' => $fakeblockshtml,
];
echo $OUTPUT->render_from_template('theme_boost/embedded', $templatecontext);
border: 2px dashed $gray-800;
margin: 4px 0;
}
+
+.pagelayout-embedded {
+ .has-fake-blocks {
+ padding: 1rem;
+ display: flex;
+ }
+
+ .has-fake-blocks .embedded-main {
+ order: 0;
+ width: calc(100% - #{$blocks-column-width});
+ margin-right: 1rem;
+ }
+
+ .embedded-blocks {
+ order: 1;
+ width: $blocks-column-width;
+ }
+
+ @media (max-width: 767.98px) {
+ .has-fake-blocks {
+ display: block;
+ }
+ .has-fake-blocks .embedded-main {
+ width: 100%;
+ }
+ .embedded-blocks {
+ width: 100%;
+ }
+ }
+}
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;
.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);
}
}
.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; }
border: 2px dashed #343a40;
margin: 4px 0; }
+.pagelayout-embedded .has-fake-blocks {
+ padding: 1rem;
+ display: flex; }
+
+.pagelayout-embedded .has-fake-blocks .embedded-main {
+ order: 0;
+ width: calc(100% - 360px);
+ margin-right: 1rem; }
+
+.pagelayout-embedded .embedded-blocks {
+ order: 1;
+ width: 360px; }
+
+@media (max-width: 767.98px) {
+ .pagelayout-embedded .has-fake-blocks {
+ display: block; }
+ .pagelayout-embedded .has-fake-blocks .embedded-main {
+ width: 100%; }
+ .pagelayout-embedded .embedded-blocks {
+ width: 100%; } }
+
.navbar {
max-height: 50px; }
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; }
Context variables required for this template:
* output - The core renderer for the page
+ * hasfakeblocks - true if there are fake blocks on this page
+ * fakeblocks - HTML for the fake blocks
Example context (json):
{
"output": {
"doctype": "<!DOCTYPE html>",
+ "htmlattributes": "The attributes that should be added to the <html> tag",
"page_title": "Test page",
"favicon": "favicon.ico",
- "main_content": "<h1>Headings make html validators happier</h1>"
- }
+ "standard_head_html": "The standard tags that should be included in the <head> tag",
+ "body_attributes": "The attributes to use within the body tag",
+ "standard_top_of_body_html": "The standard tags that should be output just inside the start of the <body> tag",
+ "main_content": "<h1>Headings make html validators happier</h1>",
+ "standard_end_of_body_html": "The standard tags that should be output after everything else"
+ },
+ "hasfakeblocks": true,
+ "fakeblocks": "<h2>Fake blocks html goes here</h2>"
}
}}
{{{ output.doctype }}}
{{> core/local/toast/wrapper}}
{{{ output.standard_top_of_body_html }}}
-<div id="page">
- <div id="page-content" class="d-block">
+<div id="page" {{#hasfakeblocks}}class="has-fake-blocks"{{/hasfakeblocks}}>
+ {{#hasfakeblocks}}
+ <section class="embedded-blocks" aria-label="{{#str}}blocks{{/str}}">
+ {{{ fakeblocks }}}
+ </section>
+ {{/hasfakeblocks}}
+ <section class="embedded-main">
{{{ output.main_content }}}
- </div>
+ </section>
</div>
{{{ output.standard_end_of_body_html }}}
</body>
.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; }
border: 2px dashed #343a40;
margin: 4px 0; }
+.pagelayout-embedded .has-fake-blocks {
+ padding: 1rem;
+ display: flex; }
+
+.pagelayout-embedded .has-fake-blocks .embedded-main {
+ order: 0;
+ width: calc(100% - 360px);
+ margin-right: 1rem; }
+
+.pagelayout-embedded .embedded-blocks {
+ order: 1;
+ width: 360px; }
+
+@media (max-width: 767.98px) {
+ .pagelayout-embedded .has-fake-blocks {
+ display: block; }
+ .pagelayout-embedded .has-fake-blocks .embedded-main {
+ width: 100%; }
+ .pagelayout-embedded .embedded-blocks {
+ width: 100%; } }
+
.navbar {
max-height: 50px; }
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; }
This files describes API changes in /theme/* themes,
information provided here is intended especially for theme designer.
+=== 3.11 ===
+* The classname 'viewmode-cobmined' in course/management.php has been changed to 'viewmode-combined'
+
=== 3.10 ===
* The Bootstrap legacy css utilities from Bootstrap 2 and 4alpha have been removed.
The syntax for the new Bootstrap 4.5 utility classes is {property}{sides}-{breakpoint}-{size} for sm, md, lg, and xl.
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"
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"
| Course 2 | C2 | 0 | ##4 months ago## |
| Course 3 | C3 | 0 | ##3 months ago## |
And the following "users" exist:
- | username | firstname | lastname | email | idnumber | country | city | maildisplay |
- | student1 | Student | 1 | student1@example.com | SID1 | | SCITY1 | 0 |
- | student2 | Student | 2 | student2@example.com | SID2 | GB | SCITY2 | 1 |
- | student3 | Student | 3 | student3@example.com | SID3 | AU | SCITY3 | 0 |
- | student4 | Student | 4 | student4@moodle.com | SID4 | AT | SCITY4 | 0 |
- | student5 | Trendy | Learnson | trendy@learnson.com | SID5 | AU | SCITY5 | 0 |
- | teacher1 | Teacher | 1 | teacher1@example.org | TID1 | US | TCITY1 | 0 |
+ | username | firstname | lastname | email | idnumber | country | city | maildisplay |
+ | student1 | Student | 1 | student1@example.com | SID1 | | SCITY1 | 0 |
+ | student2 | Student | 2 | student2@example.com | SID2 | GB | SCITY2 | 1 |
+ | student3 | Student | 3 | student3@example.com | SID3 | AU | SCITY3 | 0 |
+ | student4 | Student | 4 | student4@moodle.com | SID4 | AT | SCITY4 | 0 |
+ | student5 | Trendy | Learnson | trendy@learnson.com | SID5 | AU | SCITY5 | 0 |
+ | patricia | Patricia | Pea | patricia.pea1@example.org | TID1 | US | TCITY1 | 0 |
And the following "course enrolments" exist:
| user | course | role | status | timeend |
| student1 | C1 | student | 0 | |
| student1 | C3 | student | 0 | |
| student2 | C3 | student | 0 | |
| student3 | C3 | student | 0 | |
- | teacher1 | C1 | editingteacher | 0 | |
- | teacher1 | C2 | editingteacher | 0 | |
- | teacher1 | C3 | editingteacher | 0 | |
+ | patricia | C1 | editingteacher | 0 | |
+ | patricia | C2 | editingteacher | 0 | |
+ | patricia | C3 | editingteacher | 0 | |
And the following "last access times" exist:
| user | course | lastaccess |
| student1 | C1 | ##yesterday## |
@javascript
Scenario: No filters applied
- Given I log in as "teacher1"
+ Given I log in as "patricia"
And I am on "Course 1" course homepage
And I navigate to course participants
Then I should see "Student 1" in the "participants" "table"
And I should see "Student 2" in the "participants" "table"
And I should see "Student 3" in the "participants" "table"
- And I should see "Teacher 1" in the "participants" "table"
+ And I should see "Patricia Pea" in the "participants" "table"
@javascript
Scenario Outline: Filter users for a course with a single value
- Given I log in as "teacher1"
+ Given I log in as "patricia"
And I am on "Course 1" course homepage
And I navigate to course participants
And I set the field "Match" in the "Filter 1" "fieldset" to "<matchtype>"
And I set the field "type" in the "Filter 1" "fieldset" to "<filtertype>"
- And I click on ".form-autocomplete-downarrow" "css_element" in the "Filter 1" "fieldset"
- And I click on "<filtervalue>" "list_item"
+ And I set the field "Type or select..." in the "Filter 1" "fieldset" to "<filtervalue>"
When I click on "Apply filters" "button"
Then I should see "<expected1>" in the "participants" "table"
And I should see "<expected2>" in the "participants" "table"
# Note the 'XX-IGNORE-XX' elements are for when there is less than 2 'not expected' items.
Examples:
- | matchtype | filtertype | filtervalue | expected1 | expected2 | expected3 | notexpected1 | notexpected2 |
- | Any | Groups | No group | Student 1 | Student 4 | Teacher 1 | Student 2 | Student 3 |
- | All | Groups | No group | Student 1 | Student 4 | Teacher 1 | Student 2 | Student 3 |
- | None | Groups | No group | Student 2 | Student 3 | | Student 1 | Teacher 1 |
- | Any | Role | Student | Student 1 | Student 2 | Student 3 | Teacher 1 | XX-IGNORE-XX |
- | All | Role | Student | Student 1 | Student 2 | Student 3 | Teacher 1 | XX-IGNORE-XX |
- | None | Role | Student | Teacher 1 | | | Student 1 | Student 2 |
- | Any | Status | Active | Student 1 | Student 3 | Teacher 1 | Student 2 | Student 4 |
- | All | Status | Active | Student 1 | Student 3 | Teacher 1 | Student 2 | Student 4 |
- | None | Status | Active | Student 2 | Student 4 | | Student 1 | Student 3 |
- | Any | Inactive for more than | 1 week | Student 3 | Student 4 | | Student 1 | Student 2 |
- | All | Inactive for more than | 1 week | Student 3 | Student 4 | | Student 1 | Student 2 |
- | None | Inactive for more than | 1 week | Student 1 | Student 2 | Teacher 1 | Student 3 | XX-IGNORE-XX |
+ | matchtype | filtertype | filtervalue | expected1 | expected2 | expected3 | notexpected1 | notexpected2 |
+ | Any | Groups | No group | Student 1 | Student 4 | Patricia Pea | Student 2 | Student 3 |
+ | All | Groups | No group | Student 1 | Student 4 | Patricia Pea | Student 2 | Student 3 |
+ | None | Groups | No group | Student 2 | Student 3 | | Student 1 | Patricia Pea |
+ | Any | Role | Student | Student 1 | Student 2 | Student 3 | Patricia Pea | XX-IGNORE-XX |
+ | All | Role | Student | Student 1 | Student 2 | Student 3 | Patricia Pea | XX-IGNORE-XX |
+ | None | Role | Student | Patricia Pea | | | Student 1 | Student 2 |
+ | Any | Status | Active | Student 1 | Student 3 | Patricia Pea | Student 2 | Student 4 |
+ | All | Status | Active | Student 1 | Student 3 | Patricia Pea | Student 2 | Student 4 |
+ | None | Status | Active | Student 2 | Student 4 | | Student 1 | Student 3 |
+ | Any | Inactive for more than | 1 week | Student 3 | Student 4 | | Student 1 | Student 2 |
+ | All | Inactive for more than | 1 week | Student 3 | Student 4 | | Student 1 | Student 2 |
+ | None | Inactive for more than | 1 week | Student 1 | Student 2 | Patricia Pea | Student 3 | XX-IGNORE-XX |
@javascript
Scenario Outline: Filter users for a course with multiple values for a single filter
- Given I log in as "teacher1"
+ Given I log in as "patricia"
And I am on "Course 1" course homepage
And I navigate to course participants
And I set the field "Match" in the "Filter 1" "fieldset" to "<matchtype>"
And I set the field "type" in the "Filter 1" "fieldset" to "<filtertype>"
- And I click on ".form-autocomplete-downarrow" "css_element" in the "Filter 1" "fieldset"
- And I click on "<filtervalue1>" "list_item"
- And I click on "<filtervalue2>" "list_item"
+ And I set the field "Type or select..." in the "Filter 1" "fieldset" to "<filtervalue1>,<filtervalue2>"
When I click on "Apply filters" "button"
Then I should see "<expected1>" in the "participants" "table"
And I should see "<expected2>" in the "participants" "table"
| matchtype | filtertype | filtervalue1 | filtervalue2 | expected1 | expected2 | expected3 | notexpected1 | notexpected2 |
| Any | Groups | Group 1 | Group 2 | Student 2 | Student 3 | | Student 1 | XX-IGNORE-XX |
| All | Groups | Group 1 | Group 2 | Student 2 | | | Student 1 | Student 3 |
- | None | Groups | Group 1 | Group 2 | Student 1 | Teacher 1 | | Student 2 | Student 3 |
+ | None | Groups | Group 1 | Group 2 | Student 1 | Patricia Pea | | Student 2 | Student 3 |
@javascript
Scenario Outline: Filter users which are group members in several courses
- Given I log in as "teacher1"
+ Given I log in as "patricia"
And I am on "Course 3" course homepage
And I navigate to course participants
And I set the field "type" in the "Filter 1" "fieldset" to "<filtertype>"
- And I click on ".form-autocomplete-downarrow" "css_element" in the "Filter 1" "fieldset"
- And I click on "<filtervalue>" "list_item"
+ And I set the field "Type or select..." in the "Filter 1" "fieldset" to "<filtervalue>"
When I click on "Apply filters" "button"
Then I should see "<expected1>" in the "participants" "table"
And I should see "<expected2>" in the "participants" "table"
@javascript
Scenario: In separate groups mode, a student in a single group can only view and filter by users in their own group
- Given I log in as "teacher1"
+ Given I log in as "patricia"
And I am on "Course 1" course homepage
And I navigate to course participants
+
# Unsuspend student 2 for to improve coverage of this test.
And I click on "Edit enrolment" "icon" in the "Student 2" "table_row"
And I set the field "Status" to "Active"
And I click on "Save changes" "button"
And I log out
+
+ # Default view should have groups filter pre-set.
+ # Match:
+ # Groups Any ["Group 2"].
+
When I log in as "student3"
And I am on "Course 1" course homepage
And I navigate to course participants
- # Default view should have groups filter pre-set.
+
Then I should see "Student 2" in the "participants" "table"
And I should see "Student 3" in the "participants" "table"
- And I should not see "Student 1" in the "participants" "table"
And I should see "Group 2" in the "Filter 1" "fieldset"
+ But I should not see "Student 1" in the "participants" "table"
And I should not see "Group 1" in the "Filter 1" "fieldset"
- And I should see "Student 2" in the "participants" "table"
- And I should see "Student 3" in the "participants" "table"
- And I should not see "Student 1" in the "participants" "table"
+
# Testing result of removing groups filter row.
- And I click on "Remove filter row" "button" in the "Filter 1" "fieldset"
- And I should see "Student 2" in the "participants" "table"
+ # Match any available user.
+ When I click on "Remove filter row" "button" in the "Filter 1" "fieldset"
+
+ Then I should see "Student 2" in the "participants" "table"
And I should see "Student 3" in the "participants" "table"
- And I should not see "Student 1" in the "participants" "table"
+ But I should not see "Student 1" in the "participants" "table"
+
# Testing result of applying groups filter manually.
- And I set the field "Match" in the "Filter 1" "fieldset" to "Any"
+ # Match:
+ # Group Any ["Group 2"].
+
+ # Match Groups Any ["Group 2"]
+ Given I set the field "Match" in the "Filter 1" "fieldset" to "Any"
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 should see "Group 2" in the ".form-autocomplete-suggestions" "css_element"
+ And I set the field "Type or select..." in the "Filter 1" "fieldset" to "Group 2"
+
+ And I open the autocomplete suggestions list in the "Filter 1" "fieldset"
And I should not see "Group 1" in the ".form-autocomplete-suggestions" "css_element"
- And I click on "Group 2" "list_item"
+
And I click on "Apply filters" "button"
- And I should see "Student 2" in the "participants" "table"
+
+ Then I should see "Student 2" in the "participants" "table"
And I should see "Student 3" in the "participants" "table"
- And I should not see "Student 1" in the "participants" "table"
+ But I should not see "Student 1" in the "participants" "table"
+
# Testing result of removing groups filter by clearing all filters.
- And I click on "Clear filters" "button"
- And I should see "Student 2" in the "participants" "table"
+ # Match any available user.
+ When I click on "Clear filters" "button"
+
+ Then I should see "Student 2" in the "participants" "table"
And I should see "Student 3" in the "participants" "table"
- And I should not see "Student 1" in the "participants" "table"
+ But I should not see "Student 1" in the "participants" "table"
@javascript
Scenario: In separate groups mode, a student in multiple groups can only view and filter by users in their own groups
- Given I log in as "teacher1"
+ Given I log in as "patricia"
And I am on "Course 1" course homepage
And I navigate to course participants
+
# Unsuspend student 2 for to improve coverage of this test.
And I click on "Edit enrolment" "icon" in the "Student 2" "table_row"
And I set the field "Status" to "Active"
And I click on "Save changes" "button"
And I log out
+
When I log in as "student2"
And I am on "Course 1" course homepage
And I navigate to course participants
+
# Default view should have groups filter pre-set.
+ # Match:
+ # Groups Any ["Group 1", "Group 2"].
+
Then I should see "Student 2" in the "participants" "table"
And I should see "Student 3" in the "participants" "table"
And I should not see "Student 1" in the "participants" "table"
And I should see "Student 2" in the "participants" "table"
And I should see "Student 3" in the "participants" "table"
And I should not see "Student 1" in the "participants" "table"
+
# Testing result of removing groups filter row.
- And I click on "Remove filter row" "button" in the "Filter 1" "fieldset"
- And I should see "Student 2" in the "participants" "table"
+ # Match any available user.
+ When I click on "Remove filter row" "button" in the "Filter 1" "fieldset"
+
+ Then I should see "Student 2" in the "participants" "table"
And I should see "Student 3" in the "participants" "table"
- And I should not see "Student 1" in the "participants" "table"
+ But I should not see "Student 1" in the "participants" "table"
+
# Testing result of applying groups filter manually.
+ # Match:
+ # Groups Any ["Group 1"].
+
+ # Match Groups Any ["Group 1"]
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 "Groups"
- And I click on ".form-autocomplete-downarrow" "css_element" in the "Filter 1" "fieldset"
- And I should see "Group 1" in the ".form-autocomplete-suggestions" "css_element"
+
+ And I open the autocomplete suggestions list in the "Filter 1" "fieldset"
And I should see "Group 2" in the ".form-autocomplete-suggestions" "css_element"
- And I click on "Group 1" "list_item"
+ And I press the escape key
+
+ 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 2" in the "participants" "table"
- And I should not see "Student 1" in the "participants" "table"
+ But I should not see "Student 1" in the "participants" "table"
And I should not see "Student 3" in the "participants" "table"
+
# Testing result of removing groups filter by clearing all filters.
- And I click on "Clear filters" "button"
- And I should see "Student 2" in the "participants" "table"
+ # Match any available user.
+ When I click on "Clear filters" "button"
+
+ Then I should see "Student 2" in the "participants" "table"
And I should see "Student 3" in the "participants" "table"
- And I should not see "Student 1" in the "participants" "table"
+ But I should not see "Student 1" in the "participants" "table"
@javascript
Scenario: Filter users who have no role in a course
- Given I log in as "teacher1"
+ Given I log in as "patricia"
And I am on "Course 1" course homepage
And I navigate to course participants
+
+ # Remove the user role.
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 "Save changes" "link"
- 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 "No roles" "list_item"
- When I click on "Apply filters" "button"
+
+ # Match:
+ # Roles All ["No roles"].
+
+ # Match Roles All ["No roles"].
+ When I set the field "type" in the "Filter 1" "fieldset" to "Roles"
+ And I set the field "Type or select..." in the "Filter 1" "fieldset" to "No roles"
+
+ And I click on "Apply filters" "button"
+
Then I should see "Student 1" in the "participants" "table"
- And I should not see "Student 2" in the "participants" "table"
+ But I should not see "Student 2" in the "participants" "table"
And I should not see "Student 3" in the "participants" "table"
And I should not see "Student 4" in the "participants" "table"
- And I should not see "Teacher 1" in the "participants" "table"
+ And I should not see "Patricia Pea" in the "participants" "table"
@javascript
Scenario: Multiple filters applied (All filterset match type)
- Given I log in as "teacher1"
+ Given I log in as "patricia"
And I am on "Course 1" course homepage
And I navigate to course participants
- And I set the field "Match" in the "Filter 1" "fieldset" to "All"
+
+ # Match Any:
+ # Roles All ["Student"] and
+ # Status Any ["Active"].
+
+ # Match Roles All ["Student"].
+ When 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"
+
+ # Match Status All ["Active"].
And I click on "Add condition" "button"
# Set filterset to match all.
And I set the field "Match" to "All"
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"
- When I click on "Apply filters" "button"
+ And I set the field "Type or select..." in the "Filter 2" "fieldset" to "Active"
+
+ And I click on "Apply filters" "button"
+
Then I should see "Student 1" in the "participants" "table"
And I should see "Student 3" in the "participants" "table"
- And I should not see "Student 2" in the "participants" "table"
+ But I should not see "Student 2" in the "participants" "table"
And I should not see "Student 4" in the "participants" "table"
- And I should not see "Teacher 1" in the "participants" "table"
- # Add more filters.
- And I click on "Add condition" "button"
+ And I should not see "Patricia Pea" in the "participants" "table"
+
+ # Match Any:
+ # Roles All ["Student"]; and
+ # Status Any ["Active"]; and
+ # Enrolment method Any ["Manual"]; and
+ # Groups Any ["Group 2"].
+
+ # Match enrolment method Any ["Manual"]
+ When I click on "Add condition" "button"
And I set the field "Match" in the "Filter 3" "fieldset" to "Any"
And I set the field "type" in the "Filter 3" "fieldset" to "Enrolment methods"
- And I click on ".form-autocomplete-downarrow" "css_element" in the "Filter 3" "fieldset"
- And I click on "Manual enrolments" "list_item"
+ And I set the field "Type or select..." in the "Filter 3" "fieldset" to "Manual enrolments"
+
+ # Match Groups Any ["Group 2"]
And I click on "Add condition" "button"
And I set the field "Match" in the "Filter 4" "fieldset" to "All"
And I set the field "type" in the "Filter 4" "fieldset" to "Groups"
- And I click on ".form-autocomplete-downarrow" "css_element" in the "Filter 4" "fieldset"
- And I click on "Group 2" "list_item"
+ And I set the field "Type or select..." in the "Filter 4" "fieldset" to "Group 2"
And I click on "Apply filters" "button"
- And I should see "Student 3" in the "participants" "table"
- But I should not see "Teacher 1" in the "participants" "table"
+
+ Then I should see "Student 3" in the "participants" "table"
+ But I should not see "Patricia Pea" in the "participants" "table"
And I should not see "Student 1" in the "participants" "table"
And I should not see "Student 2" in the "participants" "table"
And I should not see "Student 4" in the "participants" "table"
+
# Change the active status filter to inactive.
+ # Match Any:
+ # Roles All ["Student"]; and
+ # Status Any ["Inactive"]; and
+ # Enrolment method Any ["Manual"]; and
+ # Groups Any ["Group 2"].
+
+ # Match Status All ["Inactive"].
And I click on "Active" "autocomplete_selection"
- And I click on ".form-autocomplete-downarrow" "css_element" in the "Filter 2" "fieldset"
- And I click on "Inactive" "list_item"
+ And I set the field "Type or select..." in the "Filter 2" "fieldset" to "Inactive"
And I click on "Apply filters" "button"
+
Then I should see "Student 2" in the "participants" "table"
But I should not see "Student 4" in the "participants" "table"
And I should not see "Student 1" in the "participants" "table"
And I should not see "Student 3" in the "participants" "table"
- And I should not see "Teacher 1" in the "participants" "table"
+ And I should not see "Patricia Pea" in the "participants" "table"
+
# Set both statuses (match any).
- And I click on ".form-autocomplete-downarrow" "css_element" in the "Filter 2" "fieldset"
- And I click on "Active" "list_item"
+ # Match Any:
+ # Roles All ["Student"]; and
+ # Status Any ["Active", "Inactive"]; and
+ # Enrolment method Any ["Manual"]; and
+ # Groups Any ["Group 2"].
+
+ # Match Status Any ["Active", "Inactive"].
+ And I set the field "Type or select..." in the "Filter 2" "fieldset" to "Active,Inactive"
And I click on "Apply filters" "button"
- And I should see "Student 2" in the "participants" "table"
+
+ Then I should see "Student 2" in the "participants" "table"
And I should see "Student 3" in the "participants" "table"
- And I should not see "Student 1" in the "participants" "table"
+ But I should not see "Student 1" in the "participants" "table"
And I should not see "Student 4" in the "participants" "table"
- # Switch to match all.
- And I set the field "Match" in the "Filter 2" "fieldset" to "All"
+
+ # Set both statuses (match all).
+ # Match Any:
+ # Roles All ["Student"]; and
+ # Status Any ["Active", "Inactive"]; and
+ # Enrolment method Any ["Manual"]; and
+ # Groups Any ["Group 2"].
+
+ # Match Status All ["Active", "Inactive"].
+ When I set the field "Match" in the "Filter 2" "fieldset" to "All"
And I click on "Apply filters" "button"
- And I should see "Nothing to display"
+
+ Then I should see "Nothing to display"
@javascript
Scenario: Multiple filters applied (Any filterset match type)
- Given I log in as "teacher1"
- #Avoid 'Teacher' list item collisions with profile dropdown.
- And I open my profile in edit mode
- And I set the field "First name" to "Patricia"
- And I press "Update profile"
+ Given I log in as "patricia"
And I am on "Course 1" course homepage
And I navigate to course participants
- And I set the field "Match" in the "Filter 1" "fieldset" to "All"
+
+ # Match Any:
+ # Roles All ["Teacher"] and
+ # Status Any ["Active"].
+
+ # Match Roles all Roles ["Teacher"].
+ When 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 "Teacher" "list_item"
+ And I set the field "Type or select..." in the "Filter 1" "fieldset" to "Teacher"
+
+ # Match Status Any ["Active"].
And I click on "Add condition" "button"
- # Set filterset to match any.
- And I set the field "Match" to "Any"
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"
- When I click on "Apply filters" "button"
+ And I set the field "Type or select..." in the "Filter 2" "fieldset" to "Active"
+
+ # Set filterset to match any.
+ And I set the field "Match" to "Any"
+ And I click on "Apply filters" "button"
+
Then I should see "Student 1" in the "participants" "table"
- And I should see "Patricia 1" in the "participants" "table"
+ And I should see "Patricia Pea" in the "participants" "table"
And I should see "Student 3" in the "participants" "table"
- And I should not see "Student 2" in the "participants" "table"
+ But I should not see "Student 2" in the "participants" "table"
And I should not see "Student 4" in the "participants" "table"
- And I set the field "Match" in the "Filter 2" "fieldset" to "None"
+
+ # Match Any:
+ # Roles All ["Teacher"] and
+ # Status None ["Active"].
+
+ # Match Status Not ["Active"]
+ When I set the field "Match" in the "Filter 2" "fieldset" to "None"
And I click on "Apply filters" "button"
- And I should see "Student 2" in the "participants" "table"
+
+ Then I should see "Student 2" in the "participants" "table"
And I should see "Student 4" in the "participants" "table"
- And I should see "Patricia 1" in the "participants" "table"
- And I should not see "Student 1" in the "participants" "table"
+ And I should see "Patricia Pea" in the "participants" "table"
+ But I should not see "Student 1" in the "participants" "table"
And I should not see "Student 3" in the "participants" "table"
+
# Add a keyword filter.
- And I click on "Add condition" "button"
+ # Match Any:
+ # Roles All ["Teacher"]; and
+ # Status None ["Active"]; and
+ # Keyword Any ["patricia"].
+
+ # Match Keyword Any ["patricia"].
+ When I click on "Add condition" "button"
And I set the field "Match" in the "Filter 3" "fieldset" to "Any"
And I set the field "type" in the "Filter 3" "fieldset" to "Keyword"
- And I set the field "Type..." to "teacher1"
- And I press the enter key
+ And I set the field "Type..." in the "Filter 3" "fieldset" to "patricia"
+
And I click on "Apply filters" "button"
- And I should see "Student 2" in the "participants" "table"
+
+ Then I should see "Student 2" in the "participants" "table"
And I should see "Student 4" in the "participants" "table"
- And I should see "Patricia 1" in the "participants" "table"
- And I should not see "Student 1" in the "participants" "table"
+ And I should see "Patricia Pea" in the "participants" "table"
+ But I should not see "Student 1" in the "participants" "table"
And I should not see "Student 3" in the "participants" "table"
@javascript
Scenario: Multiple filters applied (None filterset match type)
- Given I log in as "teacher1"
- #Avoid 'Teacher' list item collisions with profile dropdown.
- And I open my profile in edit mode
- And I set the field "First name" to "Patricia"
- And I press "Update profile"
+ Given I log in as "patricia"
And I am on "Course 1" course homepage
And I navigate to course participants
- And I set the field "Match" in the "Filter 1" "fieldset" to "All"
+
+ # Match None:
+ # Roles All ["Teacher"] and
+ # Status Any ["Active"].
+
+ # Set the Roles to "All" ["Teacher"].
+ When 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 "Teacher" "list_item"
+ And I set the field "Type or select..." in the "Filter 1" "fieldset" to "Teacher"
+
+ # Set the Status to "Any" ["Active"].
And I click on "Add condition" "button"
- # Set filterset to match none.
- And I set the field "Match" to "None"
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"
- When I click on "Apply filters" "button"
+ And I set the field "Type or select..." in the "Filter 2" "fieldset" to "Active"
+
+ # Set filterset to match None.
+ And I set the field "Match" to "None"
+ And I click on "Apply filters" "button"
+
Then I should see "Student 2" in the "participants" "table"
And I should see "Student 4" in the "participants" "table"
- And I should not see "Student 1" in the "participants" "table"
+ But I should not see "Student 1" in the "participants" "table"
And I should not see "Student 3" in the "participants" "table"
- And I should not see "Patricia 1" in the "participants" "table"
- And I set the field "Match" in the "Filter 2" "fieldset" to "None"
+ And I should not see "Patricia Pea" in the "participants" "table"
+
+ # Match None:
+ # Roles All ["Teacher"] and
+ # Status None ["Active"]
+ # Set the Status to "None ["Active"]
+ When I set the field "Match" in the "Filter 2" "fieldset" to "None"
And I click on "Apply filters" "button"
- And I should see "Student 1" in the "participants" "table"
+ Then I should see "Student 1" in the "participants" "table"
And I should see "Student 3" in the "participants" "table"
- And I should not see "Student 2" in the "participants" "table"
+ But I should not see "Student 2" in the "participants" "table"
And I should not see "Student 4" in the "participants" "table"
- And I should not see "Patricia 1" in the "participants" "table"
- # Add a keyword filter.
- And I click on "Add condition" "button"
- And I set the field "Match" in the "Filter 3" "fieldset" to "Any"
+ And I should not see "Patricia Pea" in the "participants" "table"
+
+ # Match None:
+ # Roles All ["Teacher"] and
+ # Status None ["Active"] and
+ # Keyword Any ["3@"]
+ # Set the Keyword to "Any" ["3@"]
+ When I click on "Add condition" "button"
+ Then I set the field "Match" in the "Filter 3" "fieldset" to "Any"
And I set the field "type" in the "Filter 3" "fieldset" to "Keyword"
- And I set the field "Type..." to "3@"
- And I press the enter key
- And I click on "Apply filters" "button"
- And I should see "Student 1" in the "participants" "table"
+ And I set the field "Type..." in the "Filter 3" "fieldset" to "3@"
+
+ When I click on "Apply filters" "button"
+ Then I should see "Student 1" in the "participants" "table"
And I should not see "Student 2" in the "participants" "table"
And I should not see "Student 3" in the "participants" "table"
And I should not see "Student 4" in the "participants" "table"
- And I should not see "Patricia 1" in the "participants" "table"
- And I set the field "Match" in the "Filter 3" "fieldset" to "None"
+ And I should not see "Patricia Pea" in the "participants" "table"
+
+ # Match None:
+ # Roles All ["Teacher"] and
+ # Status None ["Active"] and
+ # Keyword None ["3@"].
+
+ # Set the Keyword to "None" ["3@"]
+ When I set the field "Match" in the "Filter 3" "fieldset" to "None"
And I click on "Apply filters" "button"
- And I should see "Student 3" in the "participants" "table"
- And I should not see "Student 1" in the "participants" "table"
+
+ Then I should see "Student 3" in the "participants" "table"
+ But I should not see "Student 1" in the "participants" "table"
And I should not see "Student 2" in the "participants" "table"
And I should not see "Student 4" in the "participants" "table"
- And I should not see "Patricia 1" in the "participants" "table"
+ And I should not see "Patricia Pea" in the "participants" "table"
@javascript
Scenario: Filter match by one or more keywords and modified match types
- Given I log in as "teacher1"
+ Given I log in as "patricia"
And I am on "Course 1" course homepage
And I navigate to course participants
- And I set the field "Match" in the "Filter 1" "fieldset" to "Any"
+
+ # Match:
+ # Keyword Any ["1@example"].
+
+ # Set the Keyword to "Any" ["1@example"]
+ When I set the field "Match" in the "Filter 1" "fieldset" to "Any"
And I set the field "type" in the "Filter 1" "fieldset" to "Keyword"
- And I set the field "Type..." to "1@example"
- And I press the enter key
- When I click on "Apply filters" "button"
+ And I set the field "Type..." in the "Filter 1" "fieldset" to "1@example"
+ And I click on "Apply filters" "button"
+
Then I should see "Student 1" in the "participants" "table"
- And I should see "Teacher 1" in the "participants" "table"
- And I should not see "Student 2" in the "participants" "table"
+ And I should see "Patricia Pea" in the "participants" "table"
+
+ But I should not see "Student 2" in the "participants" "table"
And I should not see "Student 3" in the "participants" "table"
And I should not see "Student 4" in the "participants" "table"
- And I set the field "Match" in the "Filter 1" "fieldset" to "None"
- And I click on "Apply filters" "button"
- And I should see "Student 2" in the "participants" "table"
- And I should see "Student 3" in the "participants" "table"
- And I should see "Student 4" in the "participants" "table"
- And I should not see "Student 1" in the "participants" "table"
- And I should not see "Teacher 1" in the "participants" "table"
- And I set the field "Match" in the "Filter 1" "fieldset" to "All"
+
+ # Match:
+ # Keyword All ["1@example"].
+ When I set the field "Match" in the "Filter 1" "fieldset" to "All"
And I click on "Apply filters" "button"
- And I should see "Student 1" in the "participants" "table"
- And I should see "Teacher 1" in the "participants" "table"
- And I should not see "Student 2" in the "participants" "table"
+
+ Then I should see "Student 1" in the "participants" "table"
+ And I should see "Patricia Pea" in the "participants" "table"
+ But I should not see "Student 2" in the "participants" "table"
And I should not see "Student 3" in the "participants" "table"
And I should not see "Student 4" in the "participants" "table"
- And I set the field "Match" in the "Filter 1" "fieldset" to "None"
+
+ # Match:
+ # Keyword None ["1@example"].
+ When I set the field "Match" in the "Filter 1" "fieldset" to "None"
And I click on "Apply filters" "button"
- And I should see "Student 2" in the "participants" "table"
+
+ Then I should see "Student 2" in the "participants" "table"
And I should see "Student 3" in the "participants" "table"
And I should see "Student 4" in the "participants" "table"
- And I should not see "Student 1" in the "participants" "table"
- And I should not see "Teacher 1" in the "participants" "table"
- # Add a second keyword filter value
- And I set the field "Type..." to "moodle"
- And I press the enter key
+ But I should not see "Student 1" in the "participants" "table"
+ And I should not see "Patricia Pea" in the "participants" "table"
+
+ # Set two keyword values.
+ # Match:
+ # Keyword None ["1@example", "moodle"].
+ When I set the field "Type..." to "1@example, moodle"
And I click on "Apply filters" "button"
- And I should see "Student 2" in the "participants" "table"
+
+ Then I should see "Student 2" in the "participants" "table"
And I should see "Student 3" in the "participants" "table"
- And I should not see "Student 1" in the "participants" "table"
- And I should not see "Teacher 1" in the "participants" "table"
+ But I should not see "Student 1" in the "participants" "table"
+ And I should not see "Patricia Pea" in the "participants" "table"
And I should not see "Student 4" in the "participants" "table"
- And I set the field "Match" in the "Filter 1" "fieldset" to "Any"
+
+ # Set two keyword values.
+ # Match:
+ # Keyword Any ["1@example", "moodle"].
+ When I set the field "Match" in the "Filter 1" "fieldset" to "Any"
And I click on "Apply filters" "button"
- And I should see "Student 1" in the "participants" "table"
- And I should see "Teacher 1" in the "participants" "table"
+
+ Then I should see "Student 1" in the "participants" "table"
+ And I should see "Patricia Pea" in the "participants" "table"
And I should see "Student 4" in the "participants" "table"
- And I should not see "Student 2" in the "participants" "table"
+ But I should not see "Student 2" in the "participants" "table"
And I should not see "Student 3" in the "participants" "table"
- And I set the field "Match" in the "Filter 1" "fieldset" to "All"
+
+ # Match:
+ # Keyword All ["1@example", "moodle"].
+ When I set the field "Match" in the "Filter 1" "fieldset" to "All"
And I click on "Apply filters" "button"
- And I should see "Nothing to display"
+
+ Then I should see "Nothing to display"
@javascript
Scenario: Reorder users without losing filter
- Given I log in as "teacher1"
+ Given I log in as "patricia"
And I am on "Course 1" course homepage
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 click on "Student" "list_item"
+
+ When I set the field "type" in the "Filter 1" "fieldset" to "Roles"
+ And I set the field "Type or select..." in the "Filter 1" "fieldset" to "Student"
And I click on "Apply filters" "button"
+
And I should see "Student 1" in the "participants" "table"
And I should see "Student 2" in the "participants" "table"
And I should see "Student 3" in the "participants" "table"
And I should see "Student 4" in the "participants" "table"
- And I should not see "Teacher 1" in the "participants" "table"
+ And I should not see "Patricia Pea" in the "participants" "table"
+
When I click on "Surname" "link"
Then I should see "Student 1" in the "participants" "table"
And I should see "Student 2" in the "participants" "table"
And I should see "Student 3" in the "participants" "table"
And I should see "Student 4" in the "participants" "table"
- And I should not see "Teacher 1" in the "participants" "table"
+ But I should not see "Patricia Pea" in the "participants" "table"
@javascript
Scenario: Only possible to add filter rows for the number of filters available
- Given I log in as "teacher1"
+ Given I log in as "patricia"
And I am on "Course 1" course homepage
And I navigate to course participants
- And I set the field "type" in the "Filter 1" "fieldset" to "Keyword"
+ When I set the field "type" in the "Filter 1" "fieldset" to "Keyword"
And I click on "Add condition" "button"
And I set the field "type" in the "Filter 2" "fieldset" to "Status"
And I click on "Add condition" "button"
And I set the field "type" in the "Filter 5" "fieldset" to "Groups"
And I click on "Add condition" "button"
And I set the field "type" in the "Filter 6" "fieldset" to "Inactive for more than"
- And the "Add condition" "button" should be disabled
+
+ Then the "Add condition" "button" should be disabled
@javascript
Scenario: Rendering filter options for teachers in a course that don't support groups
- Given I log in as "teacher1"
+ Given I log in as "patricia"
And I am on "Course 2" course homepage
When I navigate to course participants
Then I should see "Roles" in the "type" "field"
@javascript
Scenario: Filter by user identity fields
- Given I log in as "teacher1"
+ Given I log in as "patricia"
And the following config values are set as admin:
| showuseridentity | idnumber,email,city,country |
And I am on "Course 1" course homepage
And I navigate to course participants
- And I set the field "type" in the "Filter 1" "fieldset" to "Keyword"
- # Search by email (only).
- And I set the field "Type..." to "student1@example.com"
- And I press the enter key
- When I click on "Apply filters" "button"
+
+ # Search by email (only) - should only see visible email + own.
+ # Match:
+ # Keyword Any ["student1@example.com"].
+
+ # Set the Keyword to "Any" ["student1@example.com"]
+ When I set the field "type" in the "Filter 1" "fieldset" to "Keyword"
+ And I set the field "Type..." in the "Filter 1" "fieldset" to "student1@example.com"
+ And I click on "Apply filters" "button"
+
Then I should see "Student 1" in the "participants" "table"
- And I should not see "Student 2" in the "participants" "table"
- And I should not see "Teacher 1" in the "participants" "table"
+ But I should not see "Student 2" in the "participants" "table"
+ And I should not see "Patricia Pea" in the "participants" "table"
+
# Search by idnumber (only).
+ # Match:
+ # Keyword Any ["SID"].
+
+ # Set the Keyword to "Any" ["SID"]
And I click on "student1@example.com" "autocomplete_selection"
- And I set the field "Type..." to "SID"
- And I press the enter key
+ And I set the field "Type..." in the "Filter 1" "fieldset" to "SID"
And I click on "Apply filters" "button"
- And I should see "Student 1" in the "participants" "table"
+
+ Then I should see "Student 1" in the "participants" "table"
And I should see "Student 2" in the "participants" "table"
And I should see "Student 3" in the "participants" "table"
And I should see "Student 4" in the "participants" "table"
- And I should not see "Teacher 1" in the "participants" "table"
+ But I should not see "Patricia Pea" in the "participants" "table"
+
# Search by city (only).
+ # Match:
+ # Keyword Any ["SCITY"].
+
+ # Set the Keyword to "Any" ["SCITY"]
And I click on "SID" "autocomplete_selection"
- And I set the field "Type..." to "SCITY"
- And I press the enter key
+ And I set the field "Type..." in the "Filter 1" "fieldset" to "SCITY"
And I click on "Apply filters" "button"
- And I should see "Student 1" in the "participants" "table"
+
+ Then I should see "Student 1" in the "participants" "table"
And I should see "Student 2" in the "participants" "table"
And I should see "Student 3" in the "participants" "table"
And I should see "Student 4" in the "participants" "table"
- And I should not see "Teacher 1" in the "participants" "table"
+ But I should not see "Patricia Pea" in the "participants" "table"
+
# Search by country text (only) - should not match.
+ # Match:
+ # Keyword Any ["GB"].
+
+ # Set the Keyword to "Any" ["GB"]
And I click on "SCITY" "autocomplete_selection"
- And I set the field "Type..." to "GB"
- And I press the enter key
+ And I set the field "Type..." in the "Filter 1" "fieldset" to "GB"
And I click on "Apply filters" "button"
- And I should see "Nothing to display"
+
+ Then I should see "Nothing to display"
+
# Check no match.
+ # Match:
+ # Keyword Any ["NOTHING"].
+
+ # Set the Keyword to "Any" ["NOTHING"]
And I click on "GB" "autocomplete_selection"
- And I set the field "Type..." to "NOTHING"
- And I press the enter key
+ And I set the field "Type..." in the "Filter 1" "fieldset" to "NOTHING"
And I click on "Apply filters" "button"
- And I should see "Nothing to display"
+
+ Then I should see "Nothing to display"
@javascript
Scenario: Filter by user identity fields when cannot see the field data
And the following config values are set as admin:
| showuseridentity | idnumber,email,city,country |
And I log out
- And I log in as "teacher1"
+
+ And I log in as "patricia"
And I am on "Course 1" course homepage
And I navigate to course participants
+
+ # Match:
+ # Keyword Any ["@example.com"].
+
# Search by email (only) - should only see visible email + own.
- And I set the field "type" in the "Filter 1" "fieldset" to "Keyword"
- And I set the field "Type..." to "@example."
- And I press the enter key
- When I click on "Apply filters" "button"
- Then I should not see "Student 1" in the "participants" "table"
- And I should see "Student 2" in the "participants" "table"
+ # Set the Keyword to "Any" ["@example.com"]
+ When I set the field "type" in the "Filter 1" "fieldset" to "Keyword"
+ And I set the field "Type..." in the "Filter 1" "fieldset" to "@example."
+ And I click on "Apply filters" "button"
+
+ Then I should see "Student 2" in the "participants" "table"
+ And I should see "Patricia Pea" in the "participants" "table"
+ But I should not see "Student 1" in the "participants" "table"
And I should not see "Student 3" in the "participants" "table"
And I should not see "Student 4" in the "participants" "table"
- And I should see "Teacher 1" in the "participants" "table"
+
# Search for other fields - should only see own results.
+
+ # Match:
+ # Keyword Any ["SID"].
+ # Set the Keyword to "Any" ["SID"]
And I click on "@example." "autocomplete_selection"
- And I set the field "Type..." to "SID"
- And I press the enter key
+ And I set the field "Type..." in the "Filter 1" "fieldset" to "SID"
And I click on "Apply filters" "button"
- And I should see "Nothing to display"
+
+ Then I should see "Nothing to display"
+
+ # Match:
+ # Keyword Any ["TID"].
+
+ # Set the Keyword to "Any" ["TID"]
And I click on "SID" "autocomplete_selection"
- And I set the field "Type..." to "TID"
- And I press the enter key
+ And I set the field "Type..." in the "Filter 1" "fieldset" to "TID"
And I click on "Apply filters" "button"
- And I should see "Teacher 1" in the "participants" "table"
- And I should not see "Student 1" in the "participants" "table"
+
+ Then I should see "Patricia Pea" in the "participants" "table"
+ But I should not see "Student 1" in the "participants" "table"
+
+ # Match:
+ # Keyword Any ["CITY"].
+
+ # Set the Keyword to "Any" ["CITY"]
And I click on "TID" "autocomplete_selection"
- And I set the field "Type..." to "CITY"
- And I press the enter key
+ And I set the field "Type..." in the "Filter 1" "fieldset" to "CITY"
And I click on "Apply filters" "button"
- And I should see "Teacher 1" in the "participants" "table"
- And I should not see "Student 1" in the "participants" "table"
- # Check no match.
+
+ Then I should see "Patricia Pea" in the "participants" "table"
+ But I should not see "Student 1" in the "participants" "table"
+
+ # No data matches regardless of capabilities.
+ # Match:
+ # Keyword Any ["NOTHING"].
+
+ # Set the Keyword to "Any" ["NOTHING"]
And I click on "CITY" "autocomplete_selection"
- And I set the field "Type..." to "NOTHING"
- And I press the enter key
+ And I set the field "Type..." in the "Filter 1" "fieldset" to "NOTHING"
And I click on "Apply filters" "button"
- And I should see "Nothing to display"
+
+ Then I should see "Nothing to display"
@javascript
Scenario: Individual filters can be removed, which will automatically refresh the participants list
- Given I log in as "teacher1"
+ # Match All:
+ # Roles All ["Student"]; and
+ # Keyword Any ["@example.com"].
+
+ # Set the Roles to "All" ["Student"].
+ Given I log in as "patricia"
And I am on "Course 1" course homepage
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"
+
+ # Set the Keyword to "Any" ["@example.com"]
And I click on "Add condition" "button"
- # Set filterset to match all.
- And I set the field "Match" to "All"
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 "Keyword"
- And I set the field "Type..." to "@example"
- And I press the enter key
+ And I set the field "Type..." in the "Filter 2" "fieldset" to "@example"
+
+ # Set filterset to match all.
+ And I set the field "Match" to "All"
And I click on "Apply filters" "button"
- And I should see "Student 1" in the "participants" "table"
+
+ Then I should see "Student 1" in the "participants" "table"
And I should see "Student 2" in the "participants" "table"
And I should see "Student 3" in the "participants" "table"
- And I should not see "Student 4" in the "participants" "table"
- And I should not see "Teacher 1" in the "participants" "table"
+ But I should not see "Student 4" in the "participants" "table"
+ And I should not see "Patricia Pea" in the "participants" "table"
+
+ # Match:
+ # Keyword Any ["@example.com"].
When I click on "Remove filter row" "button" in the "Filter 1" "fieldset"
Then I should see "Student 1" in the "participants" "table"
And I should see "Student 2" in the "participants" "table"
And I should see "Student 3" in the "participants" "table"
- And I should see "Teacher 1" in the "participants" "table"
- And I should not see "Student 4" in the "participants" "table"
+ And I should see "Patricia Pea" in the "participants" "table"
+ But I should not see "Student 4" in the "participants" "table"
@javascript
Scenario: All filters can be cleared at once
- Given I log in as "teacher1"
+ # Match All:
+ # Roles All ["Student"]; and
+ # Keyword Any ["@example.com"].
+
+ # Set the Roles to "All" ["Student"].
+ Given I log in as "patricia"
And I am on "Course 1" course homepage
And I navigate to course participants
- And I set the field "Match" in the "Filter 1" "fieldset" to "All"
+ When 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"
+
+ # Set the Keyword to "All" ["@example.com"].
And I click on "Add condition" "button"
- # Set filterset to match all.
- And I set the field "Match" to "All"
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 "Keyword"
- And I set the field "Type..." to "@example"
- And I press the enter key
+ And I set the field "Type..." in the "Filter 2" "fieldset" to "@example"
+
+ # Set filterset to match all.
+ And I set the field "Match" to "All"
And I click on "Apply filters" "button"
- And I should see "Student 1" in the "participants" "table"
+
+ Then I should see "Student 1" in the "participants" "table"
And I should see "Student 2" in the "participants" "table"
And I should see "Student 3" in the "participants" "table"
- And I should not see "Student 4" in the "participants" "table"
- And I should not see "Teacher 1" in the "participants" "table"
+ But I should not see "Student 4" in the "participants" "table"
+ And I should not see "Patricia Pea" in the "participants" "table"
+
+ # Match Any.
When I click on "Clear filters" "button"
Then I should see "Student 1" in the "participants" "table"
And I should see "Student 2" in the "participants" "table"
And I should see "Student 3" in the "participants" "table"
And I should see "Student 4" in the "participants" "table"
- And I should see "Teacher 1" in the "participants" "table"
+ And I should see "Patricia Pea" in the "participants" "table"
@javascript
Scenario: Filterset match type is reset when reducing to a single filter
- Given I log in as "teacher1"
+ # Match None:
+ # Keyword Any ["@example.com"]; and
+ # Roles All ["Teacher"].
+ Given I log in as "patricia"
And I am on "Course 1" course homepage
And I navigate to course participants
- And I set the field "Match" in the "Filter 1" "fieldset" to "Any"
+
+ # Set the Keyword to "Any" ["@example.com"]
+ When I set the field "Match" in the "Filter 1" "fieldset" to "Any"
And I set the field "type" in the "Filter 1" "fieldset" to "Keyword"
And I set the field "Type..." to "@example.com"
- And I press the enter key
+
+ # Set the Roles to "All" ["Student"].
And I click on "Add condition" "button"
- # Set filterset to match none.
- And I set the field "Match" to "None"
And I set the field "Match" in the "Filter 2" "fieldset" to "All"
And I set the field "type" in the "Filter 2" "fieldset" to "Roles"
- And I click on ".form-autocomplete-downarrow" "css_element" in the "Filter 2" "fieldset"
- And I click on "Student" "list_item"
+ And I set the field "Type or select..." in the "Filter 2" "fieldset" to "Student"
+
# Match none of student role and @example.com keyword.
+ And I set the field "Match" to "None"
And I click on "Apply filters" "button"
- And I should see "Teacher 1" in the "participants" "table"
- And I should not see "Student 1" in the "participants" "table"
+
+ Then I should see "Patricia Pea" in the "participants" "table"
+ But I should not see "Student 1" in the "participants" "table"
And I should not see "Student 2" in the "participants" "table"
And I should not see "Student 3" in the "participants" "table"
And I should not see "Student 4" in the "participants" "table"
+
+ # Match:
+ # Keyword Any ["@example.com"].
+ # When removing the pen-ultimate filter, the filterset match type is removed too.
When I click on "Remove filter row" "button" in the "Filter 2" "fieldset"
- # Filterset match type and role filter are removed, leaving keyword filter only.
Then I should see "Student 1" in the "participants" "table"
And I should see "Student 2" in the "participants" "table"
And I should see "Student 3" in the "participants" "table"
- And I should not see "Student 4" in the "participants" "table"
- And I should not see "Teacher 1" in the "participants" "table"
- And I click on "Add condition" "button"
- # Re-add a second filter and ensure the default (All) filterset match type is set.
+ But I should not see "Student 4" in the "participants" "table"
+ And I should not see "Patricia Pea" in the "participants" "table"
+
+ # Match Any:
+ # Keyword Any ["@example.com"]; and
+ # Role All ["Student"].
+ # The default filterset match (Any) should apply.
+ # Set the Roles to "All" ["Student"].
+ When I click on "Add condition" "button"
And I set the field "Match" in the "Filter 2" "fieldset" to "All"
And I set the field "type" in the "Filter 2" "fieldset" to "Role"
- And I click on ".form-autocomplete-downarrow" "css_element" in the "Filter 2" "fieldset"
- And I click on "Student" "list_item"
+ And I set the field "Type or select..." in the "Filter 2" "fieldset" to "Student"
And I click on "Apply filters" "button"
- And I should see "Student 1" in the "participants" "table"
+
+ Then I should see "Student 1" in the "participants" "table"
And I should see "Student 2" in the "participants" "table"
And I should see "Student 3" in the "participants" "table"
And I should not see "Student 4" in the "participants" "table"
- And I should not see "Teacher 1" in the "participants" "table"
+ But I should not see "Patricia Pea" in the "participants" "table"
@javascript
Scenario: Filter users by first initial
- Given I log in as "teacher1"
+ # Match:
+ # No filters; and
+ # First initial "T".
+ Given I log in as "patricia"
And I am on "Course 2" course homepage
And I navigate to course participants
And I should see "Student 1" in the "participants" "table"
And I should see "Student 2" in the "participants" "table"
And I should see "Student 3" in the "participants" "table"
And I should see "Trendy Learnson" in the "participants" "table"
- And I should see "Teacher 1" in the "participants" "table"
+ And I should see "Patricia Pea" in the "participants" "table"
When I click on "T" "link" in the ".firstinitial" "css_element"
Then I should see "Trendy Learnson" in the "participants" "table"
- And I should see "Teacher 1" in the "participants" "table"
+ But I should not see "Patricia Pea" in the "participants" "table"
And I should not see "Student 1" in the "participants" "table"
And I should not see "Student 2" in the "participants" "table"
And I should not see "Student 3" in the "participants" "table"
@javascript
Scenario: Filter users by last initial
- Given I log in as "teacher1"
+ # Match:
+ # No filters; and
+ # Last initial "L".
+ Given I log in as "patricia"
And I am on "Course 2" course homepage
And I navigate to course participants
And I should see "Student 1" in the "participants" "table"
And I should see "Student 2" in the "participants" "table"
And I should see "Student 3" in the "participants" "table"
And I should see "Trendy Learnson" in the "participants" "table"
- And I should see "Teacher 1" in the "participants" "table"
+ And I should see "Patricia Pea" in the "participants" "table"
When I click on "L" "link" in the ".lastinitial" "css_element"
Then I should see "Trendy Learnson" in the "participants" "table"
- And I should not see "Student 1" in the "participants" "table"
+ But I should not see "Student 1" in the "participants" "table"
And I should not see "Student 2" in the "participants" "table"
And I should not see "Student 3" in the "participants" "table"
- And I should not see "Teacher 1" in the "participants" "table"
+ And I should not see "Patricia Pea" in the "participants" "table"
@javascript
Scenario: Filter users by first and last initials
- Given I log in as "teacher1"
+ # Match:
+ # No filters; and
+ # First initial "T"; and
+ # Last initial "L".
+ Given I log in as "patricia"
And I am on "Course 2" course homepage
And I navigate to course participants
And I should see "Student 1" in the "participants" "table"
And I should see "Student 2" in the "participants" "table"
And I should see "Student 3" in the "participants" "table"
And I should see "Trendy Learnson" in the "participants" "table"
- And I should see "Teacher 1" in the "participants" "table"
+ And I should see "Patricia Pea" in the "participants" "table"
When I click on "T" "link" in the ".firstinitial" "css_element"
And I click on "L" "link" in the ".lastinitial" "css_element"
Then I should see "Trendy Learnson" in the "participants" "table"
- And I should not see "Student 1" in the "participants" "table"
+ But I should not see "Student 1" in the "participants" "table"
And I should not see "Student 2" in the "participants" "table"
And I should not see "Student 3" in the "participants" "table"
- And I should not see "Teacher 1" in the "participants" "table"
+ And I should not see "Patricia Pea" in the "participants" "table"
@javascript
Scenario: Initials filtering is always applied in addition to any other filtering
- Given I log in as "teacher1"
+ # Match:
+ # Roles All ["Teacher"]; and
+ # First initial "T".
+ Given I log in as "patricia"
And I am on "Course 2" course homepage
And I navigate to course participants
And I should see "Student 1" in the "participants" "table"
And I should see "Student 2" in the "participants" "table"
And I should see "Student 3" in the "participants" "table"
And I should see "Trendy Learnson" in the "participants" "table"
- And I should see "Teacher 1" in the "participants" "table"
+ And I should see "Patricia Pea" in the "participants" "table"
+
+ # Set the Role to "Any" ["Student"].
When I set the field "Match" in the "Filter 1" "fieldset" to "Any"
And I set the field "type" in the "Filter 1" "fieldset" to "Role"
- 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 click on "Apply filters" "button"
- When I click on "T" "link" in the ".firstinitial" "css_element"
+
+ # Last initial "T".
+ And I click on "T" "link" in the ".firstinitial" "css_element"
Then I should see "Trendy Learnson" in the "participants" "table"
- And I should not see "Student 1" in the "participants" "table"
+ But I should not see "Student 1" in the "participants" "table"
And I should not see "Student 2" in the "participants" "table"
And I should not see "Student 3" in the "participants" "table"
- And I should not see "Teacher 1" in the "participants" "table"
+ And I should not see "Patricia Pea" in the "participants" "table"
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"
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
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"
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"
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"
defined('MOODLE_INTERNAL') || die();
-$version = 2021052500.48; // YYYYMMDD = weekly release date of this DEV branch.
+$version = 2021052500.49; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.
-$release = '4.0dev (Build: 20201217)'; // Human-friendly version name
+$release = '4.0dev (Build: 20201224)'; // Human-friendly version name
$branch = '400'; // This version's branch.
$maturity = MATURITY_ALPHA; // This version's maturity level.