mirror of
https://github.com/moodle/moodle.git
synced 2025-04-13 20:42:22 +02:00
MDL-70565 user: filter course participants by country.
This commit is contained in:
parent
fc335f5ea0
commit
be2862fe6f
2
user/amd/build/local/participantsfilter/filtertypes/country.min.js
vendored
Normal file
2
user/amd/build/local/participantsfilter/filtertypes/country.min.js
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
define ("core_user/local/participantsfilter/filtertypes/country",["exports","../filter"],function(a,b){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;b=function(a){return a&&a.__esModule?a:{default:a}}(b);function c(a){"@babel/helpers - typeof";if("function"==typeof Symbol&&"symbol"==typeof Symbol.iterator){c=function(a){return typeof a}}else{c=function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a}}return c(a)}function d(a,b){if(!(a instanceof b)){throw new TypeError("Cannot call a class as a function")}}function e(a,b){for(var c=0,d;c<b.length;c++){d=b[c];d.enumerable=d.enumerable||!1;d.configurable=!0;if("value"in d)d.writable=!0;Object.defineProperty(a,d.key,d)}}function f(a,b,c){if(b)e(a.prototype,b);if(c)e(a,c);return a}function g(a,b){if("function"!=typeof b&&null!==b){throw new TypeError("Super expression must either be null or a function")}a.prototype=Object.create(b&&b.prototype,{constructor:{value:a,writable:!0,configurable:!0}});if(b)h(a,b)}function h(a,b){h=Object.setPrototypeOf||function(a,b){a.__proto__=b;return a};return h(a,b)}function i(a){return function(){var b=m(a),c;if(l()){var d=m(this).constructor;c=Reflect.construct(b,arguments,d)}else{c=b.apply(this,arguments)}return j(this,c)}}function j(a,b){if(b&&("object"===c(b)||"function"==typeof b)){return b}return k(a)}function k(a){if(void 0===a){throw new ReferenceError("this hasn't been initialised - super() hasn't been called")}return a}function l(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{Date.prototype.toString.call(Reflect.construct(Date,[],function(){}));return!0}catch(a){return!1}}function m(a){m=Object.setPrototypeOf?Object.getPrototypeOf:function(a){return a.__proto__||Object.getPrototypeOf(a)};return m(a)}var n=function(a){g(b,a);var c=i(b);function b(){d(this,b);return c.apply(this,arguments)}f(b,[{key:"values",get:function get(){return this.rawValues}}]);return b}(b.default);a.default=n;return a.default});
|
||||
//# sourceMappingURL=country.min.js.map
|
@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../../../src/local/participantsfilter/filtertypes/country.js"],"names":["rawValues","Filter"],"mappings":"sLAwBA,uD,2vDASiB,CACT,MAAO,MAAKA,SACf,C,cATwBC,S","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Country filter\n *\n * @module core_user/local/participantsfilter/filtertypes/country\n * @package core_user\n * @copyright 2021 Paul Holden <paulh@moodle.com>\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Filter from '../filter';\n\nexport default class extends Filter {\n\n /**\n * For country the final value is an array of country code strings\n *\n * @return {Object}\n */\n get values() {\n return this.rawValues;\n }\n}\n"],"file":"country.min.js"}
|
37
user/amd/src/local/participantsfilter/filtertypes/country.js
Normal file
37
user/amd/src/local/participantsfilter/filtertypes/country.js
Normal file
@ -0,0 +1,37 @@
|
||||
// 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/>.
|
||||
|
||||
/**
|
||||
* Country filter
|
||||
*
|
||||
* @module core_user/local/participantsfilter/filtertypes/country
|
||||
* @package core_user
|
||||
* @copyright 2021 Paul Holden <paulh@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
import Filter from '../filter';
|
||||
|
||||
export default class extends Filter {
|
||||
|
||||
/**
|
||||
* For country the final value is an array of country code strings
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
get values() {
|
||||
return this.rawValues;
|
||||
}
|
||||
}
|
@ -24,6 +24,7 @@
|
||||
namespace core_user\output;
|
||||
|
||||
use context_course;
|
||||
use core\user_fields;
|
||||
use renderable;
|
||||
use renderer_base;
|
||||
use stdClass;
|
||||
@ -89,6 +90,10 @@ class participants_filter implements renderable, templatable {
|
||||
$filtertypes[] = $filtertype;
|
||||
}
|
||||
|
||||
if ($filtertype = $this->get_country_filter()) {
|
||||
$filtertypes[] = $filtertype;
|
||||
}
|
||||
|
||||
return $filtertypes;
|
||||
}
|
||||
|
||||
@ -322,6 +327,34 @@ class participants_filter implements renderable, templatable {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data for the country filter
|
||||
*
|
||||
* @return stdClass|null
|
||||
*/
|
||||
protected function get_country_filter(): ?stdClass {
|
||||
$extrauserfields = user_fields::get_identity_fields($this->context, false);
|
||||
if (array_search('country', $extrauserfields) === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$countries = get_string_manager()->get_list_of_countries(true);
|
||||
|
||||
return $this->get_filter_object(
|
||||
'country',
|
||||
get_string('country'),
|
||||
false,
|
||||
true,
|
||||
'core_user/local/participantsfilter/filtertypes/country',
|
||||
array_map(function(string $code, string $name): stdClass {
|
||||
return (object) [
|
||||
'value' => $code,
|
||||
'title' => $name,
|
||||
];
|
||||
}, array_keys($countries), array_values($countries))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data for the keywords filter.
|
||||
*
|
||||
|
@ -27,7 +27,6 @@ declare(strict_types=1);
|
||||
|
||||
namespace core_user\table;
|
||||
|
||||
use core_table\local\filter\boolean_filter;
|
||||
use core_table\local\filter\filterset;
|
||||
use core_table\local\filter\integer_filter;
|
||||
use core_table\local\filter\string_filter;
|
||||
@ -61,6 +60,7 @@ class participants_filterset extends filterset {
|
||||
* - enrolments;
|
||||
* - groups;
|
||||
* - keywords;
|
||||
* - country;
|
||||
* - roles; and
|
||||
* - status.
|
||||
*
|
||||
@ -72,6 +72,7 @@ class participants_filterset extends filterset {
|
||||
'enrolments' => integer_filter::class,
|
||||
'groups' => integer_filter::class,
|
||||
'keywords' => string_filter::class,
|
||||
'country' => string_filter::class,
|
||||
'roles' => integer_filter::class,
|
||||
'status' => integer_filter::class,
|
||||
];
|
||||
|
@ -257,6 +257,22 @@ class participants_search {
|
||||
}
|
||||
}
|
||||
|
||||
// Apply any country filtering.
|
||||
if ($this->filterset->has_filter('country')) {
|
||||
[
|
||||
'where' => $countrywhere,
|
||||
'params' => $countryparams,
|
||||
] = $this->get_country_sql();
|
||||
|
||||
if (!empty($countrywhere)) {
|
||||
$wheres[] = "($countrywhere)";
|
||||
}
|
||||
|
||||
if (!empty($countryparams)) {
|
||||
$params = array_merge($params, $countryparams);
|
||||
}
|
||||
}
|
||||
|
||||
// Apply any keyword text searches.
|
||||
if ($this->filterset->has_filter('keywords')) {
|
||||
[
|
||||
@ -877,6 +893,32 @@ class participants_search {
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare SQL where clause and associated parameters for country filtering
|
||||
*
|
||||
* @return array SQL query data in the format ['where' => '', 'params' => []].
|
||||
*/
|
||||
protected function get_country_sql(): array {
|
||||
global $DB;
|
||||
|
||||
$where = '';
|
||||
$params = [];
|
||||
|
||||
$countryfilter = $this->filterset->get_filter('country');
|
||||
if ($countrycodes = $countryfilter->get_filter_values()) {
|
||||
// If filter type is "None", then we negate the comparison.
|
||||
[$countrywhere, $params] = $DB->get_in_or_equal($countrycodes, SQL_PARAMS_NAMED, 'country',
|
||||
$countryfilter->get_join_type() !== $countryfilter::JOINTYPE_NONE);
|
||||
|
||||
$where = "(u.country {$countrywhere})";
|
||||
}
|
||||
|
||||
return [
|
||||
'where' => $where,
|
||||
'params' => $params,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare SQL where clause and associated parameters for any keyword searches being performed.
|
||||
*
|
||||
@ -975,7 +1017,7 @@ class participants_search {
|
||||
$extrasearchfields = user_fields::get_identity_fields(null);
|
||||
foreach ($extrasearchfields as $fieldindex => $extrasearchfield) {
|
||||
if (in_array($extrasearchfield, ['email', 'idnumber', 'country'])) {
|
||||
// Already covered above. Search by country not supported.
|
||||
// Already covered above.
|
||||
continue;
|
||||
}
|
||||
// The param must be short (max 32 characters) so don't include field name.
|
||||
|
@ -757,6 +757,172 @@ class participants_search_test extends advanced_testcase {
|
||||
return $finaltests;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test participant search country filter
|
||||
*
|
||||
* @param array $usersdata
|
||||
* @param array $countries
|
||||
* @param int $jointype
|
||||
* @param array $expectedusers
|
||||
*
|
||||
* @dataProvider country_provider
|
||||
*/
|
||||
public function test_country_filter(array $usersdata, array $countries, int $jointype, array $expectedusers): void {
|
||||
$this->resetAfterTest();
|
||||
|
||||
$course = $this->getDataGenerator()->create_course();
|
||||
$users = [];
|
||||
|
||||
foreach ($usersdata as $username => $country) {
|
||||
$users[$username] = $this->getDataGenerator()->create_and_enrol($course, 'student', (object) [
|
||||
'username' => $username,
|
||||
'country' => $country,
|
||||
]);
|
||||
}
|
||||
|
||||
// Add filters (courseid is required).
|
||||
$filterset = new participants_filterset();
|
||||
$filterset->add_filter(new integer_filter('courseid', null, [(int) $course->id]));
|
||||
$filterset->add_filter(new string_filter('country', $jointype, $countries));
|
||||
|
||||
// Run the search, assert count matches the number of expected users.
|
||||
$search = new participants_search($course, context_course::instance($course->id), $filterset);
|
||||
$this->assertEquals(count($expectedusers), $search->get_total_participants_count());
|
||||
|
||||
$rs = $search->get_participants();
|
||||
$this->assertInstanceOf(moodle_recordset::class, $rs);
|
||||
|
||||
// Assert that each expected user is within the participant records.
|
||||
$records = $this->convert_recordset_to_array($rs);
|
||||
foreach ($expectedusers as $expecteduser) {
|
||||
$this->assertArrayHasKey($users[$expecteduser]->id, $records);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for {@see test_country_filter}
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function country_provider(): array {
|
||||
$tests = [
|
||||
'users' => [
|
||||
'user1' => 'DE',
|
||||
'user2' => 'ES',
|
||||
'user3' => 'ES',
|
||||
'user4' => 'GB',
|
||||
],
|
||||
'expects' => [
|
||||
// Tests for jointype: ANY.
|
||||
'ANY: No filter' => (object) [
|
||||
'countries' => [],
|
||||
'jointype' => filter::JOINTYPE_ANY,
|
||||
'expectedusers' => [
|
||||
'user1',
|
||||
'user2',
|
||||
'user3',
|
||||
'user4',
|
||||
],
|
||||
],
|
||||
'ANY: Matching filters' => (object) [
|
||||
'countries' => [
|
||||
'DE',
|
||||
'GB',
|
||||
],
|
||||
'jointype' => filter::JOINTYPE_ANY,
|
||||
'expectedusers' => [
|
||||
'user1',
|
||||
'user4',
|
||||
],
|
||||
],
|
||||
'ANY: Non-matching filters' => (object) [
|
||||
'countries' => [
|
||||
'RU',
|
||||
],
|
||||
'jointype' => filter::JOINTYPE_ANY,
|
||||
'expectedusers' => [],
|
||||
],
|
||||
|
||||
// Tests for jointype: ALL.
|
||||
'ALL: No filter' => (object) [
|
||||
'countries' => [],
|
||||
'jointype' => filter::JOINTYPE_ALL,
|
||||
'expectedusers' => [
|
||||
'user1',
|
||||
'user2',
|
||||
'user3',
|
||||
'user4',
|
||||
],
|
||||
],
|
||||
'ALL: Matching filters' => (object) [
|
||||
'countries' => [
|
||||
'DE',
|
||||
'GB',
|
||||
],
|
||||
'jointype' => filter::JOINTYPE_ALL,
|
||||
'expectedusers' => [
|
||||
'user1',
|
||||
'user4',
|
||||
],
|
||||
],
|
||||
'ALL: Non-matching filters' => (object) [
|
||||
'countries' => [
|
||||
'RU',
|
||||
],
|
||||
'jointype' => filter::JOINTYPE_ALL,
|
||||
'expectedusers' => [],
|
||||
],
|
||||
|
||||
// Tests for jointype: NONE.
|
||||
'NONE: No filter' => (object) [
|
||||
'countries' => [],
|
||||
'jointype' => filter::JOINTYPE_NONE,
|
||||
'expectedusers' => [
|
||||
'user1',
|
||||
'user2',
|
||||
'user3',
|
||||
'user4',
|
||||
],
|
||||
],
|
||||
'NONE: Matching filters' => (object) [
|
||||
'countries' => [
|
||||
'DE',
|
||||
'GB',
|
||||
],
|
||||
'jointype' => filter::JOINTYPE_NONE,
|
||||
'expectedusers' => [
|
||||
'user2',
|
||||
'user3',
|
||||
],
|
||||
],
|
||||
'NONE: Non-matching filters' => (object) [
|
||||
'countries' => [
|
||||
'RU',
|
||||
],
|
||||
'jointype' => filter::JOINTYPE_NONE,
|
||||
'expectedusers' => [
|
||||
'user1',
|
||||
'user2',
|
||||
'user3',
|
||||
'user4',
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
$finaltests = [];
|
||||
foreach ($tests['expects'] as $testname => $test) {
|
||||
$finaltests[$testname] = [
|
||||
'users' => $tests['users'],
|
||||
'countries' => $test->countries,
|
||||
'jointype' => $test->jointype,
|
||||
'expectedusers' => $test->expectedusers,
|
||||
];
|
||||
}
|
||||
|
||||
return $finaltests;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the keywords filter works as expected with the provided test cases.
|
||||
*
|
||||
|
Loading…
x
Reference in New Issue
Block a user