MDL-79681 user: fix participant filter "never accessed" options.
[moodle.git] / user / classes / output / participants_filter.php
bloba43825bfc1682a463a9350fd9c11f9a0d29c9805
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17 /**
18 * Class for rendering user filters on the course participants page.
20 * @package core_user
21 * @copyright 2020 Michael Hawkins <michaelh@moodle.com>
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 namespace core_user\output;
26 use core_user\fields;
27 use renderer_base;
28 use stdClass;
30 /**
31 * Class for rendering user filters on the course participants page.
33 * @copyright 2020 Michael Hawkins <michaelh@moodle.com>
34 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36 class participants_filter extends \core\output\datafilter {
38 /**
39 * Get data for all filter types.
41 * @return array
43 protected function get_filtertypes(): array {
44 $filtertypes = [];
46 $filtertypes[] = $this->get_keyword_filter();
48 if ($filtertype = $this->get_enrolmentstatus_filter()) {
49 $filtertypes[] = $filtertype;
52 if ($filtertype = $this->get_roles_filter()) {
53 $filtertypes[] = $filtertype;
56 if ($filtertype = $this->get_enrolments_filter()) {
57 $filtertypes[] = $filtertype;
60 if ($filtertype = $this->get_groups_filter()) {
61 $filtertypes[] = $filtertype;
64 if ($filtertype = $this->get_accesssince_filter()) {
65 $filtertypes[] = $filtertype;
68 if ($filtertype = $this->get_country_filter()) {
69 $filtertypes[] = $filtertype;
72 return $filtertypes;
75 /**
76 * Get data for the enrolment status filter.
78 * @return stdClass|null
80 protected function get_enrolmentstatus_filter(): ?stdClass {
81 if (!has_capability('moodle/course:enrolreview', $this->context)) {
82 return null;
85 return $this->get_filter_object(
86 'status',
87 get_string('participationstatus', 'core_enrol'),
88 false,
89 true,
90 null,
92 (object) [
93 'value' => ENROL_USER_ACTIVE,
94 'title' => get_string('active'),
96 (object) [
97 'value' => ENROL_USER_SUSPENDED,
98 'title' => get_string('inactive'),
105 * Get data for the roles filter.
107 * @return stdClass|null
109 protected function get_roles_filter(): ?stdClass {
110 $roles = [];
111 $roles += [-1 => get_string('noroles', 'role')];
112 $roles += get_viewable_roles($this->context, null, ROLENAME_BOTH);
114 if (has_capability('moodle/role:assign', $this->context)) {
115 $roles += get_assignable_roles($this->context, ROLENAME_BOTH);
118 return $this->get_filter_object(
119 'roles',
120 get_string('roles', 'core_role'),
121 false,
122 true,
123 null,
124 array_map(function($id, $title) {
125 return (object) [
126 'value' => $id,
127 'title' => $title,
129 }, array_keys($roles), array_values($roles))
134 * Get data for the roles filter.
136 * @return stdClass|null
138 protected function get_enrolments_filter(): ?stdClass {
139 if (!has_capability('moodle/course:enrolreview', $this->context)) {
140 return null;
143 if ($this->course->id == SITEID) {
144 // No enrolment methods for the site.
145 return null;
148 $instances = enrol_get_instances($this->course->id, true);
149 $plugins = enrol_get_plugins(false);
151 return $this->get_filter_object(
152 'enrolments',
153 get_string('enrolmentinstances', 'core_enrol'),
154 false,
155 true,
156 null,
157 array_filter(array_map(function($instance) use ($plugins): ?stdClass {
158 if (!array_key_exists($instance->enrol, $plugins)) {
159 return null;
162 return (object) [
163 'value' => $instance->id,
164 'title' => $plugins[$instance->enrol]->get_instance_name($instance),
166 }, array_values($instances)))
171 * Get data for the groups filter.
173 * @return stdClass|null
175 protected function get_groups_filter(): ?stdClass {
176 global $USER;
178 // Filter options for groups, if available.
179 $seeallgroups = has_capability('moodle/site:accessallgroups', $this->context);
180 $seeallgroups = $seeallgroups || ($this->course->groupmode != SEPARATEGROUPS);
181 if ($seeallgroups) {
182 $groups = [];
183 $groups += [USERSWITHOUTGROUP => (object) [
184 'id' => USERSWITHOUTGROUP,
185 'name' => get_string('nogroup', 'group'),
187 $groups += groups_get_all_groups($this->course->id);
188 } else {
189 // Otherwise, just list the groups the user belongs to.
190 $groups = groups_get_all_groups($this->course->id, $USER->id);
193 // Return no data if no groups found (which includes if the only value is 'No group').
194 if (empty($groups) || (count($groups) === 1 && array_key_exists(-1, $groups))) {
195 return null;
198 return $this->get_filter_object(
199 'groups',
200 get_string('groups', 'core_group'),
201 false,
202 true,
203 null,
204 array_map(function($group) {
205 return (object) [
206 'value' => $group->id,
207 'title' => format_string($group->name, true, ['context' => $this->context]),
209 }, array_values($groups))
214 * Get data for the accesssince filter.
216 * @return stdClass|null
218 protected function get_accesssince_filter(): ?stdClass {
219 global $CFG, $DB;
221 $hiddenfields = [];
222 if (!has_capability('moodle/course:viewhiddenuserfields', $this->context)) {
223 $hiddenfields = array_flip(explode(',', $CFG->hiddenuserfields));
226 if (array_key_exists('lastaccess', $hiddenfields)) {
227 return null;
230 // Get minimum lastaccess for this course and display a dropbox to filter by lastaccess going back this far.
231 // We need to make it diferently for normal courses and site course.
232 if (!($this->course->id == SITEID)) {
233 // Regular course.
234 $params = [
235 'courseid' => $this->course->id,
236 'timeaccess' => 0,
238 $select = 'courseid = :courseid AND timeaccess != :timeaccess';
239 $minlastaccess = $DB->get_field_select('user_lastaccess', 'MIN(timeaccess)', $select, $params);
241 // Determine enrolled users, who do not have accompanying lastaccess to the course.
242 [$enrolledsql, $enrolledparams] = get_enrolled_sql($this->context);
244 $sql = "SELECT 'x'
245 FROM {user} u
246 JOIN ({$enrolledsql}) je ON je.id = u.id
247 LEFT JOIN {user_lastaccess} ula ON ula.userid = je.id AND ula.courseid = :courseid
248 WHERE COALESCE(ula.timeaccess, 0) = :timeaccess";
250 $lastaccess0exists = $DB->record_exists_sql($sql, array_merge($params, $enrolledparams));
251 } else {
252 // Front page.
253 $params = ['lastaccess' => 0];
254 $select = 'lastaccess != :lastaccess';
255 $minlastaccess = $DB->get_field_select('user', 'MIN(lastaccess)', $select, $params);
256 $lastaccess0exists = $DB->record_exists('user', $params);
259 $now = usergetmidnight(time());
261 $getoptions = function(int $count, string $singletype, string $type) use ($now, $minlastaccess): array {
262 $values = [];
263 for ($i = 1; $i <= $count; $i++) {
264 $timestamp = strtotime("-{$i} {$type}", $now);
265 if ($timestamp < $minlastaccess) {
266 break;
269 if ($i === 1) {
270 $title = get_string("num{$singletype}", 'moodle', $i);
271 } else {
272 $title = get_string("num{$type}", 'moodle', $i);
275 $values[] = [
276 'value' => $timestamp,
277 'title' => $title,
281 return $values;
284 $values = array_merge(
285 $getoptions(6, 'day', 'days'),
286 $getoptions(10, 'week', 'weeks'),
287 $getoptions(11, 'month', 'months'),
288 $getoptions(1, 'year', 'years')
291 if ($lastaccess0exists) {
292 $values[] = [
293 'value' => -1,
294 'title' => get_string('never', 'moodle'),
298 if (count($values) <= 1) {
299 // Nothing to show.
300 return null;
303 return $this->get_filter_object(
304 'accesssince',
305 get_string('usersnoaccesssince'),
306 false,
307 false,
308 null,
309 $values
314 * Get data for the country filter
316 * @return stdClass|null
318 protected function get_country_filter(): ?stdClass {
319 $extrauserfields = fields::get_identity_fields($this->context, false);
320 if (array_search('country', $extrauserfields) === false) {
321 return null;
324 $countries = get_string_manager()->get_list_of_countries(true);
326 return $this->get_filter_object(
327 'country',
328 get_string('country'),
329 false,
330 true,
331 'core/datafilter/filtertypes/country',
332 array_map(function(string $code, string $name): stdClass {
333 return (object) [
334 'value' => $code,
335 'title' => $name,
337 }, array_keys($countries), array_values($countries))
342 * Get data for the keywords filter.
344 * @return stdClass|null
346 protected function get_keyword_filter(): ?stdClass {
347 return $this->get_filter_object(
348 'keywords',
349 get_string('filterbykeyword', 'core_user'),
350 true,
351 true,
352 'core/datafilter/filtertypes/keyword',
354 true
359 * Export the renderer data in a mustache template friendly format.
361 * @param renderer_base $output Unused.
362 * @return stdClass Data in a format compatible with a mustache template.
364 public function export_for_template(renderer_base $output): stdClass {
365 return (object) [
366 'tableregionid' => $this->tableregionid,
367 'courseid' => $this->context->instanceid,
368 'filtertypes' => $this->get_filtertypes(),
369 'rownumber' => 1,
372 return $data;