MDL-63387 user: show both role names on course participants page.
[moodle.git] / user / classes / table / participants.php
CommitLineData
bc47b706
MN
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.
13//
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/>.
16
17/**
18 * Contains the class used for the displaying the participants table.
19 *
20 * @package core_user
21 * @copyright 2017 Mark Nelson <markn@moodle.com>
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
bc6f5f23 24declare(strict_types=1);
bc47b706 25
bc6f5f23 26namespace core_user\table;
bc47b706 27
8146335f 28use DateTime;
fb792967 29use context;
8146335f
SL
30use core_table\dynamic as dynamic_table;
31use core_table\local\filter\filterset;
90ffcfa8 32use core_user\output\status_field;
86e71acc 33use core_user\table\participants_search;
8146335f 34use moodle_url;
fb792967 35
bc47b706
MN
36defined('MOODLE_INTERNAL') || die;
37
38global $CFG;
39
40require_once($CFG->libdir . '/tablelib.php');
41require_once($CFG->dirroot . '/user/lib.php');
42
43/**
44 * Class for the displaying the participants table.
45 *
46 * @package core_user
47 * @copyright 2017 Mark Nelson <markn@moodle.com>
48 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
49 */
bc6f5f23 50class participants extends \table_sql implements dynamic_table {
bc47b706
MN
51
52 /**
53 * @var int $courseid The course id
54 */
55 protected $courseid;
56
bc47b706
MN
57 /**
58 * @var string[] The list of countries.
59 */
60 protected $countries;
61
2fa35b8d 62 /**
f3ecea3a 63 * @var \stdClass[] The list of groups with membership info for the course.
2fa35b8d
DW
64 */
65 protected $groups;
66
bc47b706
MN
67 /**
68 * @var string[] Extra fields to display.
69 */
70 protected $extrafields;
71
2fa35b8d 72 /**
fb792967 73 * @var \stdClass $course The course details.
2fa35b8d
DW
74 */
75 protected $course;
76
77 /**
fb792967 78 * @var context $context The course context.
2fa35b8d
DW
79 */
80 protected $context;
81
73d0d562
DW
82 /**
83 * @var \stdClass[] List of roles indexed by roleid.
84 */
85 protected $allroles;
86
0ae28fad
DW
87 /**
88 * @var \stdClass[] List of roles indexed by roleid.
89 */
90 protected $allroleassignments;
91
73d0d562
DW
92 /**
93 * @var \stdClass[] Assignable roles in this course.
94 */
95 protected $assignableroles;
96
9df2fdec
DW
97 /**
98 * @var \stdClass[] Profile roles in this course.
99 */
100 protected $profileroles;
101
86e71acc
MH
102 /**
103 * @var filterset Filterset describing which participants to include.
104 */
105 protected $filterset;
106
a63cd3e2
AH
107 /** @var \stdClass[] $viewableroles */
108 private $viewableroles;
109
1592c3c4
SL
110 /** @var moodle_url $baseurl The base URL for the report. */
111 public $baseurl;
112
80d13bfe
JP
113 /**
114 * Render the participants table.
115 *
116 * @param int $pagesize Size of page for paginated displayed table.
117 * @param bool $useinitialsbar Whether to use the initials bar which will only be used if there is a fullname column defined.
118 * @param string $downloadhelpbutton
119 */
120 public function out($pagesize, $useinitialsbar, $downloadhelpbutton = '') {
1592c3c4
SL
121 global $CFG, $OUTPUT, $PAGE;
122
123 // Define the headers and columns.
124 $headers = [];
125 $columns = [];
126
127 $bulkoperations = has_capability('moodle/course:bulkmessaging', $this->context);
128 if ($bulkoperations) {
129 $mastercheckbox = new \core\output\checkbox_toggleall('participants-table', true, [
130 'id' => 'select-all-participants',
131 'name' => 'select-all-participants',
07c91064 132 'label' => get_string('selectall'),
1592c3c4
SL
133 'labelclasses' => 'sr-only',
134 'classes' => 'm-1',
07c91064 135 'checked' => false,
1592c3c4
SL
136 ]);
137 $headers[] = $OUTPUT->render($mastercheckbox);
138 $columns[] = 'select';
139 }
140
141 $headers[] = get_string('fullname');
142 $columns[] = 'fullname';
143
144 $extrafields = get_extra_user_fields($this->context);
145 foreach ($extrafields as $field) {
146 $headers[] = get_user_field_name($field);
147 $columns[] = $field;
148 }
149
150 $headers[] = get_string('roles');
151 $columns[] = 'roles';
152
153 // Get the list of fields we have to hide.
154 $hiddenfields = array();
155 if (!has_capability('moodle/course:viewhiddenuserfields', $this->context)) {
156 $hiddenfields = array_flip(explode(',', $CFG->hiddenuserfields));
157 }
158
159 // Add column for groups if the user can view them.
160 $canseegroups = !isset($hiddenfields['groups']);
161 if ($canseegroups) {
162 $headers[] = get_string('groups');
163 $columns[] = 'groups';
164 }
165
166 // Do not show the columns if it exists in the hiddenfields array.
167 if (!isset($hiddenfields['lastaccess'])) {
168 if ($this->courseid == SITEID) {
169 $headers[] = get_string('lastsiteaccess');
170 } else {
171 $headers[] = get_string('lastcourseaccess');
172 }
173 $columns[] = 'lastaccess';
174 }
175
176 $canreviewenrol = has_capability('moodle/course:enrolreview', $this->context);
177 if ($canreviewenrol && $this->courseid != SITEID) {
178 $columns[] = 'status';
179 $headers[] = get_string('participationstatus', 'enrol');
180 $this->no_sorting('status');
181 };
182
183 $this->define_columns($columns);
184 $this->define_headers($headers);
185
8a805e03
SR
186 // The name column is a header.
187 $this->define_header_column('fullname');
188
1592c3c4
SL
189 // Make this table sorted by last name by default.
190 $this->sortable(true, 'lastname');
191
192 $this->no_sorting('select');
193 $this->no_sorting('roles');
194 if ($canseegroups) {
195 $this->no_sorting('groups');
196 }
197
198 $this->set_attribute('id', 'participants');
199
200 $this->countries = get_string_manager()->get_list_of_countries(true);
201 $this->extrafields = $extrafields;
202 if ($canseegroups) {
203 $this->groups = groups_get_all_groups($this->courseid, 0, 0, 'g.*', true);
204 }
3827cbe5
PH
205
206 // If user has capability to review enrol, show them both role names.
207 $allrolesnamedisplay = ($canreviewenrol ? ROLENAME_BOTH : ROLENAME_ALIAS);
208 $this->allroles = role_fix_names(get_all_roles($this->context), $this->context, $allrolesnamedisplay);
209 $this->assignableroles = get_assignable_roles($this->context, ROLENAME_BOTH, false);
1592c3c4
SL
210 $this->profileroles = get_profile_roles($this->context);
211 $this->viewableroles = get_viewable_roles($this->context);
80d13bfe
JP
212
213 parent::out($pagesize, $useinitialsbar, $downloadhelpbutton);
214
215 if (has_capability('moodle/course:enrolreview', $this->context)) {
478039f9
AN
216 $params = [
217 'contextid' => $this->context->id,
218 'uniqueid' => $this->uniqueid,
219 ];
80d13bfe
JP
220 $PAGE->requires->js_call_amd('core_user/status_field', 'init', [$params]);
221 }
222 }
223
bc47b706
MN
224 /**
225 * Generate the select column.
226 *
227 * @param \stdClass $data
228 * @return string
229 */
230 public function col_select($data) {
df92be9d
JP
231 global $OUTPUT;
232
233 $checkbox = new \core\output\checkbox_toggleall('participants-table', false, [
234 'classes' => 'usercheckbox m-1',
1cac0870 235 'id' => 'user' . $data->id,
df92be9d 236 'name' => 'user' . $data->id,
07c91064 237 'checked' => false,
1cac0870
JP
238 'label' => get_string('selectitem', 'moodle', fullname($data)),
239 'labelclasses' => 'accesshide',
df92be9d
JP
240 ]);
241
242 return $OUTPUT->render($checkbox);
bc47b706
MN
243 }
244
245 /**
246 * Generate the fullname column.
247 *
248 * @param \stdClass $data
249 * @return string
250 */
251 public function col_fullname($data) {
252 global $OUTPUT;
253
c5d59db9 254 return $OUTPUT->user_picture($data, array('size' => 35, 'courseid' => $this->course->id, 'includefullname' => true));
bc47b706
MN
255 }
256
5d0b4765
DW
257 /**
258 * User roles column.
259 *
260 * @param \stdClass $data
261 * @return string
262 */
263 public function col_roles($data) {
264 global $OUTPUT;
265
0ae28fad 266 $roles = isset($this->allroleassignments[$data->id]) ? $this->allroleassignments[$data->id] : [];
5d0b4765
DW
267 $editable = new \core_user\output\user_roles_editable($this->course,
268 $this->context,
269 $data,
270 $this->allroles,
271 $this->assignableroles,
9df2fdec 272 $this->profileroles,
a63cd3e2
AH
273 $roles,
274 $this->viewableroles);
5d0b4765
DW
275
276 return $OUTPUT->render_from_template('core/inplace_editable', $editable->export_for_template($OUTPUT));
277 }
278
2fa35b8d
DW
279 /**
280 * Generate the groups column.
281 *
f3ecea3a 282 * @param \stdClass $data
2fa35b8d
DW
283 * @return string
284 */
f3ecea3a 285 public function col_groups($data) {
2fa35b8d
DW
286 global $OUTPUT;
287
288 $usergroups = [];
289 foreach ($this->groups as $coursegroup) {
f3ecea3a 290 if (isset($coursegroup->members[$data->id])) {
2fa35b8d
DW
291 $usergroups[] = $coursegroup->id;
292 }
293 }
f3ecea3a 294 $editable = new \core_group\output\user_groups_editable($this->course, $this->context, $data, $this->groups, $usergroups);
2fa35b8d
DW
295 return $OUTPUT->render_from_template('core/inplace_editable', $editable->export_for_template($OUTPUT));
296 }
297
bc47b706
MN
298 /**
299 * Generate the country column.
300 *
301 * @param \stdClass $data
302 * @return string
303 */
304 public function col_country($data) {
305 if (!empty($this->countries[$data->country])) {
306 return $this->countries[$data->country];
307 }
308 return '';
309 }
310
311 /**
312 * Generate the last access column.
313 *
314 * @param \stdClass $data
315 * @return string
316 */
317 public function col_lastaccess($data) {
318 if ($data->lastaccess) {
319 return format_time(time() - $data->lastaccess);
320 }
321
322 return get_string('never');
323 }
324
fb792967
JP
325 /**
326 * Generate the status column.
327 *
80d13bfe 328 * @param \stdClass $data The data object.
fb792967
JP
329 * @return string
330 */
331 public function col_status($data) {
332 global $CFG, $OUTPUT, $PAGE;
333
334 $enrolstatusoutput = '';
335 $canreviewenrol = has_capability('moodle/course:enrolreview', $this->context);
336 if ($canreviewenrol) {
c157e137
MH
337 $canviewfullnames = has_capability('moodle/site:viewfullnames', $this->context);
338 $fullname = fullname($data, $canviewfullnames);
4a42d3de 339 $coursename = format_string($this->course->fullname, true, array('context' => $this->context));
fb792967
JP
340 require_once($CFG->dirroot . '/enrol/locallib.php');
341 $manager = new \course_enrolment_manager($PAGE, $this->course);
342 $userenrolments = $manager->get_user_enrolments($data->id);
343 foreach ($userenrolments as $ue) {
fb792967
JP
344 $timestart = $ue->timestart;
345 $timeend = $ue->timeend;
6676f202 346 $timeenrolled = $ue->timecreated;
90ffcfa8
JP
347 $actions = $ue->enrolmentplugin->get_user_enrolment_actions($manager, $ue);
348 $instancename = $ue->enrolmentinstancename;
349
350 // Default status field label and value.
351 $status = get_string('participationactive', 'enrol');
352 $statusval = status_field::STATUS_ACTIVE;
fb792967
JP
353 switch ($ue->status) {
354 case ENROL_USER_ACTIVE:
355 $currentdate = new DateTime();
356 $now = $currentdate->getTimestamp();
3449816e
SA
357 $isexpired = $timestart > $now || ($timeend > 0 && $timeend < $now);
358 $enrolmentdisabled = $ue->enrolmentinstance->status == ENROL_INSTANCE_DISABLED;
359 // If user enrolment status has not yet started/already ended or the enrolment instance is disabled.
360 if ($isexpired || $enrolmentdisabled) {
fb792967 361 $status = get_string('participationnotcurrent', 'enrol');
90ffcfa8 362 $statusval = status_field::STATUS_NOT_CURRENT;
fb792967
JP
363 }
364 break;
365 case ENROL_USER_SUSPENDED:
366 $status = get_string('participationsuspended', 'enrol');
90ffcfa8 367 $statusval = status_field::STATUS_SUSPENDED;
fb792967
JP
368 break;
369 }
90ffcfa8 370
26b34c30
AA
371 $statusfield = new status_field($instancename, $coursename, $fullname, $status, $timestart, $timeend,
372 $actions, $timeenrolled);
90ffcfa8 373 $statusfielddata = $statusfield->set_status($statusval)->export_for_template($OUTPUT);
80d13bfe 374 $enrolstatusoutput .= $OUTPUT->render_from_template('core_user/status_field', $statusfielddata);
fb792967
JP
375 }
376 }
377 return $enrolstatusoutput;
378 }
379
bc47b706
MN
380 /**
381 * This function is used for the extra user fields.
382 *
383 * These are being dynamically added to the table so there are no functions 'col_<userfieldname>' as
384 * the list has the potential to increase in the future and we don't want to have to remember to add
385 * a new method to this class. We also don't want to pollute this class with unnecessary methods.
386 *
387 * @param string $colname The column name
388 * @param \stdClass $data
389 * @return string
390 */
391 public function other_cols($colname, $data) {
392 // Do not process if it is not a part of the extra fields.
393 if (!in_array($colname, $this->extrafields)) {
394 return '';
395 }
396
397 return s($data->{$colname});
398 }
399
400 /**
401 * Query the database for results to display in the table.
402 *
403 * @param int $pagesize size of page for paginated displayed table.
404 * @param bool $useinitialsbar do you want to use the initials bar.
405 */
406 public function query_db($pagesize, $useinitialsbar = true) {
407 list($twhere, $tparams) = $this->get_sql_where();
86e71acc 408 $psearch = new participants_search($this->course, $this->context, $this->filterset);
bc47b706 409
86e71acc 410 $total = $psearch->get_total_participants_count($twhere, $tparams);
bc47b706
MN
411
412 $this->pagesize($pagesize, $total);
413
414 $sort = $this->get_sql_sort();
415 if ($sort) {
416 $sort = 'ORDER BY ' . $sort;
417 }
418
86e71acc
MH
419 $rawdata = $psearch->get_participants($twhere, $tparams, $sort, $this->get_page_start(), $this->get_page_size());
420
5359c517
TH
421 $this->rawdata = [];
422 foreach ($rawdata as $user) {
423 $this->rawdata[$user->id] = $user;
424 }
425 $rawdata->close();
426
427 if ($this->rawdata) {
428 $this->allroleassignments = get_users_roles($this->context, array_keys($this->rawdata),
429 true, 'c.contextlevel DESC, r.sortorder ASC');
430 } else {
431 $this->allroleassignments = [];
432 }
bc47b706
MN
433
434 // Set initial bars.
435 if ($useinitialsbar) {
436 $this->initialbars(true);
437 }
438 }
df92be9d
JP
439
440 /**
441 * Override the table show_hide_link to not show for select column.
442 *
443 * @param string $column the column name, index into various names.
444 * @param int $index numerical index of the column.
445 * @return string HTML fragment.
446 */
447 protected function show_hide_link($column, $index) {
448 if ($index > 0) {
449 return parent::show_hide_link($column, $index);
450 }
451 return '';
452 }
8146335f
SL
453
454 /**
455 * Set filters and build table structure.
456 *
457 * @param filterset $filterset The filterset object to get the filters from.
458 */
459 public function set_filterset(filterset $filterset): void {
1592c3c4
SL
460 // Get the context.
461 $this->courseid = $filterset->get_filter('courseid')->current();
462 $this->course = get_course($this->courseid);
463 $this->context = \context_course::instance($this->courseid, MUST_EXIST);
8146335f
SL
464
465 // Process the filterset.
5615a772 466 parent::set_filterset($filterset);
8146335f
SL
467 }
468
8146335f 469 /**
5615a772 470 * Guess the base url for the participants table.
8146335f 471 */
5615a772
AN
472 public function guess_base_url(): void {
473 $this->baseurl = new moodle_url('/user/index.php', ['id' => $this->courseid]);
1592c3c4
SL
474 }
475
476 /**
477 * Get the context of the current table.
478 *
479 * Note: This function should not be called until after the filterset has been provided.
480 *
481 * @return context
482 */
c00552b3 483 public function get_context(): context {
1592c3c4
SL
484 return $this->context;
485 }
8146335f 486}