MDL-59366 user: Create a unified filter for participants page
authorJun Pataleta <jun@moodle.com>
Thu, 20 Jul 2017 05:33:02 +0000 (13:33 +0800)
committerJun Pataleta <jun@moodle.com>
Mon, 31 Jul 2017 05:04:06 +0000 (13:04 +0800)
Part of MDL-59290.

13 files changed:
lang/en/moodle.php
user/amd/build/unified_filter.min.js [new file with mode: 0644]
user/amd/build/unified_filter_datasource.min.js [new file with mode: 0644]
user/amd/src/unified_filter.js [new file with mode: 0644]
user/amd/src/unified_filter_datasource.js [new file with mode: 0644]
user/classes/output/unified_filter.php [new file with mode: 0644]
user/classes/participants_table.php
user/index.php
user/lib.php
user/renderer.php
user/templates/unified_filter.mustache [new file with mode: 0644]
user/tests/behat/filter_participants.feature [new file with mode: 0644]
user/tests/userlib_test.php

index 6f54a70..da22e2d 100644 (file)
@@ -982,6 +982,9 @@ $string['chooseuser'] = 'Choose a user';
 $string['courseduration'] = 'Course duration';
 $string['courseduration_desc'] = 'The course duration is used to calculate the default course end date. The course end date is only used for reports. Users can still enter the course after the end date.';
 $string['eventcontentviewed'] = 'Content viewed';
+$string['filter'] = 'Filter';
+$string['filteroption'] = '{$a->criteria}: {$a->value}';
+$string['filters'] = 'Filters';
 $string['icqnumber'] = 'ICQ number';
 $string['icon'] = 'Icon';
 $string['idnumber'] = 'ID number';
@@ -1343,6 +1346,7 @@ $string['nocomments'] = 'No comments';
 $string['nodstpresets'] = 'The administrator has not enabled Daylight Savings Time support.';
 $string['nofilesselected'] = 'No files have been selected to restore';
 $string['nofilesyet'] = 'No files have been uploaded to your course yet';
+$string['nofiltersapplied'] = 'No filters applied';
 $string['nograde'] = 'No grade';
 $string['nohelpforactivityorresource'] = 'There is currently no help associated with this resource or activity';
 $string['nochange'] = 'No change';
@@ -2002,6 +2006,7 @@ $string['userdescription'] = 'Description';
 $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';
diff --git a/user/amd/build/unified_filter.min.js b/user/amd/build/unified_filter.min.js
new file mode 100644 (file)
index 0000000..daa1a06
Binary files /dev/null and b/user/amd/build/unified_filter.min.js differ
diff --git a/user/amd/build/unified_filter_datasource.min.js b/user/amd/build/unified_filter_datasource.min.js
new file mode 100644 (file)
index 0000000..237dc0c
Binary files /dev/null and b/user/amd/build/unified_filter_datasource.min.js differ
diff --git a/user/amd/src/unified_filter.js b/user/amd/src/unified_filter.js
new file mode 100644 (file)
index 0000000..c2c52cc
--- /dev/null
@@ -0,0 +1,73 @@
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Unified filter page JS module for the course participants page.
+ *
+ * @module     core_user/unified_filter
+ * @package    core_user
+ * @copyright  2017 Jun Pataleta
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+define(['jquery', 'core/form-autocomplete', 'core/str', 'core/notification'],
+        function($, Autocomplete, Str, Notification) {
+
+    /**
+     * Selectors.
+     *
+     * @access private
+     * @type {{UNIFIED_FILTERS: string}}
+     */
+    var SELECTORS = {
+        UNIFIED_FILTERS: '#unified-filters'
+    };
+
+    /**
+     * Init function.
+     *
+     * @method init
+     * @private
+     */
+    var init = function() {
+        var stringkeys = [
+            {
+                key: 'userfilterplaceholder',
+                component: 'moodle'
+            },
+            {
+                key: 'nofiltersapplied',
+                component: 'moodle'
+            }
+        ];
+        Str.get_strings(stringkeys).done(function(langstrings) {
+            var placeholder = langstrings[0];
+            var noSelectionString = langstrings[1];
+            Autocomplete.enhance(SELECTORS.UNIFIED_FILTERS, true, 'core_user/unified_filter_datasource', placeholder,
+                false, true, noSelectionString, true);
+        }).fail(Notification.exception);
+    };
+
+    return /** @alias module:core/form-autocomplete */ {
+        // Public variables and functions.
+        /**
+         * Initialise the unified user filter.
+         *
+         * @method init
+         */
+        'init': function() {
+            init();
+        }
+    };
+});
diff --git a/user/amd/src/unified_filter_datasource.js b/user/amd/src/unified_filter_datasource.js
new file mode 100644 (file)
index 0000000..234a8c4
--- /dev/null
@@ -0,0 +1,109 @@
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Datasource for the core_user/unified_filter.
+ *
+ * This module is compatible with core/form-autocomplete.
+ *
+ * @package    core_user
+ * @copyright  2017 Jun Pataleta
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define(['jquery', 'core/ajax', 'core/notification'], function($, Ajax, Notification) {
+
+    return /** @alias module:core_user/unified_filter_datasource */ {
+        /**
+         * List filter options.
+         *
+         * @param {String} selector The select element selector.
+         * @param {String} query The query string.
+         * @return {Promise}
+         */
+        list: function(selector, query) {
+            var filteredOptions = [];
+
+            var el = $(selector);
+            var originalOptions = $(selector).data('originaloptionsjson');
+            var selectedFilters = el.val();
+            var categoriesToSkip = [];
+            $.each(selectedFilters, function(index, filter) {
+                var filterCatAndValue = filter.split(':', 2);
+                if (filterCatAndValue.length === 2) {
+                    var category = filterCatAndValue[0];
+                    categoriesToSkip.push(category);
+                }
+            });
+            $.each(originalOptions, function(index, option) {
+                // Skip option if it does not contain the query string.
+                if ($.trim(query) !== '' && option.label.toLocaleLowerCase().indexOf(query.toLocaleLowerCase()) === -1) {
+                    return true;
+                }
+                // Skip filters that have already been selected.
+                if ($.inArray(option.value, selectedFilters) > -1) {
+                    return true;
+                }
+                // Skip filters for categories that belong to the already selected filters.
+                var optionCatAndValue = option.value.split(':', 2);
+                if (optionCatAndValue.length === 2) {
+                    var category = optionCatAndValue[0];
+                    if ($.inArray(category, categoriesToSkip) > -1) {
+                        return true;
+                    }
+                }
+
+                filteredOptions.push(option);
+                return true;
+            });
+
+            var deferred = new $.Deferred();
+            deferred.resolve(filteredOptions);
+
+            return deferred.promise();
+        },
+
+        /**
+         * Process the results for auto complete elements.
+         *
+         * @param {String} selector The selector of the auto complete element.
+         * @param {Array} results An array or results.
+         * @return {Array} New array of results.
+         */
+        processResults: function(selector, results) {
+            var options = [];
+            $.each(results, function(index, data) {
+                options.push({
+                    value: data.value,
+                    label: data.label
+                });
+            });
+            return options;
+        },
+
+        /**
+         * Source of data for Ajax element.
+         *
+         * @param {String} selector The selector of the auto complete element.
+         * @param {String} query The query string.
+         * @param {Function} callback A callback function receiving an array of results.
+         */
+        /* eslint-disable promise/no-callback-in-promise */
+        transport: function(selector, query, callback) {
+            this.list(selector, query).then(callback).catch(Notification.exception);
+        }
+    };
+
+});
diff --git a/user/classes/output/unified_filter.php b/user/classes/output/unified_filter.php
new file mode 100644 (file)
index 0000000..503fad1
--- /dev/null
@@ -0,0 +1,90 @@
+<?php
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+
+/**
+ * Class containing the filter options data for rendering the unified filter autocomplete element for the course participants page.
+ *
+ * @package    core_user
+ * @copyright  2017 Jun Pataleta
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace core_user\output;
+
+use renderable;
+use renderer_base;
+use stdClass;
+use templatable;
+
+defined('MOODLE_INTERNAL') || die();
+
+/**
+ * Class containing the filter options data for rendering the unified filter autocomplete element for the course participants page.
+ *
+ * @copyright  2017 Jun Pataleta
+ * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class unified_filter implements renderable, templatable {
+
+    /** @var array $filteroptions The filter options. */
+    protected $filteroptions;
+
+    /** @var array $selectedoptions The list of selected filter option values. */
+    protected $selectedoptions;
+
+    /**
+     * unified_filter constructor.
+     *
+     * @param array $filteroptions The filter options.
+     * @param array $selectedoptions The list of selected filter option values.
+     */
+    public function __construct($filteroptions, $selectedoptions) {
+        $this->filteroptions = $filteroptions;
+        $this->selectedoptions = $selectedoptions;
+    }
+
+    /**
+     * Function to export the renderer data in a format that is suitable for a mustache template.
+     *
+     * @param renderer_base $output Used to do a final render of any components that need to be rendered for export.
+     * @return stdClass|array
+     */
+    public function export_for_template(renderer_base $output) {
+        global $PAGE;
+        $data = new stdClass();
+        $data->action = $PAGE->url->out(false);
+
+        foreach ($this->selectedoptions as $option) {
+            if (!isset($this->filteroptions[$option])) {
+                $this->filteroptions[$option] = $option;
+            }
+        }
+
+        $data->filteroptions = [];
+        $originalfilteroptions = [];
+        foreach ($this->filteroptions as $value => $label) {
+            $selected = in_array($value, $this->selectedoptions);
+            $filteroption = (object)[
+                'value' => $value,
+                'label' => $label
+            ];
+            $originalfilteroptions[] = $filteroption;
+            $filteroption->selected = $selected;
+            $data->filteroptions[] = $filteroption;
+        }
+        $data->originaloptionsjson = json_encode($originalfilteroptions);
+        return $data;
+    }
+}
\ No newline at end of file
index 0364f54..2bc24c6 100644 (file)
@@ -64,6 +64,16 @@ class participants_table extends \table_sql {
      */
     protected $roleid;
 
+    /**
+     * @var int $enrolid The applied filter for the user enrolment ID.
+     */
+    protected $enrolid;
+
+    /**
+     * @var int $status The applied filter for the user's enrolment status.
+     */
+    protected $status;
+
     /**
      * @var string $search The string being searched.
      */
@@ -121,11 +131,13 @@ class participants_table extends \table_sql {
      * @param int|false $currentgroup False if groups not used, int if groups used, 0 for all groups.
      * @param int $accesssince The time the user last accessed the site
      * @param int $roleid The role we are including, 0 means all enrolled users
-     * @param string $search The string being searched
+     * @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 string(s)
      * @param bool $bulkoperations Is the user allowed to perform bulk operations?
      * @param bool $selectall Has the user selected all users on the page?
      */
-    public function __construct($courseid, $currentgroup, $accesssince, $roleid, $search,
+    public function __construct($courseid, $currentgroup, $accesssince, $roleid, $enrolid, $status, $search,
             $bulkoperations, $selectall) {
         global $CFG;
 
@@ -200,6 +212,8 @@ class participants_table extends \table_sql {
         $this->accesssince = $accesssince;
         $this->roleid = $roleid;
         $this->search = $search;
+        $this->enrolid = $enrolid;
+        $this->status = $status;
         $this->selectall = $selectall;
         $this->countries = get_string_manager()->get_list_of_countries();
         $this->extrafields = $extrafields;
@@ -406,7 +420,7 @@ class participants_table extends \table_sql {
         list($twhere, $tparams) = $this->get_sql_where();
 
         $total = user_get_total_participants($this->course->id, $this->currentgroup, $this->accesssince,
-            $this->roleid, $this->search, $twhere, $tparams);
+            $this->roleid, $this->enrolid, $this->status, $this->search, $twhere, $tparams);
 
         $this->pagesize($pagesize, $total);
 
@@ -416,7 +430,7 @@ class participants_table extends \table_sql {
         }
 
         $this->rawdata = user_get_participants($this->course->id, $this->currentgroup, $this->accesssince,
-            $this->roleid, $this->search, $twhere, $tparams, $sort, $this->get_page_start(),
+            $this->roleid, $this->enrolid, $this->status, $this->search, $twhere, $tparams, $sort, $this->get_page_start(),
             $this->get_page_size());
 
         // Set initial bars.
index f5edc82..229e39d 100644 (file)
@@ -30,12 +30,14 @@ require_once($CFG->dirroot.'/enrol/locallib.php');
 
 define('DEFAULT_PAGE_SIZE', 20);
 define('SHOW_ALL_PAGE_SIZE', 5000);
+define('USER_FILTER_ENROLMENT', 1);
+define('USER_FILTER_GROUP', 2);
+define('USER_FILTER_LAST_ACCESS', 3);
+define('USER_FILTER_ROLE', 4);
+define('USER_FILTER_STATUS', 5);
 
 $page         = optional_param('page', 0, PARAM_INT); // Which page to show.
 $perpage      = optional_param('perpage', DEFAULT_PAGE_SIZE, PARAM_INT); // How many per page.
-$accesssince  = optional_param('accesssince', 0, PARAM_INT); // Filter by last access. -1 = never.
-$search       = optional_param('search', '', PARAM_RAW); // Make sure it is processed with p() or s() when sending to output!
-$roleid       = optional_param('roleid', 0, PARAM_INT); // Optional roleid, 0 means all enrolled users (or all on the frontpage).
 $contextid    = optional_param('contextid', 0, PARAM_INT); // One of this or.
 $courseid     = optional_param('id', 0, PARAM_INT); // This are required.
 $selectall    = optional_param('selectall', false, PARAM_BOOL); // When rendering checkboxes against users mark them all checked.
@@ -43,9 +45,6 @@ $selectall    = optional_param('selectall', false, PARAM_BOOL); // When renderin
 $PAGE->set_url('/user/index.php', array(
         'page' => $page,
         'perpage' => $perpage,
-        'accesssince' => $accesssince,
-        'search' => $search,
-        'roleid' => $roleid,
         'contextid' => $contextid,
         'id' => $courseid));
 
@@ -78,53 +77,11 @@ if ($isfrontpage) {
     require_capability('moodle/course:viewparticipants', $context);
 }
 
-$rolenamesurl = new moodle_url("$CFG->wwwroot/user/index.php?contextid=$context->id&sifirst=&silast=");
-
-$rolenames = role_fix_names(get_profile_roles($context), $context, ROLENAME_ALIAS, true);
-if ($isfrontpage) {
-    $rolenames[0] = get_string('allsiteusers', 'role');
-} else {
-    $rolenames[0] = get_string('allparticipants');
-}
-
-// Make sure other roles may not be selected by any means.
-if (empty($rolenames[$roleid])) {
-    print_error('noparticipants');
-}
-
-// No roles to display yet?
-// frontpage course is an exception, on the front page course we should display all users.
-if (empty($rolenames) && !$isfrontpage) {
-    if (has_capability('moodle/role:assign', $context)) {
-        redirect($CFG->wwwroot.'/'.$CFG->admin.'/roles/assign.php?contextid='.$context->id);
-    } else {
-        print_error('noparticipants');
-    }
-}
-
 // Trigger events.
 user_list_view($course, $context);
 
 $bulkoperations = has_capability('moodle/course:bulkmessaging', $context);
 
-$countries = get_string_manager()->get_list_of_countries();
-
-// Manage enrolments.
-$manager = new course_enrolment_manager($PAGE, $course);
-$enrolbuttons = $manager->get_manual_enrol_buttons();
-
-// Check to see if groups are being used in this course
-// and if so, set $currentgroup to reflect the current group.
-
-$groupmode    = groups_get_course_groupmode($course);   // Groups are being used.
-$currentgroup = groups_get_course_group($course, true);
-
-if (!$currentgroup) {      // To make some other functions work better later.
-    $currentgroup  = null;
-}
-
-$isseparategroups = ($course->groupmode == SEPARATEGROUPS and !has_capability('moodle/site:accessallgroups', $context));
-
 $PAGE->set_title("$course->shortname: ".get_string('participants'));
 $PAGE->set_heading($course->fullname);
 $PAGE->set_pagetype('course-view-' . $course->format);
@@ -134,122 +91,102 @@ $PAGE->set_other_editing_capability('moodle/course:manageactivities');
 echo $OUTPUT->header();
 echo $OUTPUT->heading(get_string('participants'));
 
-$enrolrenderer = $PAGE->get_renderer('core_enrol');
-echo '<div class="pull-right">';
-foreach ($enrolbuttons as $enrolbutton) {
-    echo $enrolrenderer->render($enrolbutton);
-}
-echo '</div>';
+// Get the currently applied filters.
+$filtersapplied = optional_param_array('unified-filters', [], PARAM_TEXT);
 
-echo '<div class="userlist">';
-
-if ($isseparategroups and (!$currentgroup) ) {
-    // The user is not in the group so show message and exit.
-    echo $OUTPUT->heading(get_string("notingroup"));
-    echo $OUTPUT->footer();
-    exit;
+// Default group ID.
+$groupid = false;
+$canaccessallgroups = has_capability('moodle/site:accessallgroups', $context);
+if ($course->groupmode != NOGROUPS) {
+    if ($canaccessallgroups) {
+        // If the user can see all groups, set default to 0.
+        $groupid = 0;
+    } 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 = [];
+$roleid = 0;
+$enrolid = 0;
+$status = -1;
+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 = clean_param($filtervalue[0], PARAM_TEXT);
+    }
 
-// 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,
-        'roleid' => $roleid,
-        'id' => $course->id,
-        'perpage' => $perpage,
-        'accesssince' => $accesssince,
-        'search' => s($search)));
-
-// Setting up tags.
-if ($course->id == SITEID) {
-    $filtertype = 'site';
-} else if ($course->id && !$currentgroup) {
-    $filtertype = 'course';
-    $filterselect = $course->id;
-} else {
-    $filtertype = 'group';
-    $filterselect = $currentgroup;
+    switch ($key) {
+        case USER_FILTER_ENROLMENT:
+            $enrolid = $value;
+            break;
+        case USER_FILTER_GROUP:
+            $groupid = $value;
+            $hasgroupfilter = true;
+            break;
+        case USER_FILTER_LAST_ACCESS:
+            $lastaccess = $value;
+            break;
+        case USER_FILTER_ROLE:
+            $roleid = $value;
+            break;
+        case USER_FILTER_STATUS:
+            // We only accept active/suspended statuses.
+            if ($value == ENROL_USER_ACTIVE || $value == ENROL_USER_SUSPENDED) {
+                $status = $value;
+            }
+            break;
+        default:
+            // Search string.
+            if (!empty($key) && empty($value)) {
+                $searchkeywords[] = $key;
+            }
+            break;
+    }
 }
 
-// Print settings and things in a table across the top.
-$controlstable = new html_table();
-$controlstable->attributes['class'] = 'controls';
-$controlstable->cellspacing = 0;
-$controlstable->data[] = new html_table_row();
-
-if ($groupmenu = groups_print_course_menu($course, $baseurl->out(), true)) {
-    $controlstable->data[0]->cells[] = $groupmenu;
+// If course supports groups, but the user can't access all groups and there's no group filter set, apply a default group filter.
+if ($groupid !== false && !$canaccessallgroups && !$hasgroupfilter) {
+    $filtersapplied[] = USER_FILTER_GROUP . ':' . $groupid;
 }
 
-// Get the list of fields we have to hide.
-$hiddenfields = array();
-if (!has_capability('moodle/course:viewhiddenuserfields', $context)) {
-    $hiddenfields = array_flip(explode(',', $CFG->hiddenuserfields));
+// Manage enrolments.
+$manager = new course_enrolment_manager($PAGE, $course);
+$enrolbuttons = $manager->get_manual_enrol_buttons();
+$enrolrenderer = $PAGE->get_renderer('core_enrol');
+$enrolbuttonsout = '';
+foreach ($enrolbuttons as $enrolbutton) {
+    $enrolbuttonsout .= $enrolrenderer->render($enrolbutton);
 }
+echo html_writer::div($enrolbuttonsout, 'pull-right');
 
-// If lastaccess is in the hidden fields access do not allow filtering.
-if (isset($hiddenfields['lastaccess'])) {
-    $accesssince = 0;
-} else { // The user is allowed to filter by last access.
-    // Get minimum lastaccess for this course and display a dropbox to filter by lastaccess going back this far.
-    // We need to make it diferently for normal courses and site course.
-    if (!$isfrontpage) {
-        $minlastaccess = $DB->get_field_sql('SELECT min(timeaccess)
-                                               FROM {user_lastaccess}
-                                              WHERE courseid = ?
-                                                    AND timeaccess != 0', array($course->id));
-        $lastaccess0exists = $DB->record_exists('user_lastaccess', array('courseid' => $course->id, 'timeaccess' => 0));
-    } else {
-        $minlastaccess = $DB->get_field_sql('SELECT min(lastaccess)
-                                               FROM {user}
-                                              WHERE lastaccess != 0');
-        $lastaccess0exists = $DB->record_exists('user', array('lastaccess' => 0));
-    }
+// Render the unified filter.
+$renderer = $PAGE->get_renderer('core_user');
+echo $renderer->unified_filter($course, $context, $filtersapplied);
 
-    $now = usergetmidnight(time());
-    $timeaccess = array();
-    $baseurl->remove_params('accesssince');
-
-    // Makes sense for this to go first.
-    $timeoptions[0] = get_string('selectperiod');
-
-    // Days.
-    for ($i = 1; $i < 7; $i++) {
-        if (strtotime('-'.$i.' days', $now) >= $minlastaccess) {
-            $timeoptions[strtotime('-'.$i.' days', $now)] = get_string('numdays', 'moodle', $i);
-        }
-    }
-    // Weeks.
-    for ($i = 1; $i < 10; $i++) {
-        if (strtotime('-'.$i.' weeks', $now) >= $minlastaccess) {
-            $timeoptions[strtotime('-'.$i.' weeks', $now)] = get_string('numweeks', 'moodle', $i);
-        }
-    }
-    // Months.
-    for ($i = 2; $i < 12; $i++) {
-        if (strtotime('-'.$i.' months', $now) >= $minlastaccess) {
-            $timeoptions[strtotime('-'.$i.' months', $now)] = get_string('nummonths', 'moodle', $i);
-        }
-    }
-    // Try a year.
-    if (strtotime('-1 year', $now) >= $minlastaccess) {
-        $timeoptions[strtotime('-1 year', $now)] = get_string('lastyear');
-    }
-
-    if (!empty($lastaccess0exists)) {
-        $timeoptions[-1] = get_string('never');
-    }
-
-    if (count($timeoptions) > 1) {
-        $select = new single_select($baseurl, 'accesssince', $timeoptions, $accesssince, null, 'timeoptions');
-        $select->set_label(get_string('usersnoaccesssince'));
-        $controlstable->data[0]->cells[] = $OUTPUT->render($select);
-    }
-}
+echo '<div class="userlist">';
 
-echo html_writer::table($controlstable);
+// 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));
 
-$participanttable = new \core_user\participants_table($course->id, $currentgroup, $accesssince, $roleid, $search,
-    $bulkoperations, $selectall);
+$participanttable = new \core_user\participants_table($course->id, $groupid, $lastaccess, $roleid, $enrolid, $status,
+    $searchkeywords, $bulkoperations, $selectall);
 $participanttable->define_baseurl($baseurl);
 
 // Do this so we can get the total number of rows.
@@ -258,53 +195,6 @@ $participanttable->out($perpage, true);
 $participanttablehtml = ob_get_contents();
 ob_end_clean();
 
-// If there are multiple Roles in the course, then show a drop down menu for switching.
-if (count($rolenames) > 1) {
-    echo '<div class="rolesform">';
-    echo $OUTPUT->single_select($rolenamesurl, 'roleid', $rolenames, $roleid, null,
-        'rolesform', array('label' => get_string('currentrole', 'role')));
-    echo '</div>';
-
-} else if (count($rolenames) == 1) {
-    // When all users with the same role - print its name.
-    echo '<div class="rolesform">';
-    echo get_string('role').get_string('labelsep', 'langconfig');
-    $rolename = reset($rolenames);
-    echo $rolename;
-    echo '</div>';
-}
-
-if ($roleid > 0) {
-    $a = new stdClass();
-    $a->number = $participanttable->totalrows;
-    $a->role = $rolenames[$roleid];
-    $heading = format_string(get_string('xuserswiththerole', 'role', $a));
-
-    if ($currentgroup) {
-        if ($group = groups_get_group($currentgroup)) {
-            $a->group = $group->name;
-            $heading .= ' ' . format_string(get_string('ingroup', 'role', $a));
-        }
-    }
-
-    if ($accesssince && !empty($timeoptions[$accesssince])) {
-        $a->timeperiod = $timeoptions[$accesssince];
-        $heading .= ' ' . format_string(get_string('inactiveformorethan', 'role', $a));
-    }
-
-    $heading .= ": $a->number";
-
-    echo $OUTPUT->heading($heading, 3);
-} else {
-    if ($course->id == SITEID and $roleid < 0) {
-        $strallparticipants = get_string('allsiteusers', 'role');
-    } else {
-        $strallparticipants = get_string('allparticipants');
-    }
-
-    echo $OUTPUT->heading($strallparticipants.get_string('labelsep', 'langconfig') . $participanttable->totalrows, 3);
-}
-
 if ($bulkoperations) {
     echo '<form action="action_redir.php" method="post" id="participantsform">';
     echo '<div>';
@@ -378,13 +268,6 @@ if ($bulkoperations) {
     $PAGE->requires->js_init_call('M.core_user.init_participation', null, false, $module);
 }
 
-// Show a search box if all participants don't fit on a single screen.
-if ($participanttable->get_page_size() < $participanttable->totalrows) {
-    echo '<form action="index.php" class="searchform"><div><input type="hidden" name="id" value="'.$course->id.'" />';
-    echo '<label for="search">' . get_string('search', 'search') . ' </label>';
-    echo '<input type="text" id="search" name="search" value="'.s($search).'" />&nbsp;<input type="submit" value="'.get_string('search').'" /></div></form>'."\n";
-}
-
 echo '</div>';  // Userlist.
 
 $enrolrenderer = $PAGE->get_renderer('core_enrol');
index 1188d09..83044f2 100644 (file)
@@ -1220,13 +1220,15 @@ function user_get_tagged_users($tag, $exclusivemode = false, $fromctx = 0, $ctx
  * @param int $groupid The groupid, 0 means all groups
  * @param int $accesssince The time since last access, 0 means any time
  * @param int $roleid The role id, 0 means all roles
- * @param string $search The search that was performed, empty means perform no search
+ * @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, $search = '', $additionalwhere = '',
-        $additionalparams = array()) {
+function user_get_participants_sql($courseid, $groupid = 0, $accesssince = 0, $roleid = 0, $enrolid = 0, $statusid = -1,
+                                   $search = '', $additionalwhere = '', $additionalparams = array()) {
     global $DB;
 
     // Get the context.
@@ -1234,9 +1236,26 @@ function user_get_participants_sql($courseid, $groupid = 0, $accesssince = 0, $r
 
     $isfrontpage = ($courseid == SITEID);
 
-    // Show active users only if the user doesn't have the 'moodle/course:enrolreview' capability.
-    $onlyactive = !has_capability('moodle/course:enrolreview', $context);
-    list($esql, $params) = get_enrolled_sql($context, null, $groupid, $onlyactive);
+    // 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)) {
+        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();
@@ -1279,13 +1298,21 @@ function user_get_participants_sql($courseid, $groupid = 0, $accesssince = 0, $r
     }
 
     if (!empty($search)) {
-        $fullname = $DB->sql_fullname('u.firstname', 'u.lastname');
-        $wheres[] = '(' . $DB->sql_like($fullname, ':search1', false, false) .
-            ' OR ' . $DB->sql_like('email', ':search2', false, false) .
-            ' OR ' . $DB->sql_like('idnumber', ':search3', false, false) . ') ';
-        $params['search1'] = "%$search%";
-        $params['search2'] = "%$search%";
-        $params['search3'] = "%$search%";
+        if (!is_array($search)) {
+            $search = [$search];
+        }
+        foreach ($search as $index => $keyword) {
+            $searchkey1 = 'search' . $index . '1';
+            $searchkey2 = 'search' . $index . '2';
+            $searchkey3 = 'search' . $index . '3';
+            $fullname = $DB->sql_fullname('u.firstname', 'u.lastname');
+            $wheres[] = '(' . $DB->sql_like($fullname, ':' . $searchkey1, false, false) .
+                ' OR ' . $DB->sql_like('email', ':' . $searchkey2, false, false) .
+                ' OR ' . $DB->sql_like('idnumber', ':' . $searchkey3, false, false) . ') ';
+            $params[$searchkey1] = "%$keyword%";
+            $params[$searchkey2] = "%$keyword%";
+            $params[$searchkey3] = "%$keyword%";
+        }
     }
 
     if (!empty($additionalwhere)) {
@@ -1310,17 +1337,19 @@ function user_get_participants_sql($courseid, $groupid = 0, $accesssince = 0, $r
  * @param int $groupid The groupid, 0 means all groups
  * @param int $accesssince The time since last access, 0 means any time
  * @param int $roleid The role id, 0 means all roles
- * @param string $search The search that was performed, empty means perform no search
+ * @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, $search = '', $additionalwhere = '',
-        $additionalparams = array()) {
+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,
-        $search, $additionalwhere, $additionalparams);
+    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);
 }
@@ -1332,6 +1361,8 @@ function user_get_total_participants($courseid, $groupid = 0, $accesssince = 0,
  * @param int $groupid The group id
  * @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
@@ -1340,12 +1371,12 @@ function user_get_total_participants($courseid, $groupid = 0, $accesssince = 0,
  * @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, $search, $additionalwhere = '',
-        $additionalparams = array(), $sort = '', $limitfrom = 0, $limitnum = 0) {
+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,
-        $search, $additionalwhere, $additionalparams);
+    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);
 }
index 2f384fc..23f1312 100644 (file)
@@ -170,6 +170,154 @@ class core_user_renderer extends plugin_renderer_base {
         return $this->output->render_from_template('core_tag/tagfeed', $items);
     }
 
+    /**
+     * Renders the unified filter element for the course participants page.
+     *
+     * @param stdClass $course The course object.
+     * @param context $context The context object.
+     * @param array $filtersapplied Array of currently applied filters.
+     * @return bool|string
+     */
+    public function unified_filter($course, $context, $filtersapplied) {
+        global $CFG, $DB, $USER;
+
+        $filteroptions = [];
+        $isfrontpage = ($course->id == SITEID);
+
+        // Get the list of fields we have to hide.
+        $hiddenfields = array();
+        if (!has_capability('moodle/course:viewhiddenuserfields', $context)) {
+            $hiddenfields = array_flip(explode(',', $CFG->hiddenuserfields));
+        }
+        $haslastaccess = !isset($hiddenfields['lastaccess']);
+        // Filter options for last access.
+        if ($haslastaccess) {
+            // Get minimum lastaccess for this course and display a dropbox to filter by lastaccess going back this far.
+            // We need to make it diferently for normal courses and site course.
+            if (!$isfrontpage) {
+                $params = ['courseid' => $course->id, 'timeaccess' => 0];
+                $select = 'courseid = :courseid AND timeaccess != :timeaccess';
+                $minlastaccess = $DB->get_field_select('user_lastaccess', 'MIN(timeaccess)', $select, $params);
+                $lastaccess0exists = $DB->record_exists('user_lastaccess', $params);
+            } else {
+                $params = ['lastaccess' => 0];
+                $select = 'lastaccess != :lastaccess';
+                $minlastaccess = $DB->get_field_select('user', 'MIN(lastaccess)', $select, $params);
+                $lastaccess0exists = $DB->record_exists('user', $params);
+            }
+            $now = usergetmidnight(time());
+            $timeoptions = [];
+            $criteria = get_string('usersnoaccesssince');
+
+            // Days.
+            for ($i = 1; $i < 7; $i++) {
+                $timestamp = strtotime('-' . $i . ' days', $now);
+                if ($timestamp >= $minlastaccess) {
+                    $value = get_string('numdays', 'moodle', $i);
+                    $timeoptions += $this->format_filter_option(USER_FILTER_LAST_ACCESS, $criteria, $timestamp, $value);
+                }
+            }
+            // Weeks.
+            for ($i = 1; $i < 10; $i++) {
+                $timestamp = strtotime('-'.$i.' weeks', $now);
+                if ($timestamp >= $minlastaccess) {
+                    $value = get_string('numweeks', 'moodle', $i);
+                    $timeoptions += $this->format_filter_option(USER_FILTER_LAST_ACCESS, $criteria, $timestamp, $value);
+                }
+            }
+            // Months.
+            for ($i = 2; $i < 12; $i++) {
+                $timestamp = strtotime('-'.$i.' months', $now);
+                if ($timestamp >= $minlastaccess) {
+                    $value = get_string('nummonths', 'moodle', $i);
+                    $timeoptions += $this->format_filter_option(USER_FILTER_LAST_ACCESS, $criteria, $timestamp, $value);
+                }
+            }
+            // Try a year.
+            $timestamp = strtotime('-'.$i.' year', $now);
+            if ($timestamp >= $minlastaccess) {
+                $value = get_string('lastyear', 'moodle');
+                $timeoptions += $this->format_filter_option(USER_FILTER_LAST_ACCESS, $criteria, $timestamp, $value);
+            }
+            if (!empty($lastaccess0exists)) {
+                $value = get_string('never', 'moodle');
+                $timeoptions += $this->format_filter_option(USER_FILTER_LAST_ACCESS, $criteria, $timestamp, $value);
+            }
+            if (count($timeoptions) > 1) {
+                $filteroptions += $timeoptions;
+            }
+        }
+
+        require_once($CFG->dirroot . '/enrol/locallib.php');
+        $manager = new course_enrolment_manager($this->page, $course);
+
+        $canreviewenrol = has_capability('moodle/course:enrolreview', $context);
+
+        // Filter options for enrolment methods.
+        if ($canreviewenrol && $enrolmentmethods = $manager->get_enrolment_instance_names(true)) {
+            $criteria = get_string('enrolmentinstances', 'enrol');
+            $enroloptions = [];
+            foreach ($enrolmentmethods as $id => $enrolname) {
+                $enroloptions += $this->format_filter_option(USER_FILTER_ENROLMENT, $criteria, $id, $enrolname);
+            }
+            $filteroptions += $enroloptions;
+        }
+
+        // Filter options for groups, if available.
+        if ($course->groupmode != NOGROUPS) {
+            if (has_capability('moodle/site:accessallgroups', $context)) {
+                // List all groups if the user can access all groups.
+                $groups = $manager->get_all_groups();
+            } else {
+                // Otherwise, just list the groups the user belongs to.
+                $groups = groups_get_all_groups($course->id, $USER->id);
+            }
+            $criteria = get_string('group');
+            $groupoptions = [];
+            foreach ($groups as $id => $group) {
+                $groupoptions += $this->format_filter_option(USER_FILTER_GROUP, $criteria, $id, $group->name);
+            }
+            $filteroptions += $groupoptions;
+        }
+
+        // Filter options for role.
+        $roles = role_fix_names(get_profile_roles($context), $context, ROLENAME_ALIAS, true);
+        $criteria = get_string('role');
+        $roleoptions = [];
+        foreach ($roles as $id => $role) {
+            $roleoptions += $this->format_filter_option(USER_FILTER_ROLE, $criteria, $id, $role);
+        }
+        $filteroptions += $roleoptions;
+
+        // Filter options for status.
+        if ($canreviewenrol) {
+            $criteria = get_string('status');
+            // Add statuses.
+            $filteroptions += $this->format_filter_option(USER_FILTER_STATUS, $criteria, ENROL_USER_ACTIVE, get_string('active'));
+            $filteroptions += $this->format_filter_option(USER_FILTER_STATUS, $criteria, ENROL_USER_SUSPENDED,
+                get_string('inactive'));
+        }
+
+        $indexpage = new \core_user\output\unified_filter($filteroptions, $filtersapplied);
+        $context = $indexpage->export_for_template($this->output);
+
+        return $this->output->render_from_template('core_user/unified_filter', $context);
+    }
+
+    /**
+     * Returns a formatted filter option.
+     *
+     * @param int $filtertype The filter type (e.g. status, role, group, enrolment, last access).
+     * @param string $criteria The string label of the filter type.
+     * @param int $value The value for the filter option.
+     * @param string $label The string representation of the filter option's value.
+     * @return array The formatted option with the ['filtertype:value' => 'criteria: label'] format.
+     */
+    protected function format_filter_option($filtertype, $criteria, $value, $label) {
+        $optionlabel = get_string('filteroption', 'moodle', (object)['criteria' => $criteria, 'value' => $label]);
+        $optionvalue = "$filtertype:$value";
+        return [$optionvalue => $optionlabel];
+    }
 }
 
 /**
diff --git a/user/templates/unified_filter.mustache b/user/templates/unified_filter.mustache
new file mode 100644 (file)
index 0000000..81663ae
--- /dev/null
@@ -0,0 +1,67 @@
+{{!
+    This file is part of Moodle - http://moodle.org/
+
+    Moodle is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    Moodle is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
+}}
+{{!
+    @template core_user/unified_filter
+
+    Template for the unified filter element.
+
+    Context variables required for this template:
+    * action string - The action URL for the form.
+    * filteroptions - Array of filter options.
+      * value string - The option value.
+      * label string - The option label.
+      * selected boolean - Whether the option is selected
+
+    Example context (json):
+    {
+        "action": "/user/index.php",
+        "filteroptions": [
+            {
+                "value": "1",
+                "label": "Option 1"
+            },
+            {
+                "value": "2",
+                "label": "Option 2",
+                "selected": true
+            },
+            {
+                "value": "3",
+                "label": "Option 3",
+                "selected": true
+            },
+            {
+                "value": "4",
+                "label": "Option 4"
+            }
+        ]
+    }
+}}
+<form method="post" action="{{action}}" class="form-inline m-b-2" role="search">
+    <label for="unified-filters" class="sr-only">{{#str}}filters{{/str}}</label>
+    <select name="unified-filters[]" id="unified-filters" multiple="multiple" data-originaloptionsjson="{{originaloptionsjson}}">
+        {{#filteroptions}}
+            <option value="{{value}}" {{#selected}}selected="selected"{{/selected}}>{{{label}}}</option>
+        {{/filteroptions}}
+    </select>
+    <input type="submit" class="btn btn-primary" id="user-filter-button" value={{#quote}}{{#str}}filter{{/str}}{{/quote}}>
+</form>
+{{#js}}
+require(['core_user/unified_filter'], function(Filter) {
+    Filter.init();
+});
+{{/js}}
diff --git a/user/tests/behat/filter_participants.feature b/user/tests/behat/filter_participants.feature
new file mode 100644 (file)
index 0000000..7540a66
--- /dev/null
@@ -0,0 +1,121 @@
+@core @core_user
+Feature: Course participants can be filtered
+  In order to filter the list of course participants
+  As a user
+  I need to visit the course participants page and apply the appropriate filters
+
+  Background:
+    Given the following "courses" exist:
+      | fullname | shortname | groupmode |
+      | Course 1 | C1        |     1     |
+      | Course 2 | C2        |     0     |
+    And the following "users" exist:
+      | username | firstname | lastname | email                |
+      | student1 | Student   | 1        | student1@example.com |
+      | student2 | Student   | 2        | student2@example.com |
+      | student3 | Student   | 3        | student3@example.com |
+      | student4 | Student   | 4        | student4@example.com |
+      | teacher1 | Teacher   | 1        | teacher1@example.com |
+    And the following "course enrolments" exist:
+      | user     | course | role           | status | timeend       |
+      | student1 | C1     | student        |    0   |               |
+      | student2 | C1     | student        |    1   |               |
+      | student3 | C1     | student        |    0   |               |
+      | student4 | C1     | student        |    0   | ##yesterday## |
+      | student1 | C2     | student        |    0   |               |
+      | student2 | C2     | student        |    0   |               |
+      | student3 | C2     | student        |    0   |               |
+      | teacher1 | C1     | editingteacher |    0   |               |
+      | teacher1 | C2     | editingteacher |    0   |               |
+    And the following "groups" exist:
+      | name    | course | idnumber |
+      | Group 1 | C1     | G1       |
+      | Group 2 | C1     | G2       |
+    And the following "group members" exist:
+      | user     | group |
+      | student2 | G1    |
+      | student2 | G2    |
+      | student3 | G2    |
+
+  @javascript
+  Scenario: No filters applied
+    Given I log in as "teacher1"
+    And I am on "Course 1" course homepage
+    And I navigate to course participants
+    When I press "Filter"
+    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"
+
+  @javascript
+  Scenario Outline: Filter users for a course
+    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 press "Filter"
+    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: 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    |
+
+  @javascript
+  Scenario: Multiple filters applied
+    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 press "Filter"
+    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"
+
+  @javascript
+  Scenario: Filter by keyword
+    Given I log in as "teacher1"
+    And I am on "Course 1" course homepage
+    And I navigate to course participants
+    When I set the field "Filters" to "student"
+    And I press key "13" in the field "Filters"
+    And I press "Filter"
+    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: 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"
+
+  @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"
index 54b4db0..0683cba 100644 (file)
@@ -748,7 +748,8 @@ class core_userliblib_testcase extends advanced_testcase {
 
         // 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 also accessed the course in the last day.
-        $count = user_get_total_participants($course->id, $group->id, $accesssince + 1, $roleids['student'], 'searchforthis');
+        $count = user_get_total_participants($course->id, $group->id, $accesssince + 1, $roleids['student'], 0, -1,
+            'searchforthis');
 
         $this->assertEquals(1, $count);
     }
@@ -778,7 +779,7 @@ class core_userliblib_testcase extends advanced_testcase {
 
         // 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 also accessed the site in the last day.
-        $count = user_get_total_participants(SITEID, $group->id, $accesssince + 1, 0, 'searchforthis');
+        $count = user_get_total_participants(SITEID, $group->id, $accesssince + 1, 0, 0, -1, 'searchforthis');
 
         $this->assertEquals(2, $count);
     }
@@ -836,7 +837,7 @@ class core_userliblib_testcase extends advanced_testcase {
 
         // 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 also accessed the course in the last day.
-        $userset = user_get_participants($course->id, $group->id, $accesssince + 1, $roleids['student'], 'searchforthis');
+        $userset = user_get_participants($course->id, $group->id, $accesssince + 1, $roleids['student'], 0, -1, 'searchforthis');
 
         $this->assertEquals(1, sizeof($userset));
         $this->assertEquals($student1->id, $userset->current()->id);
@@ -867,7 +868,8 @@ class core_userliblib_testcase extends advanced_testcase {
 
         // 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 also accessed the site in the last day.
-        $userset = user_get_participants(SITEID, $group->id, $accesssince + 1, 0, 'searchforthis', '', array(), 'ORDER BY id ASC');
+        $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();