Merge branch 'MDL-59366-master' of git://github.com/junpataleta/moodle

This commit is contained in:
Dan Poltawski 2017-08-01 10:36:14 +01:00
commit e3a2d7a473
18 changed files with 842 additions and 258 deletions

View File

@ -521,13 +521,12 @@ class course_enrolment_manager {
/**
* Returns all of the enrolment instances for this course.
*
* NOTE: since 2.4 it includes instances of disabled plugins too.
*
* @param bool $onlyenabled Whether to return data from enabled enrolment instance names only.
* @return array
*/
public function get_enrolment_instances() {
public function get_enrolment_instances($onlyenabled = false) {
if ($this->_instances === null) {
$this->_instances = enrol_get_instances($this->course->id, false);
$this->_instances = enrol_get_instances($this->course->id, $onlyenabled);
}
return $this->_instances;
}
@ -535,13 +534,12 @@ class course_enrolment_manager {
/**
* Returns the names for all of the enrolment instances for this course.
*
* NOTE: since 2.4 it includes instances of disabled plugins too.
*
* @param bool $onlyenabled Whether to return data from enabled enrolment instance names only.
* @return array
*/
public function get_enrolment_instance_names() {
public function get_enrolment_instance_names($onlyenabled = false) {
if ($this->_inames === null) {
$instances = $this->get_enrolment_instances();
$instances = $this->get_enrolment_instances($onlyenabled);
$plugins = $this->get_enrolment_plugins(false);
foreach ($instances as $key=>$instance) {
if (!isset($plugins[$instance->enrol])) {

View File

@ -983,6 +983,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';
@ -1344,6 +1347,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';
@ -2003,6 +2007,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';

File diff suppressed because one or more lines are too long

View File

@ -451,7 +451,7 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
// Notifiy that the selection changed.
notifyChange(originalSelect);
if (!options.multiple) {
if (options.closeSuggestionsOnSelect) {
// Clear the input element.
inputElement.val('');
// Close the list of suggestions.
@ -718,9 +718,11 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
* @param {Boolean} caseSensitive - If search has to be made case sensitive.
* @param {Boolean} showSuggestions - If suggestions should be shown
* @param {String} noSelectionString - Text to display when there is no selection
* @param {Boolean} closeSuggestionsOnSelect - Whether to close the suggestions immediately after making a selection.
* @return {Promise}
*/
enhance: function(selector, tags, ajax, placeholder, caseSensitive, showSuggestions, noSelectionString) {
enhance: function(selector, tags, ajax, placeholder, caseSensitive, showSuggestions, noSelectionString,
closeSuggestionsOnSelect) {
// Set some default values.
var options = {
selector: selector,
@ -769,6 +771,13 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
};
options.multiple = originalSelect.attr('multiple');
if (typeof closeSuggestionsOnSelect !== "undefined") {
options.closeSuggestionsOnSelect = closeSuggestionsOnSelect;
} else {
// If not specified, this will close suggestions by default for single-select elements only.
options.closeSuggestionsOnSelect = !options.multiple;
}
var originalLabel = $('[for=' + state.selectId + ']');
// Create the new markup and insert it after the select.
var suggestions = [];

View File

@ -1209,15 +1209,16 @@ function is_enrolled(context $context, $user = null, $withcapability = '', $only
* @param int $group optional, 0 indicates no current group, otherwise the group id
* @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
* @param bool $onlysuspended inverse of onlyactive, consider only suspended enrolments
* @param int $enrolid The enrolment ID. If not 0, only users enrolled using this enrolment method will be returned.
* @return \core\dml\sql_join Contains joins, wheres, params
*/
function get_enrolled_with_capabilities_join(context $context, $prefix = '', $capability = '', $group = 0,
$onlyactive = false, $onlysuspended = false) {
$onlyactive = false, $onlysuspended = false, $enrolid = 0) {
$uid = $prefix . 'u.id';
$joins = array();
$wheres = array();
$enrolledjoin = get_enrolled_join($context, $uid, $onlyactive, $onlysuspended);
$enrolledjoin = get_enrolled_join($context, $uid, $onlyactive, $onlysuspended, $enrolid);
$joins[] = $enrolledjoin->joins;
$wheres[] = $enrolledjoin->wheres;
$params = $enrolledjoin->params;
@ -1253,9 +1254,11 @@ function get_enrolled_with_capabilities_join(context $context, $prefix = '', $ca
* @param int $groupid 0 means ignore groups, any other value limits the result by group id
* @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
* @param bool $onlysuspended inverse of onlyactive, consider only suspended enrolments
* @param int $enrolid The enrolment ID. If not 0, only users enrolled using this enrolment method will be returned.
* @return array list($sql, $params)
*/
function get_enrolled_sql(context $context, $withcapability = '', $groupid = 0, $onlyactive = false, $onlysuspended = false) {
function get_enrolled_sql(context $context, $withcapability = '', $groupid = 0, $onlyactive = false, $onlysuspended = false,
$enrolid = 0) {
// Use unique prefix just in case somebody makes some SQL magic with the result.
static $i = 0;
@ -1263,7 +1266,7 @@ function get_enrolled_sql(context $context, $withcapability = '', $groupid = 0,
$prefix = 'eu' . $i . '_';
$capjoin = get_enrolled_with_capabilities_join(
$context, $prefix, $withcapability, $groupid, $onlyactive, $onlysuspended);
$context, $prefix, $withcapability, $groupid, $onlyactive, $onlysuspended, $enrolid);
$sql = "SELECT DISTINCT {$prefix}u.id
FROM {user} {$prefix}u
@ -1285,9 +1288,10 @@ function get_enrolled_sql(context $context, $withcapability = '', $groupid = 0,
* @param string $useridcolumn User id column used the calling query, e.g. u.id
* @param bool $onlyactive consider only active enrolments in enabled plugins and time restrictions
* @param bool $onlysuspended inverse of onlyactive, consider only suspended enrolments
* @param int $enrolid The enrolment ID. If not 0, only users enrolled using this enrolment method will be returned.
* @return \core\dml\sql_join Contains joins, wheres, params
*/
function get_enrolled_join(context $context, $useridcolumn, $onlyactive = false, $onlysuspended = false) {
function get_enrolled_join(context $context, $useridcolumn, $onlyactive = false, $onlysuspended = false, $enrolid = 0) {
// Use unique prefix just in case somebody makes some SQL magic with the result.
static $i = 0;
$i++;
@ -1315,7 +1319,18 @@ function get_enrolled_join(context $context, $useridcolumn, $onlyactive = false,
if (!$isfrontpage) {
$where1 = "{$prefix}ue.status = :{$prefix}active AND {$prefix}e.status = :{$prefix}enabled";
$where2 = "{$prefix}ue.timestart < :{$prefix}now1 AND ({$prefix}ue.timeend = 0 OR {$prefix}ue.timeend > :{$prefix}now2)";
$ejoin = "JOIN {enrol} {$prefix}e ON ({$prefix}e.id = {$prefix}ue.enrolid AND {$prefix}e.courseid = :{$prefix}courseid)";
$enrolconditions = array(
"{$prefix}e.id = {$prefix}ue.enrolid",
"{$prefix}e.courseid = :{$prefix}courseid",
);
if ($enrolid) {
$enrolconditions[] = "{$prefix}e.id = :{$prefix}enrolid";
$params[$prefix . 'enrolid'] = $enrolid;
}
$enrolconditionssql = implode(" AND ", $enrolconditions);
$ejoin = "JOIN {enrol} {$prefix}e ON ($enrolconditionssql)";
$params[$prefix.'courseid'] = $coursecontext->instanceid;
if (!$onlysuspended) {
@ -1329,8 +1344,16 @@ function get_enrolled_join(context $context, $useridcolumn, $onlyactive = false,
// Consider multiple enrols where one is not suspended or plain role_assign.
$enrolselect = "SELECT DISTINCT {$prefix}ue.userid FROM {user_enrolments} {$prefix}ue $ejoin WHERE $where1 AND $where2";
$joins[] = "JOIN {user_enrolments} {$prefix}ue1 ON {$prefix}ue1.userid = $useridcolumn";
$joins[] = "JOIN {enrol} {$prefix}e1 ON ({$prefix}e1.id = {$prefix}ue1.enrolid
AND {$prefix}e1.courseid = :{$prefix}_e1_courseid)";
$enrolconditions = array(
"{$prefix}e1.id = {$prefix}ue1.enrolid",
"{$prefix}e1.courseid = :{$prefix}_e1_courseid",
);
if ($enrolid) {
$enrolconditions[] = "{$prefix}e1.id = :{$prefix}e1.enrolid";
$params[$prefix . 'e1.enrolid'] = $enrolid;
}
$enrolconditionssql = implode(" AND ", $enrolconditions);
$joins[] = "JOIN {enrol} {$prefix}e1 ON ($enrolconditionssql)";
$params["{$prefix}_e1_courseid"] = $coursecontext->instanceid;
$wheres[] = "$useridcolumn NOT IN ($enrolselect)";
}

View File

@ -32,6 +32,15 @@ information provided here is intended especially for developers.
These attributes enable enrol actions to be rendered via modals. If not added, clicking on the enrolment action buttons will still
redirect the user to the appropriate enrolment action page. Though optional, it is recommended to add these attributes for a
better user experience when performing enrol actions.
* New optional parameter $enrolid for the following functions:
- get_enrolled_join()
- get_enrolled_sql()
- get_enrolled_with_capabilities_join()
Setting this parameter to a non-zero value will add a condition to the query such that only users that were enrolled
with this enrolment method will be returned.
* New optional parameter 'closeSuggestionsOnSelect' for the enhance() function for form-autocomplete. Setting this to true will
close the suggestions popup immediately after an option has been selected. If not specified, it defaults to true for single-select
elements and false for multiple-select elements.
=== 3.3.1 ===

1
user/amd/build/unified_filter.min.js vendored Normal file
View File

@ -0,0 +1 @@
define(["jquery","core/form-autocomplete","core/str","core/notification"],function(a,b,c,d){var e={UNIFIED_FILTERS:"#unified-filters"},f=function(){var a=[{key:"userfilterplaceholder",component:"moodle"},{key:"nofiltersapplied",component:"moodle"}];c.get_strings(a).done(function(a){var c=a[0],d=a[1];b.enhance(e.UNIFIED_FILTERS,!0,"core_user/unified_filter_datasource",c,!1,!0,d,!0)}).fail(d.exception)};return{init:function(){f()}}});

View File

@ -0,0 +1 @@
define(["jquery","core/ajax","core/notification"],function(a,b,c){return{list:function(b,c){var d=[],e=a(b),f=a(b).data("originaloptionsjson"),g=e.val(),h=[];a.each(g,function(a,b){var c=b.split(":",2);if(2===c.length){var d=c[0];h.push(d)}}),a.each(f,function(b,e){if(""!==a.trim(c)&&e.label.toLocaleLowerCase().indexOf(c.toLocaleLowerCase())===-1)return!0;if(a.inArray(e.value,g)>-1)return!0;var f=e.value.split(":",2);if(2===f.length){var i=f[0];if(a.inArray(i,h)>-1)return!0}return d.push(e),!0});var i=new a.Deferred;return i.resolve(d),i.promise()},processResults:function(b,c){var d=[];return a.each(c,function(a,b){d.push({value:b.value,label:b.label})}),d},transport:function(a,b,d){this.list(a,b).then(d)["catch"](c.exception)}}});

View File

@ -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();
}
};
});

View File

@ -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);
}
};
});

View File

@ -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;
}
}

View 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.

View 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);
// Get the currently applied filters.
$filtersapplied = optional_param_array('unified-filters', [], PARAM_TEXT);
// 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;
}
}
}
echo '</div>';
$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);
}
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;
}
}
// 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;
}
// 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');
// Render the unified filter.
$renderer = $PAGE->get_renderer('core_user');
echo $renderer->unified_filter($course, $context, $filtersapplied);
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;
}
// 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)));
'perpage' => $perpage));
// Setting up tags.
if ($course->id == SITEID) {
$filtertype = 'site';
} else if ($course->id && !$currentgroup) {
$filtertype = 'course';
$filterselect = $course->id;
} else {
$filtertype = 'group';
$filterselect = $currentgroup;
}
// 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;
}
// Get the list of fields we have to hide.
$hiddenfields = array();
if (!has_capability('moodle/course:viewhiddenuserfields', $context)) {
$hiddenfields = array_flip(explode(',', $CFG->hiddenuserfields));
}
// 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));
}
$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 html_writer::table($controlstable);
$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>';
@ -394,13 +284,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');

View 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);
}

View 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];
}
}
/**

View File

@ -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}}

View File

@ -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"

View 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();