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