04d47f56fb78b31a2e30059bf956d642ae0fdebe
[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         }
205         $this->allroles = role_fix_names(get_all_roles($this->context), $this->context);
206         $this->assignableroles = get_assignable_roles($this->context, ROLENAME_ALIAS, false);
207         $this->profileroles = get_profile_roles($this->context);
208         $this->viewableroles = get_viewable_roles($this->context);
210         parent::out($pagesize, $useinitialsbar, $downloadhelpbutton);
212         if (has_capability('moodle/course:enrolreview', $this->context)) {
213             $params = [
214                 'contextid' => $this->context->id,
215                 'uniqueid' => $this->uniqueid,
216             ];
217             $PAGE->requires->js_call_amd('core_user/status_field', 'init', [$params]);
218         }
219     }
221     /**
222      * Generate the select column.
223      *
224      * @param \stdClass $data
225      * @return string
226      */
227     public function col_select($data) {
228         global $OUTPUT;
230         $checkbox = new \core\output\checkbox_toggleall('participants-table', false, [
231             'classes' => 'usercheckbox m-1',
232             'id' => 'user' . $data->id,
233             'name' => 'user' . $data->id,
234             'checked' => false,
235             'label' => get_string('selectitem', 'moodle', fullname($data)),
236             'labelclasses' => 'accesshide',
237         ]);
239         return $OUTPUT->render($checkbox);
240     }
242     /**
243      * Generate the fullname column.
244      *
245      * @param \stdClass $data
246      * @return string
247      */
248     public function col_fullname($data) {
249         global $OUTPUT;
251         return $OUTPUT->user_picture($data, array('size' => 35, 'courseid' => $this->course->id, 'includefullname' => true));
252     }
254     /**
255      * User roles column.
256      *
257      * @param \stdClass $data
258      * @return string
259      */
260     public function col_roles($data) {
261         global $OUTPUT;
263         $roles = isset($this->allroleassignments[$data->id]) ? $this->allroleassignments[$data->id] : [];
264         $editable = new \core_user\output\user_roles_editable($this->course,
265                                                               $this->context,
266                                                               $data,
267                                                               $this->allroles,
268                                                               $this->assignableroles,
269                                                               $this->profileroles,
270                                                               $roles,
271                                                               $this->viewableroles);
273         return $OUTPUT->render_from_template('core/inplace_editable', $editable->export_for_template($OUTPUT));
274     }
276     /**
277      * Generate the groups column.
278      *
279      * @param \stdClass $data
280      * @return string
281      */
282     public function col_groups($data) {
283         global $OUTPUT;
285         $usergroups = [];
286         foreach ($this->groups as $coursegroup) {
287             if (isset($coursegroup->members[$data->id])) {
288                 $usergroups[] = $coursegroup->id;
289             }
290         }
291         $editable = new \core_group\output\user_groups_editable($this->course, $this->context, $data, $this->groups, $usergroups);
292         return $OUTPUT->render_from_template('core/inplace_editable', $editable->export_for_template($OUTPUT));
293     }
295     /**
296      * Generate the country column.
297      *
298      * @param \stdClass $data
299      * @return string
300      */
301     public function col_country($data) {
302         if (!empty($this->countries[$data->country])) {
303             return $this->countries[$data->country];
304         }
305         return '';
306     }
308     /**
309      * Generate the last access column.
310      *
311      * @param \stdClass $data
312      * @return string
313      */
314     public function col_lastaccess($data) {
315         if ($data->lastaccess) {
316             return format_time(time() - $data->lastaccess);
317         }
319         return get_string('never');
320     }
322     /**
323      * Generate the status column.
324      *
325      * @param \stdClass $data The data object.
326      * @return string
327      */
328     public function col_status($data) {
329         global $CFG, $OUTPUT, $PAGE;
331         $enrolstatusoutput = '';
332         $canreviewenrol = has_capability('moodle/course:enrolreview', $this->context);
333         if ($canreviewenrol) {
334             $canviewfullnames = has_capability('moodle/site:viewfullnames', $this->context);
335             $fullname = fullname($data, $canviewfullnames);
336             $coursename = format_string($this->course->fullname, true, array('context' => $this->context));
337             require_once($CFG->dirroot . '/enrol/locallib.php');
338             $manager = new \course_enrolment_manager($PAGE, $this->course);
339             $userenrolments = $manager->get_user_enrolments($data->id);
340             foreach ($userenrolments as $ue) {
341                 $timestart = $ue->timestart;
342                 $timeend = $ue->timeend;
343                 $timeenrolled = $ue->timecreated;
344                 $actions = $ue->enrolmentplugin->get_user_enrolment_actions($manager, $ue);
345                 $instancename = $ue->enrolmentinstancename;
347                 // Default status field label and value.
348                 $status = get_string('participationactive', 'enrol');
349                 $statusval = status_field::STATUS_ACTIVE;
350                 switch ($ue->status) {
351                     case ENROL_USER_ACTIVE:
352                         $currentdate = new DateTime();
353                         $now = $currentdate->getTimestamp();
354                         $isexpired = $timestart > $now || ($timeend > 0 && $timeend < $now);
355                         $enrolmentdisabled = $ue->enrolmentinstance->status == ENROL_INSTANCE_DISABLED;
356                         // If user enrolment status has not yet started/already ended or the enrolment instance is disabled.
357                         if ($isexpired || $enrolmentdisabled) {
358                             $status = get_string('participationnotcurrent', 'enrol');
359                             $statusval = status_field::STATUS_NOT_CURRENT;
360                         }
361                         break;
362                     case ENROL_USER_SUSPENDED:
363                         $status = get_string('participationsuspended', 'enrol');
364                         $statusval = status_field::STATUS_SUSPENDED;
365                         break;
366                 }
368                 $statusfield = new status_field($instancename, $coursename, $fullname, $status, $timestart, $timeend,
369                     $actions, $timeenrolled);
370                 $statusfielddata = $statusfield->set_status($statusval)->export_for_template($OUTPUT);
371                 $enrolstatusoutput .= $OUTPUT->render_from_template('core_user/status_field', $statusfielddata);
372             }
373         }
374         return $enrolstatusoutput;
375     }
377     /**
378      * This function is used for the extra user fields.
379      *
380      * These are being dynamically added to the table so there are no functions 'col_<userfieldname>' as
381      * the list has the potential to increase in the future and we don't want to have to remember to add
382      * a new method to this class. We also don't want to pollute this class with unnecessary methods.
383      *
384      * @param string $colname The column name
385      * @param \stdClass $data
386      * @return string
387      */
388     public function other_cols($colname, $data) {
389         // Do not process if it is not a part of the extra fields.
390         if (!in_array($colname, $this->extrafields)) {
391             return '';
392         }
394         return s($data->{$colname});
395     }
397     /**
398      * Query the database for results to display in the table.
399      *
400      * @param int $pagesize size of page for paginated displayed table.
401      * @param bool $useinitialsbar do you want to use the initials bar.
402      */
403     public function query_db($pagesize, $useinitialsbar = true) {
404         list($twhere, $tparams) = $this->get_sql_where();
405         $psearch = new participants_search($this->course, $this->context, $this->filterset);
407         $total = $psearch->get_total_participants_count($twhere, $tparams);
409         $this->pagesize($pagesize, $total);
411         $sort = $this->get_sql_sort();
412         if ($sort) {
413             $sort = 'ORDER BY ' . $sort;
414         }
416         $rawdata = $psearch->get_participants($twhere, $tparams, $sort, $this->get_page_start(), $this->get_page_size());
418         $this->rawdata = [];
419         foreach ($rawdata as $user) {
420             $this->rawdata[$user->id] = $user;
421         }
422         $rawdata->close();
424         if ($this->rawdata) {
425             $this->allroleassignments = get_users_roles($this->context, array_keys($this->rawdata),
426                     true, 'c.contextlevel DESC, r.sortorder ASC');
427         } else {
428             $this->allroleassignments = [];
429         }
431         // Set initial bars.
432         if ($useinitialsbar) {
433             $this->initialbars(true);
434         }
435     }
437     /**
438      * Override the table show_hide_link to not show for select column.
439      *
440      * @param string $column the column name, index into various names.
441      * @param int $index numerical index of the column.
442      * @return string HTML fragment.
443      */
444     protected function show_hide_link($column, $index) {
445         if ($index > 0) {
446             return parent::show_hide_link($column, $index);
447         }
448         return '';
449     }
451     /**
452      * Set filters and build table structure.
453      *
454      * @param filterset $filterset The filterset object to get the filters from.
455      */
456     public function set_filterset(filterset $filterset): void {
457         // Get the context.
458         $this->courseid = $filterset->get_filter('courseid')->current();
459         $this->course = get_course($this->courseid);
460         $this->context = \context_course::instance($this->courseid, MUST_EXIST);
462         // Process the filterset.
463         parent::set_filterset($filterset);
464     }
466     /**
467      * Guess the base url for the participants table.
468      */
469     public function guess_base_url(): void {
470         $this->baseurl = new moodle_url('/user/index.php', ['id' => $this->courseid]);
471     }
473     /**
474      * Get the context of the current table.
475      *
476      * Note: This function should not be called until after the filterset has been provided.
477      *
478      * @return context
479      */
480     public function get_context(): context {
481         return $this->context;
482     }