MDL-63387 user: show both role names on course participants page.
[moodle.git] / user / classes / table / participants.php
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/>.
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 declare(strict_types=1);
26 namespace core_user\table;
28 use DateTime;
29 use context;
30 use core_table\dynamic as dynamic_table;
31 use core_table\local\filter\filterset;
32 use core_user\output\status_field;
33 use core_user\table\participants_search;
34 use moodle_url;
36 defined('MOODLE_INTERNAL') || die;
38 global $CFG;
40 require_once($CFG->libdir . '/tablelib.php');
41 require_once($CFG->dirroot . '/user/lib.php');
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  */
50 class participants extends \table_sql implements dynamic_table {
52     /**
53      * @var int $courseid The course id
54      */
55     protected $courseid;
57     /**
58      * @var string[] The list of countries.
59      */
60     protected $countries;
62     /**
63      * @var \stdClass[] The list of groups with membership info for the course.
64      */
65     protected $groups;
67     /**
68      * @var string[] Extra fields to display.
69      */
70     protected $extrafields;
72     /**
73      * @var \stdClass $course The course details.
74      */
75     protected $course;
77     /**
78      * @var  context $context The course context.
79      */
80     protected $context;
82     /**
83      * @var \stdClass[] List of roles indexed by roleid.
84      */
85     protected $allroles;
87     /**
88      * @var \stdClass[] List of roles indexed by roleid.
89      */
90     protected $allroleassignments;
92     /**
93      * @var \stdClass[] Assignable roles in this course.
94      */
95     protected $assignableroles;
97     /**
98      * @var \stdClass[] Profile roles in this course.
99      */
100     protected $profileroles;
102     /**
103      * @var filterset Filterset describing which participants to include.
104      */
105     protected $filterset;
107     /** @var \stdClass[] $viewableroles */
108     private $viewableroles;
110     /** @var moodle_url $baseurl The base URL for the report. */
111     public $baseurl;
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 = '') {
121         global $CFG, $OUTPUT, $PAGE;
123         // Define the headers and columns.
124         $headers = [];
125         $columns = [];
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',
132                 'label' => get_string('selectall'),
133                 'labelclasses' => 'sr-only',
134                 'classes' => 'm-1',
135                 'checked' => false,
136             ]);
137             $headers[] = $OUTPUT->render($mastercheckbox);
138             $columns[] = 'select';
139         }
141         $headers[] = get_string('fullname');
142         $columns[] = 'fullname';
144         $extrafields = get_extra_user_fields($this->context);
145         foreach ($extrafields as $field) {
146             $headers[] = get_user_field_name($field);
147             $columns[] = $field;
148         }
150         $headers[] = get_string('roles');
151         $columns[] = 'roles';
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         }
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         }
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         }
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         };
183         $this->define_columns($columns);
184         $this->define_headers($headers);
186         // The name column is a header.
187         $this->define_header_column('fullname');
189         // Make this table sorted by last name by default.
190         $this->sortable(true, 'lastname');
192         $this->no_sorting('select');
193         $this->no_sorting('roles');
194         if ($canseegroups) {
195             $this->no_sorting('groups');
196         }
198         $this->set_attribute('id', 'participants');
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         }
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);
210         $this->profileroles = get_profile_roles($this->context);
211         $this->viewableroles = get_viewable_roles($this->context);
213         parent::out($pagesize, $useinitialsbar, $downloadhelpbutton);
215         if (has_capability('moodle/course:enrolreview', $this->context)) {
216             $params = [
217                 'contextid' => $this->context->id,
218                 'uniqueid' => $this->uniqueid,
219             ];
220             $PAGE->requires->js_call_amd('core_user/status_field', 'init', [$params]);
221         }
222     }
224     /**
225      * Generate the select column.
226      *
227      * @param \stdClass $data
228      * @return string
229      */
230     public function col_select($data) {
231         global $OUTPUT;
233         $checkbox = new \core\output\checkbox_toggleall('participants-table', false, [
234             'classes' => 'usercheckbox m-1',
235             'id' => 'user' . $data->id,
236             'name' => 'user' . $data->id,
237             'checked' => false,
238             'label' => get_string('selectitem', 'moodle', fullname($data)),
239             'labelclasses' => 'accesshide',
240         ]);
242         return $OUTPUT->render($checkbox);
243     }
245     /**
246      * Generate the fullname column.
247      *
248      * @param \stdClass $data
249      * @return string
250      */
251     public function col_fullname($data) {
252         global $OUTPUT;
254         return $OUTPUT->user_picture($data, array('size' => 35, 'courseid' => $this->course->id, 'includefullname' => true));
255     }
257     /**
258      * User roles column.
259      *
260      * @param \stdClass $data
261      * @return string
262      */
263     public function col_roles($data) {
264         global $OUTPUT;
266         $roles = isset($this->allroleassignments[$data->id]) ? $this->allroleassignments[$data->id] : [];
267         $editable = new \core_user\output\user_roles_editable($this->course,
268                                                               $this->context,
269                                                               $data,
270                                                               $this->allroles,
271                                                               $this->assignableroles,
272                                                               $this->profileroles,
273                                                               $roles,
274                                                               $this->viewableroles);
276         return $OUTPUT->render_from_template('core/inplace_editable', $editable->export_for_template($OUTPUT));
277     }
279     /**
280      * Generate the groups column.
281      *
282      * @param \stdClass $data
283      * @return string
284      */
285     public function col_groups($data) {
286         global $OUTPUT;
288         $usergroups = [];
289         foreach ($this->groups as $coursegroup) {
290             if (isset($coursegroup->members[$data->id])) {
291                 $usergroups[] = $coursegroup->id;
292             }
293         }
294         $editable = new \core_group\output\user_groups_editable($this->course, $this->context, $data, $this->groups, $usergroups);
295         return $OUTPUT->render_from_template('core/inplace_editable', $editable->export_for_template($OUTPUT));
296     }
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     }
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         }
322         return get_string('never');
323     }
325     /**
326      * Generate the status column.
327      *
328      * @param \stdClass $data The data object.
329      * @return string
330      */
331     public function col_status($data) {
332         global $CFG, $OUTPUT, $PAGE;
334         $enrolstatusoutput = '';
335         $canreviewenrol = has_capability('moodle/course:enrolreview', $this->context);
336         if ($canreviewenrol) {
337             $canviewfullnames = has_capability('moodle/site:viewfullnames', $this->context);
338             $fullname = fullname($data, $canviewfullnames);
339             $coursename = format_string($this->course->fullname, true, array('context' => $this->context));
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) {
344                 $timestart = $ue->timestart;
345                 $timeend = $ue->timeend;
346                 $timeenrolled = $ue->timecreated;
347                 $actions = $ue->enrolmentplugin->get_user_enrolment_actions($manager, $ue);
348                 $instancename = $ue->enrolmentinstancename;
350                 // Default status field label and value.
351                 $status = get_string('participationactive', 'enrol');
352                 $statusval = status_field::STATUS_ACTIVE;
353                 switch ($ue->status) {
354                     case ENROL_USER_ACTIVE:
355                         $currentdate = new DateTime();
356                         $now = $currentdate->getTimestamp();
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) {
361                             $status = get_string('participationnotcurrent', 'enrol');
362                             $statusval = status_field::STATUS_NOT_CURRENT;
363                         }
364                         break;
365                     case ENROL_USER_SUSPENDED:
366                         $status = get_string('participationsuspended', 'enrol');
367                         $statusval = status_field::STATUS_SUSPENDED;
368                         break;
369                 }
371                 $statusfield = new status_field($instancename, $coursename, $fullname, $status, $timestart, $timeend,
372                     $actions, $timeenrolled);
373                 $statusfielddata = $statusfield->set_status($statusval)->export_for_template($OUTPUT);
374                 $enrolstatusoutput .= $OUTPUT->render_from_template('core_user/status_field', $statusfielddata);
375             }
376         }
377         return $enrolstatusoutput;
378     }
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         }
397         return s($data->{$colname});
398     }
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();
408         $psearch = new participants_search($this->course, $this->context, $this->filterset);
410         $total = $psearch->get_total_participants_count($twhere, $tparams);
412         $this->pagesize($pagesize, $total);
414         $sort = $this->get_sql_sort();
415         if ($sort) {
416             $sort = 'ORDER BY ' . $sort;
417         }
419         $rawdata = $psearch->get_participants($twhere, $tparams, $sort, $this->get_page_start(), $this->get_page_size());
421         $this->rawdata = [];
422         foreach ($rawdata as $user) {
423             $this->rawdata[$user->id] = $user;
424         }
425         $rawdata->close();
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         }
434         // Set initial bars.
435         if ($useinitialsbar) {
436             $this->initialbars(true);
437         }
438     }
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     }
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 {
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);
465         // Process the filterset.
466         parent::set_filterset($filterset);
467     }
469     /**
470      * Guess the base url for the participants table.
471      */
472     public function guess_base_url(): void {
473         $this->baseurl = new moodle_url('/user/index.php', ['id' => $this->courseid]);
474     }
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      */
483     public function get_context(): context {
484         return $this->context;
485     }