Then "Tutor" "button" should exist
And "Learner" "button" should exist
And I navigate to course participants
- And I open the autocomplete suggestions list
- And I should see "Role: Tutor" in the ".form-autocomplete-suggestions" "css_element"
- And I should see "Role: Learner" in the ".form-autocomplete-suggestions" "css_element"
- And I should not see "Role: Student" in the ".form-autocomplete-suggestions" "css_element"
+ 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" in the ".form-autocomplete-suggestions" "css_element"
+ And I should see "Learner" in the ".form-autocomplete-suggestions" "css_element"
+ And I should not see "Student" in the ".form-autocomplete-suggestions" "css_element"
And I am on "Course 1" course homepage
And I navigate to "Edit settings" in current page administration
And I set the following fields to these values:
And "Student" "button" should exist
And "Learner" "button" should not exist
And I navigate to course participants
- And I open the autocomplete suggestions list
- And I should see "Role: Non-editing teacher" in the ".form-autocomplete-suggestions" "css_element"
- And I should see "Role: Student" in the ".form-autocomplete-suggestions" "css_element"
+ 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 "Non-editing teacher" in the ".form-autocomplete-suggestions" "css_element"
+ And I should see "Student" in the ".form-autocomplete-suggestions" "css_element"
And the "members" select box should not contain "Student 0 (student0@example.com)"
And the "members" select box should not contain "Student 1 (student1@example.com)"
And I navigate to course participants
- And I open the autocomplete suggestions list
- And I click on "Group: Group 1" item in the autocomplete list
+ 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 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: Group 1" "text" in the ".form-autocomplete-selection" "css_element"
- And I open the autocomplete suggestions list
- And I click on "Group: Group 2" item in the autocomplete list
+ And I click on "Remove \"Group 1\" from filter" "button" in the "Filter 1" "fieldset"
+ And I click on ".form-autocomplete-downarrow" "css_element" in the "Filter 1" "fieldset"
+ And I click on "Group 2" "list_item"
+ And I click on "Apply filters" "button"
And I should see "Student 2"
And I should see "Student 3"
And I should not see "Student 0"
And I add "Student 2 (student2@example.com)" user to "Group B" group members
And I am on "Course 1" course homepage
And I navigate to course participants
- And I open the autocomplete suggestions list
- And I click on "Group: Group A" item in the autocomplete list
+ And I click on "Student 1" "link" in the "participants" "table"
+ And I click on "Group A" "link"
And I should see "Description for Group A"
And ".groupinfobox" "css_element" should exist
- And I should see "Description for Group A"
- And I click on "Group: Group A" "autocomplete_selection"
- And I open the autocomplete suggestions list
- And I click on "Group: Group B" item in the autocomplete list
+ 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 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 "Student 2" in the "participants" "table"
And ".groupinfobox" "css_element" should not exist
And I log out
When I log in as "student1"
And I am on "Course 1" course homepage
And I navigate to course participants
+ And I click on "Student 1" "link" in the "participants" "table"
+ And I click on "Group A" "link"
Then I should see "Description for Group A"
And I log out
And I log in as "student2"
And I am on "Course 1" course homepage
And I navigate to course participants
+ And I click on "Student 2" "link" in the "participants" "table"
+ And I click on "Group B" "link"
+ And I should see "Student 2" in the "participants" "table"
And ".groupinfobox" "css_element" should not exist
@javascript
And I add "Student 2 (student2@example.com)" user to "Group B" group members
And I am on "Course 1" course homepage
And I navigate to course participants
- And I open the autocomplete suggestions list
- And I click on "Group: Group A" item in the autocomplete list
+ And I click on "Student 1" "link" in the "participants" "table"
+ And I click on "Group A" "link"
And I should see "Description for Group A"
And ".groupinfobox" "css_element" should exist
- And I click on "Group: Group A" "autocomplete_selection"
- And I open the autocomplete suggestions list
- And I click on "Group: Group B" item in the autocomplete list
+ 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 click on "Apply filters" "button"
+ And I click on "Student 2" "link" in the "participants" "table"
+ And I click on "Group B" "link"
And ".groupinfobox" "css_element" should not exist
And I log out
When I log in as "student1"
And I am on "Course 1" course homepage
And I navigate to course participants
- Then I should not see "Description for Group A"
+ And I click on "Student 1" "link" in the "participants" "table"
+ And I click on "Group A" "link"
+ And I should see "Student 1" in the "participants" "table"
+ And I should not see "Description for Group A"
And ".groupinfobox" "css_element" should not exist
And I log out
And I log in as "student2"
And I am on "Course 1" course homepage
And I navigate to course participants
+ And I click on "Student 2" "link" in the "participants" "table"
+ And I click on "Group B" "link"
+ And I should see "Student 2" in the "participants" "table"
And ".groupinfobox" "css_element" should not exist
editsettings,core_badges
availablelicenses,core_admin
managelicenses,core_admin
+userfilterplaceholder,core
$string['userdescription_help'] = 'This box enables you to enter some text about yourself which will then be displayed on your profile page for others to view.';
$string['userdetails'] = 'User details';
$string['userfiles'] = 'User files';
-$string['userfilterplaceholder'] = 'Search keyword or select filter';
$string['userlist'] = 'User list';
$string['usermenu'] = 'User menu';
$string['username'] = 'Username';
// Deprecated since Moodle 3.9.
$string['participantscount'] = 'Number of participants: {$a}';
+$string['userfilterplaceholder'] = 'Search keyword or select filter';
$string['applyfilters'] = 'Apply filters';
$string['clearfilterrow'] = 'Remove filter row';
$string['clearfilters'] = 'Clear filters';
+$string['clearfilterselection'] = 'Remove "{$a}" from filter';
$string['countparticipantsfound'] = '{$a} participants found';
+$string['filterrowlegend'] = 'Filter {$a}';
$string['filtersetmatchdescription'] = 'How multiple filters should be combined';
$string['match'] = 'Match';
$string['matchofthefollowing'] = 'of the following:';
'datagenerator' => 'setup_backpack_connected',
'required' => ['user', 'externalbackpack'],
'switchids' => ['user' => 'userid', 'externalbackpack' => 'externalbackpackid']
- ]
+ ],
+ 'last access times' => [
+ 'datagenerator' => 'last_access_times',
+ 'required' => ['user', 'course', 'lastaccess'],
+ 'switchids' => ['user' => 'userid', 'course' => 'courseid'],
+ ],
];
}
$backpack->externalbackpackid = $data['externalbackpackid'];
$DB->insert_record('badge_backpack', $backpack);
}
+
+ /**
+ * Creates user last access data within given courses.
+ *
+ * @param array $data
+ * @return void
+ */
+ protected function process_last_access_times(array $data) {
+ global $DB;
+
+ if (!isset($data['userid'])) {
+ throw new Exception('\'last acces times\' requires the field \'user\' to be specified');
+ }
+
+ if (!isset($data['courseid'])) {
+ throw new Exception('\'last acces times\' requires the field \'course\' to be specified');
+ }
+
+ if (!isset($data['lastaccess'])) {
+ throw new Exception('\'last acces times\' requires the field \'lastaccess\' to be specified');
+ }
+
+ $userdata = [];
+ $userdata['old'] = $DB->get_record('user', ['id' => $data['userid']], 'firstaccess, lastaccess, lastlogin, currentlogin');
+ $userdata['new'] = [
+ 'firstaccess' => $userdata['old']->firstaccess,
+ 'lastaccess' => $userdata['old']->lastaccess,
+ 'lastlogin' => $userdata['old']->lastlogin,
+ 'currentlogin' => $userdata['old']->currentlogin,
+ ];
+
+ // Check for lastaccess data for this course.
+ $lastaccessdata = [
+ 'userid' => $data['userid'],
+ 'courseid' => $data['courseid'],
+ ];
+
+ $lastaccessid = $DB->get_field('user_lastaccess', 'id', $lastaccessdata);
+
+ $dbdata = (object) $lastaccessdata;
+ $dbdata->timeaccess = $data['lastaccess'];
+
+ // Set the course last access time.
+ if ($lastaccessid) {
+ $dbdata->id = $lastaccessid;
+ $DB->update_record('user_lastaccess', $dbdata);
+ } else {
+ $DB->insert_record('user_lastaccess', $dbdata);
+ }
+
+ // Store changes to other user access times as needed.
+
+ // Update first access if this is the user's first login, or this access is earlier than their current first access.
+ if (empty($userdata['new']['firstaccess']) ||
+ $userdata['new']['firstaccess'] > $data['lastaccess']) {
+ $userdata['new']['firstaccess'] = $data['lastaccess'];
+ }
+
+ // Update last access if it is the user's most recent access.
+ if (empty($userdata['new']['lastaccess']) ||
+ $userdata['new']['lastaccess'] < $data['lastaccess']) {
+ $userdata['new']['lastaccess'] = $data['lastaccess'];
+ }
+
+ // Update last and current login if it is the user's most recent access.
+ if (empty($userdata['new']['lastlogin']) ||
+ $userdata['new']['lastlogin'] < $data['lastaccess']) {
+ $userdata['new']['lastlogin'] = $data['lastaccess'];
+ $userdata['new']['currentlogin'] = $data['lastaccess'];
+ }
+
+ $updatedata = [];
+
+ if ($userdata['new']['firstaccess'] != $userdata['old']->firstaccess) {
+ $updatedata['firstaccess'] = $userdata['new']['firstaccess'];
+ }
+
+ if ($userdata['new']['lastaccess'] != $userdata['old']->lastaccess) {
+ $updatedata['lastaccess'] = $userdata['new']['lastaccess'];
+ }
+
+ if ($userdata['new']['lastlogin'] != $userdata['old']->lastlogin) {
+ $updatedata['lastlogin'] = $userdata['new']['lastlogin'];
+ }
+
+ if ($userdata['new']['currentlogin'] != $userdata['old']->currentlogin) {
+ $updatedata['currentlogin'] = $userdata['new']['currentlogin'];
+ }
+
+ // Only update user access data if there have been any changes.
+ if (!empty($updatedata)) {
+ $updatedata['id'] = $data['userid'];
+ $updatedata = (object) $updatedata;
+ $DB->update_record('user', $updatedata);
+ }
+ }
}
return $plugins;
}
+
+/**
+ * Returns the SQL used by the participants table.
+ *
+ * @deprecated since Moodle 3.9 MDL-68612 - See \core_user\table\participants_search for an improved way to fetch participants.
+ * @param int $courseid The course id
+ * @param int $groupid The groupid, 0 means all groups and USERSWITHOUTGROUP no group
+ * @param int $accesssince The time since last access, 0 means any time
+ * @param int $roleid The role id, 0 means all roles and -1 no roles
+ * @param int $enrolid The enrolment id, 0 means all enrolment methods will be returned.
+ * @param int $statusid The user enrolment status, -1 means all enrolments regardless of the status will be returned, if allowed.
+ * @param string|array $search The search that was performed, empty means perform no search
+ * @param string $additionalwhere Any additional SQL to add to where
+ * @param array $additionalparams The additional params
+ * @return array
+ */
+function user_get_participants_sql($courseid, $groupid = 0, $accesssince = 0, $roleid = 0, $enrolid = 0, $statusid = -1,
+ $search = '', $additionalwhere = '', $additionalparams = array()) {
+ global $DB, $USER, $CFG;
+
+ $deprecatedtext = __FUNCTION__ . '() is deprecated. ' .
+ 'Please use \core\table\participants_search::class with table filtersets instead.';
+ debugging($deprecatedtext, DEBUG_DEVELOPER);
+
+ // Get the context.
+ $context = \context_course::instance($courseid, MUST_EXIST);
+
+ $isfrontpage = ($courseid == SITEID);
+
+ // Default filter settings. We only show active by default, especially if the user has no capability to review enrolments.
+ $onlyactive = true;
+ $onlysuspended = false;
+ if (has_capability('moodle/course:enrolreview', $context) && (has_capability('moodle/course:viewsuspendedusers', $context))) {
+ switch ($statusid) {
+ case ENROL_USER_ACTIVE:
+ // Nothing to do here.
+ break;
+ case ENROL_USER_SUSPENDED:
+ $onlyactive = false;
+ $onlysuspended = true;
+ break;
+ default:
+ // If the user has capability to review user enrolments, but statusid is set to -1, set $onlyactive to false.
+ $onlyactive = false;
+ break;
+ }
+ }
+
+ list($esql, $params) = get_enrolled_sql($context, null, $groupid, $onlyactive, $onlysuspended, $enrolid);
+
+ $joins = array('FROM {user} u');
+ $wheres = array();
+
+ $userfields = get_extra_user_fields($context);
+ $userfieldssql = user_picture::fields('u', $userfields);
+
+ if ($isfrontpage) {
+ $select = "SELECT $userfieldssql, u.lastaccess";
+ $joins[] = "JOIN ($esql) e ON e.id = u.id"; // Everybody on the frontpage usually.
+ if ($accesssince) {
+ $wheres[] = user_get_user_lastaccess_sql($accesssince);
+ }
+ } else {
+ $select = "SELECT $userfieldssql, COALESCE(ul.timeaccess, 0) AS lastaccess";
+ $joins[] = "JOIN ($esql) e ON e.id = u.id"; // Course enrolled users only.
+ // Not everybody has accessed the course yet.
+ $joins[] = 'LEFT JOIN {user_lastaccess} ul ON (ul.userid = u.id AND ul.courseid = :courseid)';
+ $params['courseid'] = $courseid;
+ if ($accesssince) {
+ $wheres[] = user_get_course_lastaccess_sql($accesssince);
+ }
+ }
+
+ // Performance hacks - we preload user contexts together with accounts.
+ $ccselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
+ $ccjoin = 'LEFT JOIN {context} ctx ON (ctx.instanceid = u.id AND ctx.contextlevel = :contextlevel)';
+ $params['contextlevel'] = CONTEXT_USER;
+ $select .= $ccselect;
+ $joins[] = $ccjoin;
+
+ // Limit list to users with some role only.
+ if ($roleid) {
+ // We want to query both the current context and parent contexts.
+ list($relatedctxsql, $relatedctxparams) = $DB->get_in_or_equal($context->get_parent_context_ids(true),
+ SQL_PARAMS_NAMED, 'relatedctx');
+
+ // Get users without any role.
+ if ($roleid == -1) {
+ $wheres[] = "u.id NOT IN (SELECT userid FROM {role_assignments} WHERE contextid $relatedctxsql)";
+ $params = array_merge($params, $relatedctxparams);
+ } else {
+ $wheres[] = "u.id IN (SELECT userid FROM {role_assignments} WHERE roleid = :roleid AND contextid $relatedctxsql)";
+ $params = array_merge($params, array('roleid' => $roleid), $relatedctxparams);
+ }
+ }
+
+ if (!empty($search)) {
+ if (!is_array($search)) {
+ $search = [$search];
+ }
+ foreach ($search as $index => $keyword) {
+ $searchkey1 = 'search' . $index . '1';
+ $searchkey2 = 'search' . $index . '2';
+ $searchkey3 = 'search' . $index . '3';
+ $searchkey4 = 'search' . $index . '4';
+ $searchkey5 = 'search' . $index . '5';
+ $searchkey6 = 'search' . $index . '6';
+ $searchkey7 = 'search' . $index . '7';
+
+ $conditions = array();
+ // Search by fullname.
+ $fullname = $DB->sql_fullname('u.firstname', 'u.lastname');
+ $conditions[] = $DB->sql_like($fullname, ':' . $searchkey1, false, false);
+
+ // Search by email.
+ $email = $DB->sql_like('email', ':' . $searchkey2, false, false);
+ if (!in_array('email', $userfields)) {
+ $maildisplay = 'maildisplay' . $index;
+ $userid1 = 'userid' . $index . '1';
+ // Prevent users who hide their email address from being found by others
+ // who aren't allowed to see hidden email addresses.
+ $email = "(". $email ." AND (" .
+ "u.maildisplay <> :$maildisplay " .
+ "OR u.id = :$userid1". // User can always find himself.
+ "))";
+ $params[$maildisplay] = core_user::MAILDISPLAY_HIDE;
+ $params[$userid1] = $USER->id;
+ }
+ $conditions[] = $email;
+
+ // Search by idnumber.
+ $idnumber = $DB->sql_like('idnumber', ':' . $searchkey3, false, false);
+ if (!in_array('idnumber', $userfields)) {
+ $userid2 = 'userid' . $index . '2';
+ // Users who aren't allowed to see idnumbers should at most find themselves
+ // when searching for an idnumber.
+ $idnumber = "(". $idnumber . " AND u.id = :$userid2)";
+ $params[$userid2] = $USER->id;
+ }
+ $conditions[] = $idnumber;
+
+ if (!empty($CFG->showuseridentity)) {
+ // Search all user identify fields.
+ $extrasearchfields = explode(',', $CFG->showuseridentity);
+ foreach ($extrasearchfields as $extrasearchfield) {
+ if (in_array($extrasearchfield, ['email', 'idnumber', 'country'])) {
+ // Already covered above. Search by country not supported.
+ continue;
+ }
+ $param = $searchkey3 . $extrasearchfield;
+ $condition = $DB->sql_like($extrasearchfield, ':' . $param, false, false);
+ $params[$param] = "%$keyword%";
+ if (!in_array($extrasearchfield, $userfields)) {
+ // User cannot see this field, but allow match if their own account.
+ $userid3 = 'userid' . $index . '3' . $extrasearchfield;
+ $condition = "(". $condition . " AND u.id = :$userid3)";
+ $params[$userid3] = $USER->id;
+ }
+ $conditions[] = $condition;
+ }
+ }
+
+ // Search by middlename.
+ $middlename = $DB->sql_like('middlename', ':' . $searchkey4, false, false);
+ $conditions[] = $middlename;
+
+ // Search by alternatename.
+ $alternatename = $DB->sql_like('alternatename', ':' . $searchkey5, false, false);
+ $conditions[] = $alternatename;
+
+ // Search by firstnamephonetic.
+ $firstnamephonetic = $DB->sql_like('firstnamephonetic', ':' . $searchkey6, false, false);
+ $conditions[] = $firstnamephonetic;
+
+ // Search by lastnamephonetic.
+ $lastnamephonetic = $DB->sql_like('lastnamephonetic', ':' . $searchkey7, false, false);
+ $conditions[] = $lastnamephonetic;
+
+ $wheres[] = "(". implode(" OR ", $conditions) .") ";
+ $params[$searchkey1] = "%$keyword%";
+ $params[$searchkey2] = "%$keyword%";
+ $params[$searchkey3] = "%$keyword%";
+ $params[$searchkey4] = "%$keyword%";
+ $params[$searchkey5] = "%$keyword%";
+ $params[$searchkey6] = "%$keyword%";
+ $params[$searchkey7] = "%$keyword%";
+ }
+ }
+
+ if (!empty($additionalwhere)) {
+ $wheres[] = $additionalwhere;
+ $params = array_merge($params, $additionalparams);
+ }
+
+ $from = implode("\n", $joins);
+ if ($wheres) {
+ $where = 'WHERE ' . implode(' AND ', $wheres);
+ } else {
+ $where = '';
+ }
+
+ return array($select, $from, $where, $params);
+}
+
+/**
+ * Returns the total number of participants for a given course.
+ *
+ * @deprecated since Moodle 3.9 MDL-68612 - See \core_user\table\participants_search for an improved way to fetch participants.
+ * @param int $courseid The course id
+ * @param int $groupid The groupid, 0 means all groups and USERSWITHOUTGROUP no group
+ * @param int $accesssince The time since last access, 0 means any time
+ * @param int $roleid The role id, 0 means all roles
+ * @param int $enrolid The applied filter for the user enrolment ID.
+ * @param int $status The applied filter for the user's enrolment status.
+ * @param string|array $search The search that was performed, empty means perform no search
+ * @param string $additionalwhere Any additional SQL to add to where
+ * @param array $additionalparams The additional params
+ * @return int
+ */
+function user_get_total_participants($courseid, $groupid = 0, $accesssince = 0, $roleid = 0, $enrolid = 0, $statusid = -1,
+ $search = '', $additionalwhere = '', $additionalparams = array()) {
+ global $DB;
+
+ $deprecatedtext = __FUNCTION__ . '() is deprecated. ' .
+ 'Please use \core\table\participants_search::class with table filtersets instead.';
+ debugging($deprecatedtext, DEBUG_DEVELOPER);
+
+ list($select, $from, $where, $params) = user_get_participants_sql($courseid, $groupid, $accesssince, $roleid, $enrolid,
+ $statusid, $search, $additionalwhere, $additionalparams);
+
+ return $DB->count_records_sql("SELECT COUNT(u.id) $from $where", $params);
+}
+
+/**
+ * Returns the participants for a given course.
+ *
+ * @deprecated since Moodle 3.9 MDL-68612 - See \core_user\table\participants_search for an improved way to fetch participants.
+ * @param int $courseid The course id
+ * @param int $groupid The groupid, 0 means all groups and USERSWITHOUTGROUP no group
+ * @param int $accesssince The time since last access
+ * @param int $roleid The role id
+ * @param int $enrolid The applied filter for the user enrolment ID.
+ * @param int $status The applied filter for the user's enrolment status.
+ * @param string $search The search that was performed
+ * @param string $additionalwhere Any additional SQL to add to where
+ * @param array $additionalparams The additional params
+ * @param string $sort The SQL sort
+ * @param int $limitfrom return a subset of records, starting at this point (optional).
+ * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
+ * @return moodle_recordset
+ */
+function user_get_participants($courseid, $groupid = 0, $accesssince, $roleid, $enrolid = 0, $statusid, $search,
+ $additionalwhere = '', $additionalparams = array(), $sort = '', $limitfrom = 0, $limitnum = 0) {
+ global $DB;
+
+ $deprecatedtext = __FUNCTION__ . '() is deprecated. ' .
+ 'Please use \core\table\participants_search::class with table filtersets instead.';
+ debugging($deprecatedtext, DEBUG_DEVELOPER);
+
+ list($select, $from, $where, $params) = user_get_participants_sql($courseid, $groupid, $accesssince, $roleid, $enrolid,
+ $statusid, $search, $additionalwhere, $additionalparams);
+
+ return $DB->get_recordset_sql("$select $from $where $sort", $params, $limitfrom, $limitnum);
+}
}
};
+/**
+ * Get the table dataset for the specified tableRoot, ensuring that the provided table is a dynamic table.
+ *
+ * @param {HTMLElement} tableRoot
+ * @returns {DOMStringMap}
+ */
+const getTableData = tableRoot => {
+ checkTableIsDynamic(tableRoot);
+
+ return tableRoot.dataset;
+};
+
/**
* Update the specified table using the new filters.
*
export const setFilters = (tableRoot, filters, refreshContent = true) =>
updateTable(tableRoot, {filters}, refreshContent);
+/**
+ * Get the filter data for the specified table.
+ *
+ * @param {HTMLElement} tableRoot
+ * @returns {Object}
+ */
+export const getFilters = tableRoot => {
+ checkTableIsDynamic(tableRoot);
+
+ return getFiltersetFromTable(tableRoot);
+};
+
/**
* Update the sort order.
*
export const setPageNumber = (tableRoot, pageNumber, refreshContent = true) =>
updateTable(tableRoot, {pageNumber}, refreshContent);
+/**
+ * Get the current page number.
+ *
+ * @param {HTMLElement} tableRoot
+ * @returns {Number}
+ */
+export const getPageNumber = tableRoot => getTableData(tableRoot).tablePageNumber;
+
/**
* Set the page size.
*
export const setPageSize = (tableRoot, pageSize, refreshContent = true) =>
updateTable(tableRoot, {pageSize, pageNumber: 0}, refreshContent);
+/**
+ * Get the current page size.
+ *
+ * @param {HTMLElement} tableRoot
+ * @returns {Number}
+ */
+export const getPageSize = tableRoot => getTableData(tableRoot).tablePageSize;
+
/**
* Update the first initial to show.
*
export const setFirstInitial = (tableRoot, firstInitial, refreshContent = true) =>
updateTable(tableRoot, {firstInitial}, refreshContent);
+/**
+ * Get the current first initial filter.
+ *
+ * @param {HTMLElement} tableRoot
+ * @returns {String}
+ */
+export const getFirstInitial = tableRoot => getTableData(tableRoot).tableFirstInitial;
+
/**
* Update the last initial to show.
*
export const setLastInitial = (tableRoot, lastInitial, refreshContent = true) =>
updateTable(tableRoot, {lastInitial}, refreshContent);
+/**
+ * Get the current last initial filter.
+ *
+ * @param {HTMLElement} tableRoot
+ * @returns {String}
+ */
+export const getLastInitial = tableRoot => getTableData(tableRoot).tableLastInitial;
+
/**
* Hide a column in the participants table.
*
*/
class filter implements Countable, Iterator, JsonSerializable {
- /**
- * @var in The default filter type (ALL)
- * Note: This is for backwards compatibility with the old UI behaviour and will be set to JOINTYPE_ANY as part of MDL-68612.
- */
- const JOINTYPE_DEFAULT = 2;
+ /** @var in The default filter type (ANY) */
+ const JOINTYPE_DEFAULT = 1;
/** @var int None of the following match */
const JOINTYPE_NONE = 0;
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class filterset implements JsonSerializable {
- /**
- * @var in The default filter type (ALL)
- * Note: This is for backwards compatibility with the old UI behaviour and will be set to JOINTYPE_ANY as part of MDL-68612.
- */
- const JOINTYPE_DEFAULT = 2;
+ /** @var in The default filter type (ANY) */
+ const JOINTYPE_DEFAULT = 1;
/** @var int None of the following match */
const JOINTYPE_NONE = 0;
*
* @param {String} filterType The type of filter that this relates to
* @param {HTMLElement} rootNode The root node for the participants filterset
+ * @param {Array} initialValues The initial values for the selector
*/
- constructor(filterType, rootNode) {
+ constructor(filterType, rootNode, initialValues) {
this.filterType = filterType;
this.rootNode = rootNode;
- this.addValueSelector();
+ this.addValueSelector(initialValues);
}
/**
/**
* Add the value selector to the filter row.
+ *
+ * @param {Array} initialValues
*/
- async addValueSelector() {
+ async addValueSelector(initialValues = []) {
const filterValueNode = this.getFilterValueNode();
// Copy the data in place.
const dataSource = filterValueNode.querySelector('select');
+ // If there are any initial values then attempt to apply them.
+ initialValues.forEach(filterValue => {
+ let selectedOption = dataSource.querySelector(`option[value="${filterValue}"]`);
+ if (selectedOption) {
+ selectedOption.selected = true;
+ } else if (!this.showSuggestions) {
+ selectedOption = document.createElement('option');
+ selectedOption.value = filterValue;
+ selectedOption.innerHTML = filterValue;
+ selectedOption.selected = true;
+
+ dataSource.append(selectedOption);
+ }
+ });
+
Autocomplete.enhance(
// The source select element.
dataSource,
import {get_string as getString} from 'core/str';
export default class extends Filter {
- constructor(filterType, filterSet) {
- super(filterType, filterSet);
- }
-
/**
* For keywords the final value is an Array of strings.
*
all: `${getFilterRegion('filtertypedata')} [data-field-name]`,
},
typeList: getFilterRegion('filtertypelist'),
+ typeListSelect: `select${getFilterRegion('filtertypelist')}`,
},
};
import CourseFilter from './local/participantsfilter/filtertypes/courseid';
import * as DynamicTable from 'core_table/dynamic';
import GenericFilter from './local/participantsfilter/filter';
+import {get_strings as getStrings} from 'core/str';
import Notification from 'core/notification';
import Selectors from './local/participantsfilter/selectors';
import Templates from 'core/templates';
* @return {Promise}
*/
const addFilterRow = () => {
- return Templates.renderForPromise('core_user/local/participantsfilter/filterrow', {})
+ const rownum = 1 + getFilterRegion().querySelectorAll(Selectors.filter.region).length;
+ return Templates.renderForPromise('core_user/local/participantsfilter/filterrow', {"rownumber": rownum})
.then(({html, js}) => {
const newContentNodes = Templates.appendNodeContents(getFilterRegion(), html, js);
*
* @param {HTMLElement} filterRow
* @param {String} filterType
+ * @param {Array} initialFilterValues The initially selected values for the filter
+ * @returns {Filter}
*/
- const addFilter = async(filterRow, filterType) => {
+ const addFilter = async(filterRow, filterType, initialFilterValues) => {
// Name the filter on the filter row.
filterRow.dataset.filterType = filterType;
if (filterDataNode.dataset.filterTypeClass) {
Filter = await import(filterDataNode.dataset.filterTypeClass);
}
- activeFilters[filterType] = new Filter(filterType, filterSet);
+ activeFilters[filterType] = new Filter(filterType, filterSet, initialFilterValues);
// Disable the select.
const typeField = filterRow.querySelector(Selectors.filter.fields.type);
+ typeField.value = filterType;
typeField.disabled = 'disabled';
// Update the list of available filter types.
updateFiltersOptions();
+
+ return activeFilters[filterType];
};
/**
*
* @param {HTMLElement} filterRow
*/
- const removeFilterRow = filterRow => {
+ const removeFilterRow = async filterRow => {
// Remove the filter object.
removeFilterObject(filterRow.dataset.filterType);
// Remove the actual filter HTML.
filterRow.remove();
+ // Update the list of available filter types.
+ updateFiltersOptions();
+
// Refresh the table.
updateTableFromFilter();
- // Update the list of available filter types.
- updateFiltersOptions();
+ // Update filter fieldset legends.
+ const filterLegends = await getAvailableFilterLegends();
+
+ getFilterRegion().querySelectorAll(Selectors.filter.region).forEach((filterRow, index) => {
+ filterRow.querySelector('legend').innerText = filterLegends[index];
+ });
+
};
/**
* Replace the specified filter row with a new one.
*
* @param {HTMLElement} filterRow
+ * @param {Number} rowNum The number used to label the filter fieldset legend (eg Row 1). Defaults to 1 (the first filter).
* @return {Promise}
*/
- const replaceFilterRow = filterRow => {
+ const replaceFilterRow = (filterRow, rowNum = 1) => {
// Remove the filter object.
removeFilterObject(filterRow.dataset.filterType);
- return Templates.renderForPromise('core_user/local/participantsfilter/filterrow', {})
+ return Templates.renderForPromise('core_user/local/participantsfilter/filterrow', {"rownumber": rowNum})
.then(({html, js}) => {
const newContentNodes = Templates.replaceNode(filterRow, html, js);
/**
* Remove all filters.
+ *
+ * @returns {Promise}
*/
- const removeAllFilters = async() => {
+ const removeAllFilters = () => {
const filters = getFilterRegion().querySelectorAll(Selectors.filter.region);
- filters.forEach((filterRow) => {
- removeOrReplaceFilterRow(filterRow);
- });
+ filters.forEach(filterRow => removeOrReplaceFilterRow(filterRow));
// Refresh the table.
- updateTableFromFilter();
+ return updateTableFromFilter();
+ };
+
+ /**
+ * Remove any empty filters.
+ */
+ const removeEmptyFilters = () => {
+ const filters = getFilterRegion().querySelectorAll(Selectors.filter.region);
+ filters.forEach(filterRow => {
+ const filterType = filterRow.querySelector(Selectors.filter.fields.type);
+ if (!filterType.value) {
+ removeOrReplaceFilterRow(filterRow);
+ }
+ });
};
/**
}
};
+ /**
+ * Set the current filter options based on a provided configuration.
+ *
+ * @param {Object} config
+ * @param {Number} config.jointype
+ * @param {Object} config.filters
+ */
+ const setFilterFromConfig = config => {
+ const filterConfig = Object.entries(config.filters);
+
+ if (!filterConfig.length) {
+ // There are no filters to set from.
+ return;
+ }
+
+ // Set the main join type.
+ filterSet.querySelector(Selectors.filterset.fields.join).value = config.jointype;
+
+ const filterPromises = filterConfig.map(([filterType, filterData]) => {
+ if (filterType === 'courseid') {
+ // The courseid is a special case.
+ return Promise.resolve();
+ }
+
+ const filterValues = filterData.values;
+
+ if (!filterValues.length) {
+ // There are no values for this filter.
+ // Skip it.
+ return Promise.resolve();
+ }
+
+ return addFilterRow().then(([filterRow]) => addFilter(filterRow, filterType, filterValues));
+ });
+
+ Promise.all(filterPromises).then(() => {
+ return removeEmptyFilters();
+ })
+ .then(updateFiltersOptions)
+ .then(updateTableFromFilter)
+ .catch();
+ };
+
/**
* Update the Dynamic table based upon the current filter.
*
);
};
+ /**
+ * Fetch the strings used to populate the fieldset legends for the maximum number of filters possible.
+ *
+ * @return {array}
+ */
+ const getAvailableFilterLegends = async() => {
+ const maxFilters = document.querySelector(Selectors.data.typeListSelect).length - 1;
+ let requests = [];
+
+ [...Array(maxFilters)].forEach((_, rowIndex) => {
+ requests.push({
+ "key": "filterrowlegend",
+ "component": "core_user",
+ // Add 1 since rows begin at 1 (index begins at zero).
+ "param": rowIndex + 1
+ });
+ });
+
+ const legendStrings = await getStrings(requests)
+ .then(fetchedStrings => {
+ return fetchedStrings;
+ })
+ .catch(Notification.exception);
+
+ return legendStrings;
+ };
+
// Add listeners for the main actions.
filterSet.querySelector(Selectors.filterset.region).addEventListener('click', e => {
if (e.target.closest(Selectors.filterset.actions.addRow)) {
filterSet.querySelector(Selectors.filterset.fields.join).addEventListener('change', e => {
filterSet.dataset.filterverb = e.target.value;
});
+
+ const tableRoot = DynamicTable.getTableFromId(filterSet.dataset.tableRegion);
+ const initialFilters = DynamicTable.getFilters(tableRoot);
+ if (initialFilters) {
+ // Apply the initial filter configuration.
+ setFilterFromConfig(initialFilters);
+ }
};
/**
* Unified filter page JS module for the course participants page.
*
+ * @deprecated since Moodle 3.9 MDL-68612 - user unified filter replaced by participants filter.
* @module core_user/unified_filter
* @package core_user
* @copyright 2017 Jun Pataleta
/**
* Init function.
*
+ * @deprecated since Moodle 3.9 MDL-68612 - user unified filter replaced by participants filter.
* @method init
* @private
*/
/**
* Return the unified user filter form.
*
+ * @deprecated since Moodle 3.9 MDL-68612 - user unified filter replaced by participants filter.
* @method getForm
* @return {DOMElement}
*/
/**
* Datasource for the core_user/unified_filter.
+ * @deprecated since Moodle 3.9 MDL-68612 - user unified filter replaced by participants filter.
*
* This module is compatible with core/form-autocomplete.
*
$groups = groups_get_all_groups($this->course->id, $USER->id);
}
- if (empty($groups)) {
+ // Return no data if no groups found (which includes if the only value is 'No group').
+ if (empty($groups) || (count($groups) === 1 && array_key_exists(-1, $groups))) {
return null;
}
'tableregionid' => $this->tableregionid,
'courseid' => $this->context->instanceid,
'filtertypes' => $this->get_filtertypes(),
+ 'rownumber' => 1,
];
return $data;
/**
* Class containing the filter options data for rendering the unified filter autocomplete element for the course participants page.
*
+ * @deprecated since Moodle 3.9 MDL-68612 - Please use \core_user\table\participants_search::class and table filtersets instead.
* @package core_user
* @copyright 2017 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
/**
* Class containing the filter options data for rendering the unified filter autocomplete element for the course participants page.
*
+ * @deprecated since Moodle 3.9 MDL-68612 - Please use \core_user\table\participants_search::class and table filtersets instead.
* @copyright 2017 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ *
*/
class unified_filter implements renderable, templatable {
* @param string|moodle_url $baseurl The url with params needed to call up this page.
*/
public function __construct($filteroptions, $selectedoptions, $baseurl = null) {
+ $deprecatedtext = __CLASS__ . ' class is deprecated. Please use \core\table\participants_search::class' .
+ ' with table filtersets instead.';
+ debugging($deprecatedtext, DEBUG_DEVELOPER);
+
$this->filteroptions = $filteroptions;
$this->selectedoptions = $selectedoptions;
if (!empty($baseurl)) {
* @return array SQL query data in the format ['sql' => '', 'forcedsql' => '', 'params' => []].
*/
protected function get_enrolled_sql(): array {
+ global $USER;
+
$isfrontpage = ($this->context->instanceid == SITEID);
$prefix = 'eu_';
$filteruid = "{$prefix}u.id";
$params = array_merge($params, $methodparams, $statusparams);
}
- // Prepare any groups filtering.
$groupids = [];
if ($this->filterset->has_filter('groups')) {
$groupids = $this->filterset->get_filter('groups')->get_filter_values();
}
+ // Force additional groups filtering if required due to lack of capabilities.
+ // Note: This means results will always be limited to allowed groups, even if the user applies their own groups filtering.
+ $canaccessallgroups = has_capability('moodle/site:accessallgroups', $this->context);
+ $forcegroups = ($this->course->groupmode == SEPARATEGROUPS && !$canaccessallgroups);
+
+ if ($forcegroups) {
+ $allowedgroupids = array_keys(groups_get_all_groups($this->course->id, $USER->id));
+
+ // Users not in any group in a course with separate groups mode should not be able to access the participants filter.
+ if (empty($allowedgroupids)) {
+ // The UI does not support this, so it should not be reachable unless someone is trying to bypass the restriction.
+ throw new \coding_exception('User must be part of a group to filter by participants.');
+ }
+
+ $forceduid = "{$forcedprefix}u.id";
+ $forcedjointype = $this->get_groups_jointype(\core_table\local\filter\filter::JOINTYPE_ANY);
+ $forcedgroupjoin = groups_get_members_join($allowedgroupids, $forceduid, $this->context, $forcedjointype);
+
+ $forcedjoins[] = $forcedgroupjoin->joins;
+ $forcedwhere .= "AND ({$forcedgroupjoin->wheres})";
+
+ $params = array_merge($params, $forcedgroupjoin->params);
+
+ // Remove any filtered groups the user does not have access to.
+ $groupids = array_intersect($allowedgroupids, $groupids);
+ }
+
+ // Prepare any user defined groups filtering.
if ($groupids) {
$groupjoin = groups_get_members_join($groupids, $filteruid, $this->context, $this->get_groups_jointype());
+
$joins[] = $groupjoin->joins;
$params = array_merge($params, $groupjoin->params);
if (!empty($groupjoin->wheres)) {
* Fetch the groups filter's grouplib jointype, based on its filterset jointype.
* This mapping is to ensure compatibility between the two, should their values ever differ.
*
+ * @param int|null $forcedjointype If set, specifies the join type to fetch mapping for (used when applying forced filtering).
+ * If null, then user defined filter join type is used.
* @return int
*/
- protected function get_groups_jointype(): int {
+ protected function get_groups_jointype(?int $forcedjointype = null): int {
+
+ // If applying forced groups filter and no manual groups filtering is applied, add an empty filter so we can map the join.
+ if (!is_null($forcedjointype) && !$this->filterset->has_filter('groups')) {
+ $this->filterset->add_filter(new \core_table\local\filter\integer_filter('groups'));
+ }
+
$groupsfilter = $this->filterset->get_filter('groups');
- switch ($groupsfilter->get_join_type()) {
+ if (is_null($forcedjointype)) {
+ // Fetch join type mapping for a user supplied groups filtering.
+ $filterjointype = $groupsfilter->get_join_type();
+ } else {
+ // Fetch join type mapping for forced groups filtering.
+ $filterjointype = $forcedjointype;
+ }
+
+ switch ($filterjointype) {
case $groupsfilter::JOINTYPE_NONE:
$groupsjoin = GROUPS_JOIN_NONE;
break;
$courseid = optional_param('id', 0, PARAM_INT); // This are required.
$newcourse = optional_param('newcourse', false, PARAM_BOOL);
$roleid = optional_param('roleid', 0, PARAM_INT);
-$groupparam = optional_param('group', 0, PARAM_INT);
+$urlgroupid = optional_param('group', 0, PARAM_INT);
$PAGE->set_url('/user/index.php', array(
'page' => $page,
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('participants'));
-// Get the currently applied filters.
-$filtersapplied = optional_param_array('unified-filters', [], PARAM_NOTAGS);
-$filterwassubmitted = optional_param('unified-filter-submitted', 0, PARAM_BOOL);
-
-// If they passed a role make sure they can view that role.
-if ($roleid) {
- $viewableroles = get_profile_roles($context);
+$filterset = new \core_user\table\participants_filterset();
+$filterset->add_filter(new integer_filter('courseid', filter::JOINTYPE_DEFAULT, [(int)$course->id]));
- // Check if the user can view this role.
- if (array_key_exists($roleid, $viewableroles)) {
- $filtersapplied[] = USER_FILTER_ROLE . ':' . $roleid;
- } else {
- $roleid = 0;
- }
-}
+$participanttable = new \core_user\table\participants("user-index-participants-{$course->id}");
-// Default group ID.
-$groupid = false;
$canaccessallgroups = has_capability('moodle/site:accessallgroups', $context);
-if ($course->groupmode != NOGROUPS) {
- if ($canaccessallgroups) {
- // Change the group if the user can access all groups and has specified group in the URL.
- if ($groupparam) {
- $groupid = $groupparam;
- }
- } else {
- // Otherwise, get the user's default group.
- $groupid = groups_get_course_group($course, true);
- if ($course->groupmode == SEPARATEGROUPS && !$groupid) {
- // The user is not in the group so show message and exit.
- echo $OUTPUT->notification(get_string('notingroup'));
- echo $OUTPUT->footer();
- exit;
- }
- }
-}
-$hasgroupfilter = false;
-$lastaccess = 0;
-$searchkeywords = [];
-$enrolid = 0;
+$filtergroupids = $urlgroupid ? [$urlgroupid] : [];
-$participanttable = new \core_user\table\participants("user-index-participants-{$course->id}");
+// Force group filtering if user should only see a subset of groups' users.
+if ($course->groupmode == SEPARATEGROUPS && !$canaccessallgroups) {
+ $filtergroupids = array_keys(groups_get_all_groups($course->id, $USER->id));
-$filterset = new \core_user\table\participants_filterset();
-$filterset->add_filter(new integer_filter('courseid', filter::JOINTYPE_DEFAULT, [(int)$course->id]));
-$enrolfilter = new integer_filter('enrolments');
-$groupfilter = new integer_filter('groups');
-$keywordfilter = new string_filter('keywords');
-$lastaccessfilter = new integer_filter('accesssince');
-$rolefilter = new integer_filter('roles');
-$statusfilter = new integer_filter('status');
-
-foreach ($filtersapplied as $filter) {
- $filtervalue = explode(':', $filter, 2);
- $value = null;
- if (count($filtervalue) == 2) {
- $key = clean_param($filtervalue[0], PARAM_INT);
- $value = clean_param($filtervalue[1], PARAM_INT);
- } else {
- // Search string.
- $key = USER_FILTER_STRING;
- $value = clean_param($filtervalue[0], PARAM_TEXT);
- }
-
- switch ($key) {
- case USER_FILTER_ENROLMENT:
- $enrolid = $value;
- $enrolfilter->add_filter_value($value);
- break;
- case USER_FILTER_GROUP:
- $groupid = $value;
- $groupfilter->add_filter_value($value);
- $hasgroupfilter = true;
- break;
- case USER_FILTER_LAST_ACCESS:
- $lastaccess = $value;
- $lastaccessfilter->add_filter_value($value);
- break;
- case USER_FILTER_ROLE:
- $roleid = $value;
- $rolefilter->add_filter_value($value);
- break;
- case USER_FILTER_STATUS:
- // We only accept active/suspended statuses.
- if ($value == ENROL_USER_ACTIVE || $value == ENROL_USER_SUSPENDED) {
- $status = $value;
- $statusfilter->add_filter_value($value);
- }
- break;
- default:
- // Search string.
- $searchkeywords[] = $value;
- $keywordfilter->add_filter_value($value);
- break;
+ if (empty($filtergroupids)) {
+ // The user is not in a group so show message and exit.
+ echo $OUTPUT->notification(get_string('notingroup'));
+ echo $OUTPUT->footer();
+ exit();
}
}
-// If course supports groups we may need to set a default.
-if (!empty($groupid)) {
- if ($canaccessallgroups) {
- // User can access all groups, let them filter by whatever was selected.
- $filtersapplied[] = USER_FILTER_GROUP . ':' . $groupid;
- $groupfilter->add_filter_value((int)$groupid);
- } else if (!$filterwassubmitted && $course->groupmode == VISIBLEGROUPS) {
- // If we are in a course with visible groups and the user has not submitted anything and does not have
- // access to all groups, then set a default group.
- $filtersapplied[] = USER_FILTER_GROUP . ':' . $groupid;
- $groupfilter->add_filter_value((int)$groupid);
- } else if (!$hasgroupfilter && $course->groupmode != VISIBLEGROUPS) {
- // The user can't access all groups and has not set a group filter in a course where the groups are not visible
- // then apply a default group filter.
- $filtersapplied[] = USER_FILTER_GROUP . ':' . $groupid;
- $groupfilter->add_filter_value((int)$groupid);
- } else if (!$hasgroupfilter) { // No need for the group id to be set.
- $groupid = false;
- }
+
+// Apply groups filter if included in URL or forced due to lack of capabilities.
+if (!empty($filtergroupids)) {
+ $filterset->add_filter(new integer_filter('groups', filter::JOINTYPE_DEFAULT, $filtergroupids));
}
-if ($groupid > 0 && ($course->groupmode != SEPARATEGROUPS || $canaccessallgroups)) {
+// Display single group information if requested in the URL.
+if ($urlgroupid > 0 && ($course->groupmode != SEPARATEGROUPS || $canaccessallgroups)) {
$grouprenderer = $PAGE->get_renderer('core_group');
- $groupdetailpage = new \core_group\output\group_details($groupid);
+ $groupdetailpage = new \core_group\output\group_details($urlgroupid);
echo $grouprenderer->group_details($groupdetailpage);
}
-// Should use this variable so that we don't break stuff every time a variable is added or changed.
-$baseurl = new moodle_url('/user/index.php', array(
- 'contextid' => $context->id,
- 'id' => $course->id,
- 'perpage' => $perpage));
+// Filter by role if passed via URL (used on profile page).
+if ($roleid) {
+ $viewableroles = get_profile_roles($context);
-$participanttable = new \core_user\table\participants("user-index-participants-{$course->id}");
-$participanttable->define_baseurl($baseurl);
+ // Apply filter if the user can view this role.
+ if (array_key_exists($roleid, $viewableroles)) {
+ $filterset->add_filter(new integer_filter('roles', filter::JOINTYPE_DEFAULT, [$roleid]));
+ }
+}
// Manage enrolments.
$manager = new course_enrolment_manager($PAGE, $course);
foreach ($enrolbuttons as $enrolbutton) {
$enrolbuttonsout .= $enrolrenderer->render($enrolbutton);
}
+
echo html_writer::div($enrolbuttonsout, 'd-flex justify-content-end', [
'data-region' => 'wrapper',
'data-table-uniqueid' => $participanttable->uniqueid,
]);
-// Render the unified filter.
-$renderer = $PAGE->get_renderer('core_user');
-echo $renderer->unified_filter($course, $context, $filtersapplied, $baseurl);
-
// Render the user filters.
$userrenderer = $PAGE->get_renderer('core_user');
echo $userrenderer->participants_filter($context, $participanttable->uniqueid);
echo '<div class="userlist">';
-// Add filters to the baseurl after creating unified_filter to avoid losing them.
-foreach (array_unique($filtersapplied) as $filterix => $filter) {
- $baseurl->param('unified-filters[' . $filterix . ']', $filter);
-}
-
-if (count($groupfilter)) {
- $filterset->add_filter($groupfilter);
-}
-
-if (count($lastaccessfilter)) {
- $filterset->add_filter($lastaccessfilter);
-}
-
-if (count($rolefilter)) {
- $filterset->add_filter($rolefilter);
-}
-
-if (count($enrolfilter)) {
- $filterset->add_filter($enrolfilter);
-}
-
-if (count($statusfilter)) {
- $filterset->add_filter($statusfilter);
-}
-
-if (count($keywordfilter)) {
- $filterset->add_filter($keywordfilter);
-}
-
// Do this so we can get the total number of rows.
ob_start();
$participanttable->set_filterset($filterset);
echo $participanttablehtml;
-$perpageurl = clone($baseurl);
-$perpageurl->remove_params('perpage');
+$perpageurl = new moodle_url('/user/index.php', [
+ 'contextid' => $context->id,
+ 'id' => $course->id,
+]);
$perpagesize = DEFAULT_PAGE_SIZE;
$perpagevisible = false;
$perpagestring = '';
$exclusivemode, $fromctx, $ctx, $rec, $page, $totalpages);
}
-/**
- * Returns the SQL used by the participants table.
- *
- * @param int $courseid The course id
- * @param int $groupid The groupid, 0 means all groups and USERSWITHOUTGROUP no group
- * @param int $accesssince The time since last access, 0 means any time
- * @param int $roleid The role id, 0 means all roles and -1 no roles
- * @param int $enrolid The enrolment id, 0 means all enrolment methods will be returned.
- * @param int $statusid The user enrolment status, -1 means all enrolments regardless of the status will be returned, if allowed.
- * @param string|array $search The search that was performed, empty means perform no search
- * @param string $additionalwhere Any additional SQL to add to where
- * @param array $additionalparams The additional params
- * @return array
- */
-function user_get_participants_sql($courseid, $groupid = 0, $accesssince = 0, $roleid = 0, $enrolid = 0, $statusid = -1,
- $search = '', $additionalwhere = '', $additionalparams = array()) {
- global $DB, $USER, $CFG;
-
- // Get the context.
- $context = \context_course::instance($courseid, MUST_EXIST);
-
- $isfrontpage = ($courseid == SITEID);
-
- // Default filter settings. We only show active by default, especially if the user has no capability to review enrolments.
- $onlyactive = true;
- $onlysuspended = false;
- if (has_capability('moodle/course:enrolreview', $context) && (has_capability('moodle/course:viewsuspendedusers', $context))) {
- switch ($statusid) {
- case ENROL_USER_ACTIVE:
- // Nothing to do here.
- break;
- case ENROL_USER_SUSPENDED:
- $onlyactive = false;
- $onlysuspended = true;
- break;
- default:
- // If the user has capability to review user enrolments, but statusid is set to -1, set $onlyactive to false.
- $onlyactive = false;
- break;
- }
- }
-
- list($esql, $params) = get_enrolled_sql($context, null, $groupid, $onlyactive, $onlysuspended, $enrolid);
-
- $joins = array('FROM {user} u');
- $wheres = array();
-
- $userfields = get_extra_user_fields($context);
- $userfieldssql = user_picture::fields('u', $userfields);
-
- if ($isfrontpage) {
- $select = "SELECT $userfieldssql, u.lastaccess";
- $joins[] = "JOIN ($esql) e ON e.id = u.id"; // Everybody on the frontpage usually.
- if ($accesssince) {
- $wheres[] = user_get_user_lastaccess_sql($accesssince);
- }
- } else {
- $select = "SELECT $userfieldssql, COALESCE(ul.timeaccess, 0) AS lastaccess";
- $joins[] = "JOIN ($esql) e ON e.id = u.id"; // Course enrolled users only.
- // Not everybody has accessed the course yet.
- $joins[] = 'LEFT JOIN {user_lastaccess} ul ON (ul.userid = u.id AND ul.courseid = :courseid)';
- $params['courseid'] = $courseid;
- if ($accesssince) {
- $wheres[] = user_get_course_lastaccess_sql($accesssince);
- }
- }
-
- // Performance hacks - we preload user contexts together with accounts.
- $ccselect = ', ' . context_helper::get_preload_record_columns_sql('ctx');
- $ccjoin = 'LEFT JOIN {context} ctx ON (ctx.instanceid = u.id AND ctx.contextlevel = :contextlevel)';
- $params['contextlevel'] = CONTEXT_USER;
- $select .= $ccselect;
- $joins[] = $ccjoin;
-
- // Limit list to users with some role only.
- if ($roleid) {
- // We want to query both the current context and parent contexts.
- list($relatedctxsql, $relatedctxparams) = $DB->get_in_or_equal($context->get_parent_context_ids(true),
- SQL_PARAMS_NAMED, 'relatedctx');
-
- // Get users without any role.
- if ($roleid == -1) {
- $wheres[] = "u.id NOT IN (SELECT userid FROM {role_assignments} WHERE contextid $relatedctxsql)";
- $params = array_merge($params, $relatedctxparams);
- } else {
- $wheres[] = "u.id IN (SELECT userid FROM {role_assignments} WHERE roleid = :roleid AND contextid $relatedctxsql)";
- $params = array_merge($params, array('roleid' => $roleid), $relatedctxparams);
- }
- }
-
- if (!empty($search)) {
- if (!is_array($search)) {
- $search = [$search];
- }
- foreach ($search as $index => $keyword) {
- $searchkey1 = 'search' . $index . '1';
- $searchkey2 = 'search' . $index . '2';
- $searchkey3 = 'search' . $index . '3';
- $searchkey4 = 'search' . $index . '4';
- $searchkey5 = 'search' . $index . '5';
- $searchkey6 = 'search' . $index . '6';
- $searchkey7 = 'search' . $index . '7';
-
- $conditions = array();
- // Search by fullname.
- $fullname = $DB->sql_fullname('u.firstname', 'u.lastname');
- $conditions[] = $DB->sql_like($fullname, ':' . $searchkey1, false, false);
-
- // Search by email.
- $email = $DB->sql_like('email', ':' . $searchkey2, false, false);
- if (!in_array('email', $userfields)) {
- $maildisplay = 'maildisplay' . $index;
- $userid1 = 'userid' . $index . '1';
- // Prevent users who hide their email address from being found by others
- // who aren't allowed to see hidden email addresses.
- $email = "(". $email ." AND (" .
- "u.maildisplay <> :$maildisplay " .
- "OR u.id = :$userid1". // User can always find himself.
- "))";
- $params[$maildisplay] = core_user::MAILDISPLAY_HIDE;
- $params[$userid1] = $USER->id;
- }
- $conditions[] = $email;
-
- // Search by idnumber.
- $idnumber = $DB->sql_like('idnumber', ':' . $searchkey3, false, false);
- if (!in_array('idnumber', $userfields)) {
- $userid2 = 'userid' . $index . '2';
- // Users who aren't allowed to see idnumbers should at most find themselves
- // when searching for an idnumber.
- $idnumber = "(". $idnumber . " AND u.id = :$userid2)";
- $params[$userid2] = $USER->id;
- }
- $conditions[] = $idnumber;
-
- if (!empty($CFG->showuseridentity)) {
- // Search all user identify fields.
- $extrasearchfields = explode(',', $CFG->showuseridentity);
- foreach ($extrasearchfields as $extrasearchfield) {
- if (in_array($extrasearchfield, ['email', 'idnumber', 'country'])) {
- // Already covered above. Search by country not supported.
- continue;
- }
- $param = $searchkey3 . $extrasearchfield;
- $condition = $DB->sql_like($extrasearchfield, ':' . $param, false, false);
- $params[$param] = "%$keyword%";
- if (!in_array($extrasearchfield, $userfields)) {
- // User cannot see this field, but allow match if their own account.
- $userid3 = 'userid' . $index . '3' . $extrasearchfield;
- $condition = "(". $condition . " AND u.id = :$userid3)";
- $params[$userid3] = $USER->id;
- }
- $conditions[] = $condition;
- }
- }
-
- // Search by middlename.
- $middlename = $DB->sql_like('middlename', ':' . $searchkey4, false, false);
- $conditions[] = $middlename;
-
- // Search by alternatename.
- $alternatename = $DB->sql_like('alternatename', ':' . $searchkey5, false, false);
- $conditions[] = $alternatename;
-
- // Search by firstnamephonetic.
- $firstnamephonetic = $DB->sql_like('firstnamephonetic', ':' . $searchkey6, false, false);
- $conditions[] = $firstnamephonetic;
-
- // Search by lastnamephonetic.
- $lastnamephonetic = $DB->sql_like('lastnamephonetic', ':' . $searchkey7, false, false);
- $conditions[] = $lastnamephonetic;
-
- $wheres[] = "(". implode(" OR ", $conditions) .") ";
- $params[$searchkey1] = "%$keyword%";
- $params[$searchkey2] = "%$keyword%";
- $params[$searchkey3] = "%$keyword%";
- $params[$searchkey4] = "%$keyword%";
- $params[$searchkey5] = "%$keyword%";
- $params[$searchkey6] = "%$keyword%";
- $params[$searchkey7] = "%$keyword%";
- }
- }
-
- if (!empty($additionalwhere)) {
- $wheres[] = $additionalwhere;
- $params = array_merge($params, $additionalparams);
- }
-
- $from = implode("\n", $joins);
- if ($wheres) {
- $where = 'WHERE ' . implode(' AND ', $wheres);
- } else {
- $where = '';
- }
-
- return array($select, $from, $where, $params);
-}
-
-/**
- * Returns the total number of participants for a given course.
- *
- * @param int $courseid The course id
- * @param int $groupid The groupid, 0 means all groups and USERSWITHOUTGROUP no group
- * @param int $accesssince The time since last access, 0 means any time
- * @param int $roleid The role id, 0 means all roles
- * @param int $enrolid The applied filter for the user enrolment ID.
- * @param int $status The applied filter for the user's enrolment status.
- * @param string|array $search The search that was performed, empty means perform no search
- * @param string $additionalwhere Any additional SQL to add to where
- * @param array $additionalparams The additional params
- * @return int
- */
-function user_get_total_participants($courseid, $groupid = 0, $accesssince = 0, $roleid = 0, $enrolid = 0, $statusid = -1,
- $search = '', $additionalwhere = '', $additionalparams = array()) {
- global $DB;
-
- list($select, $from, $where, $params) = user_get_participants_sql($courseid, $groupid, $accesssince, $roleid, $enrolid,
- $statusid, $search, $additionalwhere, $additionalparams);
-
- return $DB->count_records_sql("SELECT COUNT(u.id) $from $where", $params);
-}
-
-/**
- * Returns the participants for a given course.
- *
- * @param int $courseid The course id
- * @param int $groupid The groupid, 0 means all groups and USERSWITHOUTGROUP no group
- * @param int $accesssince The time since last access
- * @param int $roleid The role id
- * @param int $enrolid The applied filter for the user enrolment ID.
- * @param int $status The applied filter for the user's enrolment status.
- * @param string $search The search that was performed
- * @param string $additionalwhere Any additional SQL to add to where
- * @param array $additionalparams The additional params
- * @param string $sort The SQL sort
- * @param int $limitfrom return a subset of records, starting at this point (optional).
- * @param int $limitnum return a subset comprising this many records (optional, required if $limitfrom is set).
- * @return moodle_recordset
- */
-function user_get_participants($courseid, $groupid = 0, $accesssince, $roleid, $enrolid = 0, $statusid, $search,
- $additionalwhere = '', $additionalparams = array(), $sort = '', $limitfrom = 0, $limitnum = 0) {
- global $DB;
-
- list($select, $from, $where, $params) = user_get_participants_sql($courseid, $groupid, $accesssince, $roleid, $enrolid,
- $statusid, $search, $additionalwhere, $additionalparams);
-
- return $DB->get_recordset_sql("$select $from $where $sort", $params, $limitfrom, $limitnum);
-}
-
/**
* Returns SQL that can be used to limit a query to a period where the user last accessed / did not access a course.
*
/**
* Renders the unified filter element for the course participants page.
+ * @deprecated since Moodle 3.9 MDL-68612 - Please use participants_filter() instead.
*
* @param stdClass $course The course object.
* @param context $context The context object.
public function unified_filter($course, $context, $filtersapplied, $baseurl = null) {
global $CFG, $DB, $USER;
+ debugging('core_user_renderer->unified_filter() is deprecated. Please use participants_filter() instead.', DEBUG_DEVELOPER);
+
require_once($CFG->dirroot . '/enrol/locallib.php');
require_once($CFG->dirroot . '/lib/grouplib.php');
$manager = new course_enrolment_manager($this->page, $course);
{{#items}}
<span role="listitem" data-value="{{value}}" aria-selected="true"
class="badge badge-secondary clickable text-wrap text-break line-height-4 mr-2 my-1">
- {{label}}<i class="icon fa fa-times pl-2 mr-0"></i>
+ {{label}}
+ <button class="btn btn-link text-reset p-0" aria-label='{{#str}}clearfilterselection, core_user, {{label}}{{/str}}'>
+ <i class="icon fa fa-times pl-2 mr-0"></i>
+ </button>
</span>
{{/items}}
{{^items}}
"name": "status",
"title": "Status"
}
- ]
+ ],
+ "rownumber": 1
}
}}
<div data-filterregion="filter">
- <div class="border-radius my-2 p-2 bg-white border d-flex flex-column flex-md-row align-items-md-start">
- <div class="d-flex flex-column flex-md-row align-items-md-center">
- <label for="core_user-local-participantsfilter-filterrow-jointype-{{uniqid}}" class="mr-md-2 mb-md-0">{{#str}}match, core_user{{/str}}</label>
- <select class="custom-select mb-1 mb-md-0 mr-md-2" data-filterfield="join" id="core_user-local-participantsfilter-filterrow-jointype-{{uniqid}}">
- <option value="0">{{#str}}none{{/str}}</option>
- <option selected=selected value="1">{{#str}}any{{/str}}</option>
- <option value="2">{{#str}}all{{/str}}</option>
- </select>
- </div>
+ <fieldset>
+ <legend class="sr-only">{{#str}}filterrowlegend, core_user, {{rownumber}}{{/str}}</legend>
+ <div class="border-radius my-2 p-2 bg-white border d-flex flex-column flex-md-row align-items-md-start">
+ <div class="d-flex flex-column flex-md-row align-items-md-center">
+ <label for="core_user-local-participantsfilter-filterrow-jointype-{{uniqid}}" class="mr-md-2 mb-md-0">{{#str}}match, core_user{{/str}}</label>
+ <select class="custom-select mb-1 mb-md-0 mr-md-2" data-filterfield="join" id="core_user-local-participantsfilter-filterrow-jointype-{{uniqid}}">
+ <option value="0">{{#str}}none{{/str}}</option>
+ <option selected=selected value="1">{{#str}}any{{/str}}</option>
+ <option value="2">{{#str}}all{{/str}}</option>
+ </select>
+ </div>
- <label class="sr-only pt-2" for="core_user-local-participantsfilter-filterrow-filtertype-{{uniqid}}">filtertype</label>
- <select class="custom-select mb-1 mb-md-0 mr-md-2" data-filterfield="type" id="core_user-local-participantsfilter-filterrow-filtertype-{{uniqid}}">
- <option value="">{{#str}}selectfiltertype, core_user{{/str}}</option>
- {{#filtertypes}}
- <option value="{{name}}">{{title}}</option>
- {{/filtertypes}}
- </select>
+ <label class="sr-only pt-2" for="core_user-local-participantsfilter-filterrow-filtertype-{{uniqid}}">filtertype</label>
+ <select class="custom-select mb-1 mb-md-0 mr-md-2" data-filterfield="type" id="core_user-local-participantsfilter-filterrow-filtertype-{{uniqid}}">
+ <option value="">{{#str}}selectfiltertype, core_user{{/str}}</option>
+ {{#filtertypes}}
+ <option value="{{name}}">{{title}}</option>
+ {{/filtertypes}}
+ </select>
- <div data-filterregion="value" class="d-md-flex flex-column align-items-start flex-lg-row"></div>
+ <div data-filterregion="value" class="d-md-flex flex-column align-items-start flex-lg-row"></div>
- <button data-filteraction="remove" class="ml-auto icon-no-margin icon-size-4 btn text-reset" aria-label="{{#str}}clearfilterrow, core_user{{/str}}">
- <i class="icon fa fa-times-circle"></i>
- </button>
- </div>
- <div data-filterregion="joinadverb" class="pl-1 text-uppercase font-weight-bold">
- <div data-filterverbfor="0">{{#str}}adverbfor_andnot, core_user{{/str}}</div>
- <div data-filterverbfor="1">{{#str}}adverbfor_or, core_user{{/str}}</div>
- <div data-filterverbfor="2">{{#str}}adverbfor_and, core_user{{/str}}</div>
- </div>
+ <button data-filteraction="remove" class="ml-auto icon-no-margin icon-size-4 btn text-reset" aria-label="{{#str}}clearfilterrow, core_user{{/str}}">
+ <i class="icon fa fa-times-circle"></i>
+ </button>
+ </div>
+ <div data-filterregion="joinadverb" class="pl-1 text-uppercase font-weight-bold">
+ <div data-filterverbfor="0">{{#str}}adverbfor_andnot, core_user{{/str}}</div>
+ <div data-filterverbfor="1">{{#str}}adverbfor_or, core_user{{/str}}</div>
+ <div data-filterverbfor="2">{{#str}}adverbfor_and, core_user{{/str}}</div>
+ </div>
+ </fieldset>
</div>
}}
{{!
@template core_user/unified_filter
+ @deprecated since Moodle 3.9 MDL-68612 - please use core_user/participantsfilter instead.
Template for the unified filter element.
Background:
Given the following "courses" exist:
- | fullname | shortname | groupmode |
- | Course 1 | C1 | 1 |
- | Course 2 | C2 | 0 |
- | Course 3 | C3 | 0 |
+ | fullname | shortname | groupmode | startdate |
+ | Course 1 | C1 | 1 | ##5 months ago## |
+ | 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@example.com | SID4 | AT | SCITY4 | 0 |
- | teacher1 | Teacher | 1 | teacher1@example.com | TID1 | US | TCITY1 | 0 |
+ | student4 | Student | 4 | student4@moodle.com | SID4 | AT | SCITY4 | 0 |
+ | teacher1 | Teacher | 1 | teacher1@example.org | TID1 | US | TCITY1 | 0 |
And the following "course enrolments" exist:
| user | course | role | status | timeend |
| student1 | C1 | student | 0 | |
| teacher1 | C1 | editingteacher | 0 | |
| teacher1 | C2 | editingteacher | 0 | |
| teacher1 | C3 | editingteacher | 0 | |
+ And the following "last access times" exist:
+ | user | course | lastaccess |
+ | student1 | C1 | ##yesterday## |
+ | student1 | C2 | ##2 weeks ago## |
+ | student2 | C1 | ##4 days ago## |
+ | student3 | C1 | ##2 weeks ago## |
+ | student4 | C1 | ##3 weeks ago## |
And the following "groups" exist:
| name | course | idnumber |
| Group 1 | C1 | G1 |
And I should see "Teacher 1" in the "participants" "table"
@javascript
- Scenario Outline: Filter users for a course
+ Scenario Outline: Filter users for a course with a single value
Given I log in as "teacher1"
And I am on "Course 1" course homepage
And I navigate to course participants
- When I open the autocomplete suggestions list
- And I click on "<filter1>" item in the autocomplete list
+ 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"
+ 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"
And I should see "<expected3>" in the "participants" "table"
# Note the 'XX-IGNORE-XX' elements are for when there is less than 2 'not expected' items.
Examples:
- | filter1 | expected1 | expected2 | expected3 | notexpected1 | notexpected2 |
- | Group: No group | Student 1 | Student 4 | Teacher 1 | Student 2 | Student 3 |
- | Group: Group 1 | Student 2 | | | Student 1 | Student 3 |
- | Group: Group 2 | Student 2 | Student 3 | | Student 1 | XX-IGNORE-XX |
- | Role: Teacher | Teacher 1 | | | Student 1 | Student 2 |
- | Status: Active | Teacher 1 | Student 1 | Student 3 | Student 2 | Student 4 |
- | Status: Inactive | Student 2 | Student 4 | | Teacher 1 | Student 1 |
+ | 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 |
+
+ @javascript
+ Scenario Outline: Filter users for a course with multiple values for a single filter
+ Given I log in as "teacher1"
+ 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"
+ 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"
+ And I should see "<expected3>" in the "participants" "table"
+ And I should not see "<notexpected1>" in the "participants" "table"
+ And I should not see "<notexpected2>" in the "participants" "table"
+ # Note the 'XX-IGNORE-XX' elements are for when there is less than 2 'not expected' items.
+
+ Examples:
+ | 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 |
@javascript
Scenario Outline: Filter users which are group members in several courses
Given I log in as "teacher1"
And I am on "Course 3" course homepage
And I navigate to course participants
- When I open the autocomplete suggestions list
- And I click on "<filter1>" item in the autocomplete list
+ 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"
+ 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"
- And I should see "<expected3>" in the "participants" "table"
And I should not see "<notexpected1>" in the "participants" "table"
And I should not see "<notexpected2>" in the "participants" "table"
# Note the 'XX-IGNORE-XX' elements are for when there is less than 2 'not expected' items.
Examples:
- | filter1 | expected1 | expected2 | expected3 | notexpected1 | notexpected2 |
- | Group: No group | Student 3 | | | Student 1 | Student 2 |
- | Group: Group A | Student 1 | Student 2 | | Student 3 | XX-IGNORE-XX |
- | Group: Group B | Student 2 | | | Student 1 | Student 3 |
+ | filtertype | filtervalue | expected1 | expected2 | notexpected1 | notexpected2 |
+ | Groups | No group | Student 3 | | Student 1 | Student 2 |
+ | Groups | Group A | Student 1 | Student 2 | Student 3 | XX-IGNORE-XX |
+ | Groups | Group B | Student 2 | | Student 1 | Student 3 |
+
+ @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"
+ 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 "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"
+ 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"
+ And I should see "Student 3" in the "participants" "table"
+ And 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"
+ 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 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"
+ 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 by clearing all filters.
+ And I click on "Clear 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 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"
+ 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.
+ 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 1" in the "Filter 1" "fieldset"
+ And I should see "Group 2" 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"
+ And I should see "Student 3" in the "participants" "table"
+ And 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"
+ 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 should see "Group 2" in the ".form-autocomplete-suggestions" "css_element"
+ And I click on "Group 1" "list_item"
+ 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"
+ 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"
+ And I should see "Student 3" in the "participants" "table"
+ And I should not see "Student 1" in the "participants" "table"
@javascript
Scenario: Filter users who have no role in a course
And I click on ".form-autocomplete-selection [aria-selected=true]" "css_element"
And I press key "27" in the field "Student 1's role assignments"
And I click on "Save changes" "link"
- When I open the autocomplete suggestions list
- And I click on "Role: No roles" item in the autocomplete list
+ 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"
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"
Given I log in as "teacher1"
And I am on "Course 1" course homepage
And I navigate to course participants
- When I open the autocomplete suggestions list
- And I click on "Role: Student" item in the autocomplete list
- And I open the autocomplete suggestions list
- And I click on "Status: Active" item in the autocomplete list
+ 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 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"
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"
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 open the autocomplete suggestions list
- And I click on "Enrolment methods: Manual enrolments" item in the autocomplete list
- And I open the autocomplete suggestions list
- And I click on "Group: Group 2" item in the autocomplete list
+ And 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 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 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"
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"
- # Deselect the active status filter.
- And I click on "Status: Active" "text" in the ".form-autocomplete-selection" "css_element"
- # Apply Status: Inactive filter.
- And I open the autocomplete suggestions list
- And I click on "Status: Inactive" item in the autocomplete list
+ # Change the active status filter to inactive.
+ And I click on "Remove \"Active\" from filter" "button" in the "Filter 2" "fieldset"
+ And I click on ".form-autocomplete-downarrow" "css_element" in the "Filter 2" "fieldset"
+ And I click on "Inactive" "list_item"
+ 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"
+ # 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"
+ 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 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"
+ And I click on "Apply filters" "button"
+ And I should see "Nothing to display"
@javascript
- Scenario: Filter by keyword
+ Scenario: Filter match by one or more keywords and modified match types
Given I log in as "teacher1"
And I am on "Course 1" course homepage
And I navigate to course participants
- # Note: This is the literal string "student", not the Role student.
- When I set the field "Filters" to "student"
- And I press key "13" in the field "Filters"
+ 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 "Keyword"
+ And I set the field "Type..." to "1@example"
+ And I press key "13" in the field "Type..."
+ When 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 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 "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"
+ 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"
+ # Add a second keyword filter value
+ And I set the field "Type..." to "moodle"
+ And I press key "13" in the field "Type..."
+ 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 not see "Student 1" in the "participants" "table"
+ And I should not see "Teacher 1" 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"
+ 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 see "Student 4" 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 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"
@javascript
Scenario: Reorder users without losing filter
Given I log in as "teacher1"
And I am on "Course 1" course homepage
And I navigate to course participants
- And I open the autocomplete suggestions list
- And I click on "Role: Student" item in the autocomplete list
- When I click on "Surname" "link"
- Then I should see "Role: Student"
+ 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 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"
+ 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"
+
+ @javascript
+ Scenario: Only possible to add filter rows for the number of filters available
+ Given I log in as "teacher1"
+ 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"
+ 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 3" "fieldset" to "Roles"
+ And I click on "Add condition" "button"
+ And I set the field "type" in the "Filter 4" "fieldset" to "Enrolment methods"
+ 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
@javascript
Scenario: Rendering filter options for teachers in a course that don't support groups
Given I log in as "teacher1"
And I am on "Course 2" course homepage
- And I navigate to course participants
- When I open the autocomplete suggestions list
- Then I should see "Role:" in the ".form-autocomplete-suggestions" "css_element"
- And I should see "Enrolment methods:" in the ".form-autocomplete-suggestions" "css_element"
- But I should not see "Group:" in the ".form-autocomplete-suggestions" "css_element"
+ When I navigate to course participants
+ Then I should see "Roles" in the "type" "field"
+ And I should see "Enrolment methods" in the "type" "field"
+ But I should not see "Groups" in the "type" "field"
@javascript
Scenario: Rendering filter options for students who have limited privileges
Given I log in as "student1"
And I am on "Course 2" course homepage
- And I navigate to course participants
- When I open the autocomplete suggestions list
- Then I should see "Role:" in the ".form-autocomplete-suggestions" "css_element"
- But I should not see "Status:" in the ".form-autocomplete-suggestions" "css_element"
- And I should not see "Enrolment methods:" in the ".form-autocomplete-suggestions" "css_element"
+ When I navigate to course participants
+ Then I should see "Roles" in the "type" "field"
+ But I should not see "Status" in the "type" "field"
+ And I should not see "Enrolment methods" in the "type" "field"
@javascript
Scenario: Filter by user identity fields
| 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).
- When I set the field "Filters" to "student1@example.com"
- And I press key "13" in the field "Filters"
+ And I set the field "Type..." to "student1@example.com"
+ And I press key "13" in the field "Type..."
+ 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 "Teacher 1" in the "participants" "table"
# Search by idnumber (only).
- And I click on "student1@example.com" "text" in the ".form-autocomplete-selection" "css_element"
- And I set the field "Filters" to "SID"
- And I press key "13" in the field "Filters"
+ And I click on "Remove \"student1@example.com\" from filter" "button" in the "Filter 1" "fieldset"
+ And I set the field "Type..." to "SID"
+ And I press key "13" in the field "Type..."
+ 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"
# Search by city (only).
- And I click on "SID" "text" in the ".form-autocomplete-selection" "css_element"
- And I set the field "Filters" to "SCITY"
- And I press key "13" in the field "Filters"
+ And I click on "Remove \"SID\" from filter" "button" in the "Filter 1" "fieldset"
+ And I set the field "Type..." to "SCITY"
+ And I press key "13" in the field "Type..."
+ 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"
# Search by country text (only) - should not match.
- And I click on "SCITY" "text" in the ".form-autocomplete-selection" "css_element"
- And I set the field "Filters" to "GB"
- And I press key "13" in the field "Filters"
+ And I click on "Remove \"SCITY\" from filter" "button" in the "Filter 1" "fieldset"
+ And I set the field "Type..." to "GB"
+ And I press key "13" in the field "Type..."
+ And I click on "Apply filters" "button"
And I should see "Nothing to display"
# Check no match.
- And I click on "GB" "text" in the ".form-autocomplete-selection" "css_element"
- And I set the field "Filters" to "NOTHING"
- And I press key "13" in the field "Filters"
+ And I click on "Remove \"GB\" from filter" "button" in the "Filter 1" "fieldset"
+ And I set the field "Type..." to "NOTHING"
+ And I press key "13" in the field "Type..."
+ And I click on "Apply filters" "button"
And I should see "Nothing to display"
@javascript
And I am on "Course 1" course homepage
And I navigate to course participants
# Search by email (only) - should only see visible email + own.
- When I set the field "Filters" to "@example.com"
- And I press key "13" in the field "Filters"
+ And I set the field "type" in the "Filter 1" "fieldset" to "Keyword"
+ And I set the field "Type..." to "@example."
+ And I press key "13" in the field "Type..."
+ 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"
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.
- And I click on "@example.com" "text" in the ".form-autocomplete-selection" "css_element"
- And I set the field "Filters" to "SID"
- And I press key "13" in the field "Filters"
+ And I click on "Remove \"@example.\" from filter" "button" in the "Filter 1" "fieldset"
+ And I set the field "Type..." to "SID"
+ And I press key "13" in the field "Type..."
+ And I click on "Apply filters" "button"
And I should see "Nothing to display"
- And I click on "SID" "text" in the ".form-autocomplete-selection" "css_element"
- And I set the field "Filters" to "TID"
- And I press key "13" in the field "Filters"
+ And I click on "Remove \"SID\" from filter" "button" in the "Filter 1" "fieldset"
+ And I set the field "Type..." to "TID"
+ And I press key "13" in the field "Type..."
+ And I click on "Apply filters" "button"
And I should see "Teacher 1" in the "participants" "table"
- And I set the field "Filters" to "CITY"
- And I press key "13" in the field "Filters"
+ And I should not see "Student 1" in the "participants" "table"
+ And I click on "Remove \"TID\" from filter" "button" in the "Filter 1" "fieldset"
+ And I set the field "Type..." to "CITY"
+ And I press key "13" in the field "Type..."
+ 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.
- And I set the field "Filters" to "NOTHING"
- And I press key "13" in the field "Filters"
+ And I click on "Remove \"CITY\" from filter" "button" in the "Filter 1" "fieldset"
+ And I set the field "Type..." to "NOTHING"
+ And I press key "13" in the field "Type..."
+ And I click on "Apply filters" "button"
And 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"
+ 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 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 key "13" in the field "Type..."
+ 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 not see "Student 4" in the "participants" "table"
+ And I should not see "Teacher 1" in the "participants" "table"
+ 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"
+
+ @javascript
+ Scenario: All filters can be cleared at once
+ Given I log in as "teacher1"
+ 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 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 key "13" in the field "Type..."
+ 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 not see "Student 4" in the "participants" "table"
+ And I should not see "Teacher 1" in the "participants" "table"
+ 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"
+
+ @javascript
+ Scenario: Filterset match type is reset when reducing to a single filter
+ Given I log in as "teacher1"
+ 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"
+ 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 key "13" in the field "Type..."
+ 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"
+ # Match none of student role and @example.com keyword.
+ 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"
+ 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"
+ 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 (any) filterset match type is set.
+ 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 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"
| student3 | G2 |
@javascript
- Scenario: Show all filtered users for a course
+ Scenario: Show all users in a course that match a single filter value
Given I log in as "teacher1"
And I am on "Course 1" course homepage
And I navigate to course participants
- When I open the autocomplete suggestions list
- And I click on "Role: Student" item in the autocomplete list
+ 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"
+ When I click on "Apply filters" "button"
+ Then I should see "24 participants found"
+ And I should see "Show all 24"
+ And I should not see "Show 20 per page"
+ And I should not see "of the following"
And I click on "Show all 24" "link"
- Then I should see "Role: Student"
- And I should see "24 participants found"
And I should see "Show 20 per page"
+ And I should not see "Show all 24"
@javascript
- Scenario: Apply more than one filter and show all users
+ Scenario: Apply one value for more than one filter and show all matching users
Given I log in as "teacher1"
And I am on "Course 1" course homepage
And I navigate to course participants
- When I open the autocomplete suggestions list
- And I click on "Role: Student" item in the autocomplete list
- And I open the autocomplete suggestions list
- And I click on "Status: Active" item in the autocomplete list
+ And I click on "Add condition" "button"
+ 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 "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 click on "Show all 23" "link"
- Then I should see "Role: Student"
- And I should see "Status: Active"
- And I should see "23 participants found"
+ Then I should see "23 participants found"
+ And I should see "Show 20 per page"
+ And I should see "of the following"
And I should see "Student 1"
And I should not see "Student 24"
- And I should see "Show 20 per page"
+ And I should not see "Show all 23"
Then I should see "Group A"
And I should see "Student 1x"
And I should see "Student 2x"
- And I open the autocomplete suggestions list
- And I click on "Group: Group B" item in the autocomplete list
- And I should see "Group B"
+ 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 click on "Apply filters" "button"
And I should see "Student 3x"
And I should see "Student 4x"
return $finaltests;
}
+ /**
+ * Ensure that the groups filter works as expected when separate groups mode is enabled, with the provided test cases.
+ *
+ * @param array $usersdata The list of users to create
+ * @param array $groupsavailable The names of groups that should be created in the course
+ * @param array $filtergroups The names of groups to filter by
+ * @param int $jointype The join type to use when combining filter values
+ * @param int $count The expected count
+ * @param array $expectedusers
+ * @param string $loginusername The user to login as for the tests
+ * @dataProvider groups_separate_provider
+ */
+ public function test_groups_filter_separate_groups(array $usersdata, array $groupsavailable, array $filtergroups, int $jointype,
+ int $count, array $expectedusers, string $loginusername): void {
+
+ $course = $this->getDataGenerator()->create_course();
+ $coursecontext = context_course::instance($course->id);
+ $users = [];
+
+ // Enable separate groups mode on the course.
+ $course->groupmode = SEPARATEGROUPS;
+ $course->groupmodeforce = true;
+ update_course($course);
+
+ // Prepare data for filtering by users in no groups.
+ $nogroupsdata = (object) [
+ 'id' => USERSWITHOUTGROUP,
+ ];
+
+ // Map group names to group data.
+ $groupsdata = ['nogroups' => $nogroupsdata];
+ foreach ($groupsavailable as $groupname) {
+ $groupinfo = [
+ 'courseid' => $course->id,
+ 'name' => $groupname,
+ ];
+
+ $groupsdata[$groupname] = $this->getDataGenerator()->create_group($groupinfo);
+ }
+
+ foreach ($usersdata as $username => $userdata) {
+ $user = $this->getDataGenerator()->create_user(['username' => $username]);
+ $this->getDataGenerator()->enrol_user($user->id, $course->id, 'student');
+
+ if (array_key_exists('groups', $userdata)) {
+ foreach ($userdata['groups'] as $groupname) {
+ $userinfo = [
+ 'userid' => $user->id,
+ 'groupid' => (int) $groupsdata[$groupname]->id,
+ ];
+ $this->getDataGenerator()->create_group_member($userinfo);
+ }
+ }
+
+ $users[$username] = $user;
+
+ if ($username == $loginusername) {
+ $loginuser = $user;
+ }
+ }
+
+ // Create a secondary course with users. We should not see these users.
+ $this->create_course_with_users(1, 1, 1, 1);
+
+ // Log in as the user to be tested.
+ $this->setUser($loginuser);
+
+ // Create the basic filter.
+ $filterset = new participants_filterset();
+ $filterset->add_filter(new integer_filter('courseid', null, [(int) $course->id]));
+
+ // Create the groups filter.
+ $groupsfilter = new integer_filter('groups');
+ $filterset->add_filter($groupsfilter);
+
+ // Configure the filter.
+ foreach ($filtergroups as $filtergroupname) {
+ $groupsfilter->add_filter_value((int) $groupsdata[$filtergroupname]->id);
+ }
+ $groupsfilter->set_join_type($jointype);
+
+ // Run the search.
+ $search = new participants_search($course, $coursecontext, $filterset);
+
+ // Tests on user in no groups should throw an exception as they are not supported (participants are not visible to them).
+ if (in_array('exception', $expectedusers)) {
+ $this->expectException(\coding_exception::class);
+ $rs = $search->get_participants();
+ } else {
+ // All other cases are tested as normal.
+ $rs = $search->get_participants();
+ $this->assertInstanceOf(moodle_recordset::class, $rs);
+ $records = $this->convert_recordset_to_array($rs);
+
+ $this->assertCount($count, $records);
+ $this->assertEquals($count, $search->get_total_participants_count());
+
+ foreach ($expectedusers as $expecteduser) {
+ $this->assertArrayHasKey($users[$expecteduser]->id, $records);
+ }
+ }
+ }
+
+ /**
+ * Data provider for groups filter tests.
+ *
+ * @return array
+ */
+ public function groups_separate_provider(): array {
+ $tests = [
+ 'Users in different groups with separate groups mode enabled' => (object) [
+ 'groupsavailable' => [
+ 'groupa',
+ 'groupb',
+ 'groupc',
+ ],
+ 'users' => [
+ 'a' => [
+ 'groups' => ['groupa'],
+ ],
+ 'b' => [
+ 'groups' => ['groupb'],
+ ],
+ 'c' => [
+ 'groups' => ['groupa', 'groupb'],
+ ],
+ 'd' => [
+ 'groups' => [],
+ ],
+ ],
+ 'expect' => [
+ // Tests for jointype: ANY.
+ 'ANY: No filter, user in one group' => (object) [
+ 'loginuser' => 'a',
+ 'groups' => [],
+ 'jointype' => filter::JOINTYPE_ANY,
+ 'count' => 2,
+ 'expectedusers' => [
+ 'a',
+ 'c',
+ ],
+ ],
+ 'ANY: No filter, user in multiple groups' => (object) [
+ 'loginuser' => 'c',
+ 'groups' => [],
+ 'jointype' => filter::JOINTYPE_ANY,
+ 'count' => 3,
+ 'expectedusers' => [
+ 'a',
+ 'b',
+ 'c',
+ ],
+ ],
+ 'ANY: No filter, user in no groups' => (object) [
+ 'loginuser' => 'd',
+ 'groups' => [],
+ 'jointype' => filter::JOINTYPE_ANY,
+ 'count' => 0,
+ 'expectedusers' => ['exception'],
+ ],
+ 'ANY: Filter on a single group, user in one group' => (object) [
+ 'loginuser' => 'a',
+ 'groups' => ['groupa'],
+ 'jointype' => filter::JOINTYPE_ANY,
+ 'count' => 2,
+ 'expectedusers' => [
+ 'a',
+ 'c',
+ ],
+ ],
+ 'ANY: Filter on a single group, user in multple groups' => (object) [
+ 'loginuser' => 'c',
+ 'groups' => ['groupa'],
+ 'jointype' => filter::JOINTYPE_ANY,
+ 'count' => 2,
+ 'expectedusers' => [
+ 'a',
+ 'c',
+ ],
+ ],
+ 'ANY: Filter on a single group, user in no groups' => (object) [
+ 'loginuser' => 'd',
+ 'groups' => ['groupa'],
+ 'jointype' => filter::JOINTYPE_ANY,
+ 'count' => 0,
+ 'expectedusers' => ['exception'],
+ ],
+ 'ANY: Filter on multiple groups, user in one group (ignore invalid groups)' => (object) [
+ 'loginuser' => 'a',
+ 'groups' => ['groupa', 'groupb'],
+ 'jointype' => filter::JOINTYPE_ANY,
+ 'count' => 2,
+ 'expectedusers' => [
+ 'a',
+ 'c',
+ ],
+ ],
+ 'ANY: Filter on multiple groups, user in multiple groups' => (object) [
+ 'loginuser' => 'c',
+ 'groups' => ['groupa', 'groupb'],
+ 'jointype' => filter::JOINTYPE_ANY,
+ 'count' => 3,
+ 'expectedusers' => [
+ 'a',
+ 'b',
+ 'c',
+ ],
+ ],
+ 'ANY: Filter on multiple groups or no groups, user in multiple groups (ignore no groups)' => (object) [
+ 'loginuser' => 'c',
+ 'groups' => ['groupa', 'groupb', 'nogroups'],
+ 'jointype' => filter::JOINTYPE_ANY,
+ 'count' => 3,
+ 'expectedusers' => [
+ 'a',
+ 'b',
+ 'c',
+ ],
+ ],
+
+ // Tests for jointype: ALL.
+ 'ALL: No filter, user in one group' => (object) [
+ 'loginuser' => 'a',
+ 'groups' => [],
+ 'jointype' => filter::JOINTYPE_ALL,
+ 'count' => 2,
+ 'expectedusers' => [
+ 'a',
+ 'c',
+ ],
+ ],
+ 'ALL: No filter, user in multiple groups' => (object) [
+ 'loginuser' => 'c',
+ 'groups' => [],
+ 'jointype' => filter::JOINTYPE_ALL,
+ 'count' => 3,
+ 'expectedusers' => [
+ 'a',
+ 'b',
+ 'c',
+ ],
+ ],
+ 'ALL: No filter, user in no groups' => (object) [
+ 'loginuser' => 'd',
+ 'groups' => [],
+ 'jointype' => filter::JOINTYPE_ALL,
+ 'count' => 0,
+ 'expectedusers' => ['exception'],
+ ],
+ 'ALL: Filter on a single group, user in one group' => (object) [
+ 'loginuser' => 'a',
+ 'groups' => ['groupa'],
+ 'jointype' => filter::JOINTYPE_ALL,
+ 'count' => 2,
+ 'expectedusers' => [
+ 'a',
+ 'c',
+ ],
+ ],
+ 'ALL: Filter on a single group, user in multple groups' => (object) [
+ 'loginuser' => 'c',
+ 'groups' => ['groupa'],
+ 'jointype' => filter::JOINTYPE_ALL,
+ 'count' => 2,
+ 'expectedusers' => [
+ 'a',
+ 'c',
+ ],
+ ],
+ 'ALL: Filter on a single group, user in no groups' => (object) [
+ 'loginuser' => 'd',
+ 'groups' => ['groupa'],
+ 'jointype' => filter::JOINTYPE_ALL,
+ 'count' => 0,
+ 'expectedusers' => ['exception'],
+ ],
+ 'ALL: Filter on multiple groups, user in one group (ignore invalid groups)' => (object) [
+ 'loginuser' => 'a',
+ 'groups' => ['groupa', 'groupb'],
+ 'jointype' => filter::JOINTYPE_ALL,
+ 'count' => 2,
+ 'expectedusers' => [
+ 'a',
+ 'c',
+ ],
+ ],
+ 'ALL: Filter on multiple groups, user in multiple groups' => (object) [
+ 'loginuser' => 'c',
+ 'groups' => ['groupa', 'groupb'],
+ 'jointype' => filter::JOINTYPE_ALL,
+ 'count' => 1,
+ 'expectedusers' => [
+ 'c',
+ ],
+ ],
+ 'ALL: Filter on multiple groups or no groups, user in multiple groups (ignore no groups)' => (object) [
+ 'loginuser' => 'c',
+ 'groups' => ['groupa', 'groupb', 'nogroups'],
+ 'jointype' => filter::JOINTYPE_ALL,
+ 'count' => 1,
+ 'expectedusers' => [
+ 'c',
+ ],
+ ],
+
+ // Tests for jointype: NONE.
+ 'NONE: No filter, user in one group' => (object) [
+ 'loginuser' => 'a',
+ 'groups' => [],
+ 'jointype' => filter::JOINTYPE_NONE,
+ 'count' => 2,
+ 'expectedusers' => [
+ 'a',
+ 'c',
+ ],
+ ],
+ 'NONE: No filter, user in multiple groups' => (object) [
+ 'loginuser' => 'c',
+ 'groups' => [],
+ 'jointype' => filter::JOINTYPE_NONE,
+ 'count' => 3,
+ 'expectedusers' => [
+ 'a',
+ 'b',
+ 'c',
+ ],
+ ],
+ 'NONE: No filter, user in no groups' => (object) [
+ 'loginuser' => 'd',
+ 'groups' => [],
+ 'jointype' => filter::JOINTYPE_NONE,
+ 'count' => 0,
+ 'expectedusers' => ['exception'],
+ ],
+ 'NONE: Filter on a single group, user in one group' => (object) [
+ 'loginuser' => 'a',
+ 'groups' => ['groupa'],
+ 'jointype' => filter::JOINTYPE_NONE,
+ 'count' => 0,
+ 'expectedusers' => [],
+ ],
+ 'NONE: Filter on a single group, user in multple groups' => (object) [
+ 'loginuser' => 'c',
+ 'groups' => ['groupa'],
+ 'jointype' => filter::JOINTYPE_NONE,
+ 'count' => 1,
+ 'expectedusers' => [
+ 'b',
+ ],
+ ],
+ 'NONE: Filter on a single group, user in no groups' => (object) [
+ 'loginuser' => 'd',
+ 'groups' => ['groupa'],
+ 'jointype' => filter::JOINTYPE_NONE,
+ 'count' => 0,
+ 'expectedusers' => ['exception'],
+ ],
+ 'NONE: Filter on multiple groups, user in one group (ignore invalid groups)' => (object) [
+ 'loginuser' => 'a',
+ 'groups' => ['groupa', 'groupb'],
+ 'jointype' => filter::JOINTYPE_NONE,
+ 'count' => 0,
+ 'expectedusers' => [],
+ ],
+ 'NONE: Filter on multiple groups, user in multiple groups' => (object) [
+ 'loginuser' => 'c',
+ 'groups' => ['groupa', 'groupb'],
+ 'jointype' => filter::JOINTYPE_NONE,
+ 'count' => 0,
+ 'expectedusers' => [],
+ ],
+ 'NONE: Filter on multiple groups or no groups, user in multiple groups (ignore no groups)' => (object) [
+ 'loginuser' => 'c',
+ 'groups' => ['groupa', 'groupb', 'nogroups'],
+ 'jointype' => filter::JOINTYPE_NONE,
+ 'count' => 0,
+ 'expectedusers' => [],
+ ],
+ ],
+ ],
+ ];
+
+ $finaltests = [];
+ foreach ($tests as $testname => $testdata) {
+ foreach ($testdata->expect as $expectname => $expectdata) {
+ $finaltests["{$testname} => {$expectname}"] = [
+ 'users' => $testdata->users,
+ 'groupsavailable' => $testdata->groupsavailable,
+ 'filtergroups' => $expectdata->groups,
+ 'jointype' => $expectdata->jointype,
+ 'count' => $expectdata->count,
+ 'expectedusers' => $expectdata->expectedusers,
+ 'loginusername' => $expectdata->loginuser,
+ ];
+ }
+ }
+
+ return $finaltests;
+ }
+
+
/**
* Ensure that the last access filter works as expected with the provided test cases.
*
self::assertSame('5', $got['timezone']);
self::assertSame('0', $got['mailformat']);
}
-
- /**
- * Test returning the total number of participants.
- */
- public function test_user_get_total_participants() {
- global $DB;
-
- $this->resetAfterTest();
-
- // Create a course.
- $course = self::getDataGenerator()->create_course();
-
- // Create a teacher.
- $teacher = self::getDataGenerator()->create_user(['firstname' => 'searchforthis']);
-
- // Create a bunch of students.
- $student1 = self::getDataGenerator()->create_user(['firstname' => 'searchforthis']);
- $student2 = self::getDataGenerator()->create_user(['firstname' => 'searchforthis']);
- $student3 = self::getDataGenerator()->create_user(['firstname' => 'searchforthis']);
-
- // Create a group.
- $group = self::getDataGenerator()->create_group(array('courseid' => $course->id));
-
- // Enrol the students.
- self::getDataGenerator()->enrol_user($student1->id, $course->id);
- self::getDataGenerator()->enrol_user($student2->id, $course->id);
- self::getDataGenerator()->enrol_user($student3->id, $course->id);
-
- // Enrol the teacher.
- $roleids = $DB->get_records_menu('role', null, '', 'shortname, id');
- self::getDataGenerator()->enrol_user($teacher->id, $course->id, $roleids['editingteacher']);
-
- // Add the teacher and two of the students to the group.
- groups_add_member($group->id, $teacher->id);
- groups_add_member($group->id, $student1->id);
- groups_add_member($group->id, $student2->id);
-
- // Set it so the teacher and two of the students have not accessed the courses within the last day,
- // but only one of the students is in the group.
- $accesssince = time() - DAYSECS;
- $lastaccess = new stdClass();
- $lastaccess->userid = $teacher->id;
- $lastaccess->courseid = $course->id;
- $lastaccess->timeaccess = time() - DAYSECS;
- $DB->insert_record('user_lastaccess', $lastaccess);
-
- $lastaccess->userid = $student1->id;
- $DB->insert_record('user_lastaccess', $lastaccess);
-
- $lastaccess->userid = $student3->id;
- $DB->insert_record('user_lastaccess', $lastaccess);
-
- // Now, when we perform the following search we should only return 2 users. Student who belong to
- // the group and have the name 'searchforthis' and have not accessed the course in the last day.
- $count = user_get_total_participants($course->id, $group->id, $accesssince + 1, $roleids['student'], 0, -1,
- 'searchforthis');
-
- $this->assertEquals(2, $count);
- }
-
- /**
- * Test returning the number of participants on the front page.
- */
- public function test_user_get_total_participants_on_front_page() {
- $this->resetAfterTest();
-
- // Set it so that only 3 users have not accessed the site within the last day (including one which has never accessed it).
- $accesssince = time() - DAYSECS;
-
- // Create a bunch of users.
- $user1 = self::getDataGenerator()->create_user(['firstname' => 'searchforthis', 'lastaccess' => $accesssince]);
- $user2 = self::getDataGenerator()->create_user(['firstname' => 'searchforthis', 'lastaccess' => $accesssince]);
- $user3 = self::getDataGenerator()->create_user(['firstname' => 'searchforthis', 'lastaccess' => time()]);
- $user4 = self::getDataGenerator()->create_user(['firstname' => 'searchforthis']);
-
- // Create a group.
- $group = self::getDataGenerator()->create_group(array('courseid' => SITEID));
-
- // Add 3 of the users to a group.
- groups_add_member($group->id, $user1->id);
- groups_add_member($group->id, $user2->id);
- groups_add_member($group->id, $user3->id);
-
- // Now, when we perform the following search we should only return 2 users. Users who belong to
- // the group and have the name 'searchforthis' and have not accessed the site in the last day.
- $count = user_get_total_participants(SITEID, $group->id, $accesssince + 1, 0, 0, -1, 'searchforthis');
-
- $this->assertEquals(2, $count);
- }
-
- /**
- * Test returning the participants.
- */
- public function test_user_get_participants() {
- global $DB;
-
- $this->resetAfterTest();
-
- // Create a course.
- $course = self::getDataGenerator()->create_course();
-
- // Create a teacher.
- $teacher = self::getDataGenerator()->create_user(['firstname' => 'searchforthis']);
-
- // Create a bunch of students.
- $student1 = self::getDataGenerator()->create_user(['firstname' => 'searchforthis']);
- $student2 = self::getDataGenerator()->create_user(['firstname' => 'searchforthis']);
- $student3 = self::getDataGenerator()->create_user(['firstname' => 'searchforthis']);
-
- // Create a group.
- $group = self::getDataGenerator()->create_group(array('courseid' => $course->id));
-
- // Enrol the students.
- self::getDataGenerator()->enrol_user($student1->id, $course->id);
- self::getDataGenerator()->enrol_user($student2->id, $course->id);
- self::getDataGenerator()->enrol_user($student3->id, $course->id);
-
- // Enrol the teacher.
- $roleids = $DB->get_records_menu('role', null, '', 'shortname, id');
- self::getDataGenerator()->enrol_user($teacher->id, $course->id, $roleids['editingteacher']);
-
- // Add the teacher and two of the students to the group.
- groups_add_member($group->id, $teacher->id);
- groups_add_member($group->id, $student1->id);
- groups_add_member($group->id, $student2->id);
-
- // Set it so the teacher and two of the students have not accessed the course within the last day, but only one of
- // the students is in the group (student 3 has never accessed the course).
- $accesssince = time() - DAYSECS;
- $lastaccess = new stdClass();
- $lastaccess->userid = $teacher->id;
- $lastaccess->courseid = $course->id;
- $lastaccess->timeaccess = time() - DAYSECS;
- $DB->insert_record('user_lastaccess', $lastaccess);
-
- $lastaccess->userid = $student1->id;
- $DB->insert_record('user_lastaccess', $lastaccess);
-
- $lastaccess->userid = $student2->id;
- $lastaccess->timeaccess = time();
- $DB->insert_record('user_lastaccess', $lastaccess);
-
- // Now, when we perform the following search we should only return 1 user. A student who belongs to
- // the group and has the name 'searchforthis' and has not accessed the course in the last day.
- $userset = user_get_participants($course->id, $group->id, $accesssince + 1, $roleids['student'], 0, -1, 'searchforthis');
-
- $this->assertEquals($student1->id, $userset->current()->id);
- $this->assertEquals(1, iterator_count($userset));
-
- // Search for users without any group.
- $userset = user_get_participants($course->id, USERSWITHOUTGROUP, 0, $roleids['student'], 0, -1, '');
-
- $this->assertEquals($student3->id, $userset->current()->id);
- $this->assertEquals(1, iterator_count($userset));
- }
-
- /**
- * Test returning the participants on the front page.
- */
- public function test_user_get_participants_on_front_page() {
- $this->resetAfterTest();
-
- // Set it so that only 3 users have not accessed the site within the last day (user 4 has never accessed the site).
- $accesssince = time() - DAYSECS;
-
- // Create a bunch of users.
- $user1 = self::getDataGenerator()->create_user(['firstname' => 'searchforthis', 'lastaccess' => $accesssince]);
- $user2 = self::getDataGenerator()->create_user(['firstname' => 'searchforthis', 'lastaccess' => $accesssince]);
- $user3 = self::getDataGenerator()->create_user(['firstname' => 'searchforthis', 'lastaccess' => time()]);
- $user4 = self::getDataGenerator()->create_user(['firstname' => 'searchforthis']);
-
- // Create a group.
- $group = self::getDataGenerator()->create_group(array('courseid' => SITEID));
-
- // Add 3 of the users to a group.
- groups_add_member($group->id, $user1->id);
- groups_add_member($group->id, $user2->id);
- groups_add_member($group->id, $user3->id);
-
- // Now, when we perform the following search we should only return 2 users. Users who belong to
- // the group and have the name 'searchforthis' and have not accessed the site in the last day.
- $userset = user_get_participants(SITEID, $group->id, $accesssince + 1, 0, 0, -1, 'searchforthis', '', array(),
- 'ORDER BY id ASC');
-
- $this->assertEquals($user1->id, $userset->current()->id);
- $userset->next();
- $this->assertEquals($user2->id, $userset->current()->id);
- }
}
This files describes API changes for code that uses the user API.
+=== 3.9 ===
+
+* The unified filter has been replaced by the participants filter. The following have therefore been deprecated:
+ * Library functions:
+ * user_get_participants_sql
+ * user_get_total_participants
+ * user_get_participants
+ * Unified filter renderer (core_user_renderer::unified_filter)
+ * Unified filter renderable (\core_user\output\unified_filter)
+ * Unified filter JavaScript (core_user/unified_filter.js and core_user/unified_filter_datasource.js)
+ * Unified filter template (unified_filter.mustache)
+
=== 3.6 ===
* The following functions have been finally deprecated and can not be used anymore: