Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 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
namespace core_admin\reportbuilder\local\systemreports;
18
 
19
use core_admin\reportbuilder\local\filters\courserole;
20
use core\context\system;
21
use core_cohort\reportbuilder\local\entities\cohort;
22
use core_cohort\reportbuilder\local\entities\cohort_member;
23
use core_reportbuilder\local\entities\user;
24
use core_reportbuilder\local\filters\boolean_select;
25
use core_reportbuilder\local\helpers\database;
26
use core_reportbuilder\local\helpers\user_profile_fields;
27
use core_reportbuilder\local\report\action;
28
use core_reportbuilder\local\report\filter;
29
use core_reportbuilder\system_report;
30
use core_role\reportbuilder\local\entities\role;
31
use core_user\fields;
32
use lang_string;
33
use moodle_url;
34
use pix_icon;
35
 
36
defined('MOODLE_INTERNAL') || die();
37
 
38
require_once($CFG->libdir.'/adminlib.php');
39
require_once($CFG->libdir.'/authlib.php');
40
require_once($CFG->libdir.'/enrollib.php');
41
require_once($CFG->dirroot.'/user/lib.php');
42
 
43
/**
44
 * Browse users system report class implementation
45
 *
46
 * @package    core_admin
47
 * @copyright  2023 David Carrillo <davidmc@moodle.com>
48
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
49
 */
50
class users extends system_report {
51
 
52
    /**
53
     * Initialise report, we need to set the main table, load our entities and set columns/filters
54
     */
55
    protected function initialise(): void {
56
        global $CFG;
57
 
58
        // Our main entity, it contains all of the column definitions that we need.
59
        $entityuser = new user();
60
        $entityuseralias = $entityuser->get_table_alias('user');
61
 
62
        $this->set_main_table('user', $entityuseralias);
63
        $this->add_entity($entityuser);
64
 
65
        // Any columns required by actions should be defined here to ensure they're always available.
66
        $fullnamefields = array_map(fn($field) => "{$entityuseralias}.{$field}", fields::get_name_fields());
67
        $this->add_base_fields("{$entityuseralias}.id, {$entityuseralias}.confirmed, {$entityuseralias}.mnethostid,
68
            {$entityuseralias}.suspended, {$entityuseralias}.username, " . implode(', ', $fullnamefields));
69
 
70
        if ($this->get_parameter('withcheckboxes', false, PARAM_BOOL)) {
71
            $canviewfullnames = has_capability('moodle/site:viewfullnames', \context_system::instance());
72
            $this->set_checkbox_toggleall(static function(\stdClass $row) use ($canviewfullnames): array {
73
                return [$row->id, fullname($row, $canviewfullnames)];
74
            });
75
        }
76
 
77
        $paramguest = database::generate_param_name();
78
        $this->add_base_condition_sql("{$entityuseralias}.deleted <> 1 AND {$entityuseralias}.id <> :{$paramguest}",
79
            [$paramguest => $CFG->siteguest]);
80
 
81
        $entitycohortmember = new cohort_member();
82
        $entitycohortmemberalias = $entitycohortmember->get_table_alias('cohort_members');
83
        $this->add_entity($entitycohortmember
84
            ->add_joins($entitycohortmember->get_joins())
85
            ->add_join("LEFT JOIN {cohort_members} {$entitycohortmemberalias}
86
                ON {$entityuseralias}.id = {$entitycohortmemberalias}.userid")
87
        );
88
 
89
        $entitycohort = new cohort();
90
        $entitycohortalias = $entitycohort->get_table_alias('cohort');
91
        $this->add_entity($entitycohort
92
            ->add_joins($entitycohort->get_joins())
93
            ->add_joins($entitycohortmember->get_joins())
94
            ->add_join("LEFT JOIN {cohort} {$entitycohortalias}
95
                ON {$entitycohortalias}.id = {$entitycohortmemberalias}.cohortid")
96
        );
97
 
98
        // Join the role entity (Needed for the system role filter).
99
        $roleentity = new role();
100
        $role = $roleentity->get_table_alias('role');
101
        $this->add_entity($roleentity
102
            ->add_join("LEFT JOIN (
103
                SELECT DISTINCT r0.id, ras.userid
104
                FROM {role} r0
105
                JOIN {role_assignments} ras ON ras.roleid = r0.id
106
                WHERE ras.contextid = ".SYSCONTEXTID."
107
             ) {$role} ON {$role}.userid = {$entityuseralias}.id")
108
        );
109
 
110
        // Now we can call our helper methods to add the content we want to include in the report.
111
        $this->add_columns();
112
        $this->add_filters();
113
        $this->add_actions();
114
 
115
        // Set if report can be downloaded.
116
        $this->set_downloadable(true);
117
    }
118
 
119
    /**
120
     * Validates access to view this report
121
     *
122
     * @return bool
123
     */
124
    protected function can_view(): bool {
125
        return has_any_capability(['moodle/user:update', 'moodle/user:delete'], system::instance());
126
    }
127
 
128
    /**
129
     * Adds the columns we want to display in the report
130
     *
131
     * They are all provided by the entities we previously added in the {@see initialise} method, referencing each by their
132
     * unique identifier
133
     */
134
    public function add_columns(): void {
135
        $entityuser = $this->get_entity('user');
136
        $entityuseralias = $entityuser->get_table_alias('user');
137
 
138
        $this->add_column($entityuser->get_column('fullnamewithpicturelink'));
139
 
140
        // Include identity field columns.
141
        $identitycolumns = $entityuser->get_identity_columns($this->get_context());
142
        foreach ($identitycolumns as $identitycolumn) {
143
            $this->add_column($identitycolumn);
144
        }
145
 
146
        // Add "Last access" column.
147
        $this->add_column(($entityuser->get_column('lastaccess'))
148
            ->set_callback(static function ($value, \stdClass $row): string {
149
                if ($row->lastaccess) {
150
                    return format_time(time() - $row->lastaccess);
151
                }
152
                return get_string('never');
153
            })
154
        );
155
 
156
        if ($column = $this->get_column('user:fullnamewithpicturelink')) {
157
            $column
158
                ->add_fields("{$entityuseralias}.suspended, {$entityuseralias}.confirmed")
159
                ->add_callback(static function(string $fullname, \stdClass $row): string {
160
                    if ($row->suspended) {
161
                        $fullname .= ' ' . \html_writer::tag('span', get_string('suspended', 'moodle'),
162
                            ['class' => 'badge badge-secondary ml-1']);
163
                    }
164
                    if (!$row->confirmed) {
165
                        $fullname .= ' ' . \html_writer::tag('span', get_string('confirmationpending', 'admin'),
166
                            ['class' => 'badge badge-danger ml-1']);
167
                    }
168
                    return $fullname;
169
                });
170
        }
171
 
172
        $this->set_initial_sort_column('user:fullnamewithpicturelink', SORT_ASC);
173
        $this->set_default_no_results_notice(new lang_string('nousersfound', 'moodle'));
174
    }
175
 
176
    /**
177
     * Adds the filters we want to display in the report
178
     *
179
     * They are all provided by the entities we previously added in the {@see initialise} method, referencing each by their
180
     * unique identifier
181
     */
182
    protected function add_filters(): void {
183
        $entityuser = $this->get_entity('user');
184
        $entityuseralias = $entityuser->get_table_alias('user');
185
 
186
        $filters = [
187
            'user:fullname',
188
            'user:firstname',
189
            'user:lastname',
190
            'user:username',
191
            'user:idnumber',
192
            'user:email',
193
            'user:department',
194
            'user:institution',
195
            'user:city',
196
            'user:country',
197
            'user:confirmed',
198
            'user:suspended',
199
            'user:timecreated',
200
            'user:lastaccess',
201
            'user:timemodified',
202
            'user:auth',
203
            'user:lastip',
204
            'cohort:idnumber',
205
            'role:name',
206
        ];
207
        $this->add_filters_from_entities($filters);
208
 
209
        // Enrolled in any course filter.
210
        $ue = database::generate_alias();
211
        [$now1, $now2] = database::generate_param_names(2);
212
        $now = time();
213
        $sql = "CASE WHEN ({$entityuseralias}.id IN (
214
            SELECT userid FROM {user_enrolments} {$ue}
215
            WHERE {$ue}.status = " . ENROL_USER_ACTIVE . "
216
            AND ({$ue}.timestart = 0 OR {$ue}.timestart < :{$now1})
217
            AND ({$ue}.timeend = 0 OR {$ue}.timeend > :{$now2})
218
            )) THEN 1 ELSE 0 END";
219
 
220
        $this->add_filter((new filter(
221
            boolean_select::class,
222
            'enrolledinanycourse',
223
            new lang_string('anycourses', 'filters'),
224
            $this->get_entity('user')->get_entity_name(),
225
        ))
226
            ->set_field_sql($sql, [
227
                $now1 => $now,
228
                $now2 => $now,
229
            ])
230
        );
231
 
232
        // Course role filter.
233
        $this->add_filter((new filter(
234
            courserole::class,
235
            'courserole',
236
            new lang_string('courserole', 'filters'),
237
            $this->get_entity('user')->get_entity_name(),
238
        ))
239
            ->set_field_sql("{$entityuseralias}.id")
240
        );
241
 
242
        // Add user profile fields filters.
243
        $userprofilefields = new user_profile_fields($entityuseralias . '.id', $entityuser->get_entity_name());
244
        foreach ($userprofilefields->get_filters() as $filter) {
245
            $this->add_filter($filter);
246
        }
247
 
248
        // Set options for system role filter.
249
        if ($filter = $this->get_filter('role:name')) {
250
            $filter
251
                ->set_header(new lang_string('globalrole', 'role'))
252
                ->set_options(get_assignable_roles(system::instance()));
253
        }
254
    }
255
 
256
    /**
257
     * Add the system report actions. An extra column will be appended to each row, containing all actions added here
258
     *
259
     * Note the use of ":id" placeholder which will be substituted according to actual values in the row
260
     */
261
    protected function add_actions(): void {
262
        global $DB, $USER;
263
 
264
        $contextsystem = system::instance();
265
 
266
        // Action to edit users.
267
        $this->add_action((new action(
268
            new moodle_url('/user/editadvanced.php', ['id' => ':id', 'course' => get_site()->id]),
269
            new pix_icon('t/edit', ''),
270
            [],
271
            false,
272
            new lang_string('edit', 'moodle'),
273
        ))->add_callback(static function(\stdclass $row) use ($USER, $contextsystem): bool {
274
            return has_capability('moodle/user:update', $contextsystem) && (is_siteadmin($USER) || !is_siteadmin($row));
275
        }));
276
 
277
        // Action to suspend users (non mnet remote users).
278
        $this->add_action((new action(
279
            new moodle_url('/admin/user.php', ['suspend' => ':id', 'sesskey' => sesskey()]),
280
            new pix_icon('t/show', ''),
281
            [],
282
            false,
283
            new lang_string('suspenduser', 'admin'),
284
        ))->add_callback(static function(\stdclass $row) use ($USER, $contextsystem): bool {
285
            return has_capability('moodle/user:update', $contextsystem) && !$row->suspended && !is_mnet_remote_user($row) &&
286
                !($row->id == $USER->id || is_siteadmin($row));
287
        }));
288
 
289
        // Action to unsuspend users (non mnet remote users).
290
        $this->add_action((new action(
291
            new moodle_url('/admin/user.php', ['unsuspend' => ':id', 'sesskey' => sesskey()]),
292
            new pix_icon('t/hide', ''),
293
            [],
294
            false,
295
            new lang_string('unsuspenduser', 'admin'),
296
        ))->add_callback(static function(\stdclass $row) use ($USER, $contextsystem): bool {
297
            return has_capability('moodle/user:update', $contextsystem) && $row->suspended && !is_mnet_remote_user($row) &&
298
                !($row->id == $USER->id || is_siteadmin($row));
299
        }));
300
 
301
        // Action to unlock users (non mnet remote users).
302
        $this->add_action((new action(
303
            new moodle_url('/admin/user.php', ['unlock' => ':id', 'sesskey' => sesskey()]),
304
            new pix_icon('t/unlock', ''),
305
            [],
306
            false,
307
            new lang_string('unlockaccount', 'admin'),
308
        ))->add_callback(static function(\stdclass $row) use ($contextsystem): bool {
309
            return has_capability('moodle/user:update', $contextsystem) && !is_mnet_remote_user($row) &&
310
                login_is_lockedout($row);
311
        }));
312
 
313
        // Action to suspend users (mnet remote users).
314
        $this->add_action((new action(
315
            new moodle_url('/admin/user.php', ['acl' => ':id', 'sesskey' => sesskey(), 'accessctrl' => 'deny']),
316
            new pix_icon('t/show', ''),
317
            [],
318
            false,
319
            new lang_string('denyaccess', 'mnet'),
320
        ))->add_callback(static function(\stdclass $row) use ($DB, $contextsystem): bool {
321
            if (!$accessctrl = $DB->get_field(table: 'mnet_sso_access_control', return: 'accessctrl',
322
                conditions: ['username' => $row->username, 'mnet_host_id' => $row->mnethostid]
323
            )) {
324
                $accessctrl = 'allow';
325
            }
326
 
327
            return has_capability('moodle/user:update', $contextsystem) && !$row->suspended &&
328
                is_mnet_remote_user($row) && $accessctrl == 'allow';
329
        }));
330
 
331
        // Action to unsuspend users (mnet remote users).
332
        $this->add_action((new action(
333
            new moodle_url('/admin/user.php', ['acl' => ':id', 'sesskey' => sesskey(), 'accessctrl' => 'allow']),
334
            new pix_icon('t/hide', ''),
335
            [],
336
            false,
337
            new lang_string('allowaccess', 'mnet'),
338
        ))->add_callback(static function(\stdclass $row) use ($DB, $contextsystem): bool {
339
            if (!$accessctrl = $DB->get_field(table: 'mnet_sso_access_control', return: 'accessctrl',
340
                conditions: ['username' => $row->username, 'mnet_host_id' => $row->mnethostid]
341
            )) {
342
                $accessctrl = 'allow';
343
            }
344
 
345
            return has_capability('moodle/user:update', $contextsystem) && !$row->suspended &&
346
                is_mnet_remote_user($row) && $accessctrl == 'deny';
347
        }));
348
 
349
        // Action to delete users.
350
        $this->add_action((new action(
351
            new moodle_url('/admin/user.php', ['delete' => ':id', 'sesskey' => sesskey()]),
352
            new pix_icon('t/delete', ''),
353
            [
354
                'class' => 'text-danger',
355
                'data-modal' => 'confirmation',
356
                'data-modal-title-str' => json_encode(['deleteuser', 'admin']),
357
                'data-modal-content-str' => ':deletestr',
358
                'data-modal-yes-button-str' => json_encode(['delete', 'core']),
359
                'data-modal-destination' => ':deleteurl',
360
 
361
            ],
362
            false,
363
            new lang_string('delete', 'moodle'),
364
        ))->add_callback(static function(\stdclass $row) use ($USER, $contextsystem): bool {
365
 
366
            // Populate deletion modal attributes.
367
            $row->deletestr = json_encode([
368
                'deletecheckfull',
369
                'moodle',
370
                fullname($row, true),
371
            ]);
372
 
373
            $row->deleteurl = (new moodle_url('/admin/user.php', [
374
                'delete' => $row->id,
375
                'confirm' => md5($row->id),
376
                'sesskey' => sesskey(),
377
            ]))->out(false);
378
 
379
            return has_capability('moodle/user:delete', $contextsystem) &&
380
                !is_mnet_remote_user($row) && $row->id != $USER->id && !is_siteadmin($row);
381
        }));
382
 
383
        $this->add_action_divider();
384
 
385
        // Action to confirm users.
386
        $this->add_action((new action(
387
            new moodle_url('/admin/user.php', ['confirmuser' => ':id', 'sesskey' => sesskey()]),
388
            new pix_icon('t/check', ''),
389
            [],
390
            false,
391
            new lang_string('confirmaccount', 'moodle'),
392
        ))->add_callback(static function(\stdclass $row) use ($contextsystem): bool {
393
            return has_capability('moodle/user:update', $contextsystem) && !$row->confirmed;
394
        }));
395
 
396
        // Action to resend email.
397
        $this->add_action((new action(
398
            new moodle_url('/admin/user.php', ['resendemail' => ':id', 'sesskey' => sesskey()]),
399
            new pix_icon('t/email', ''),
400
            [],
401
            false,
402
            new lang_string('resendemail', 'moodle'),
403
        ))->add_callback(static function(\stdclass $row): bool {
404
            return !$row->confirmed && !is_mnet_remote_user($row);
405
        }));
406
    }
407
 
408
    /**
409
     * Row class
410
     *
411
     * @param \stdClass $row
412
     * @return string
413
     */
414
    public function get_row_class(\stdClass $row): string {
415
        return $row->suspended ? 'text-muted' : '';
416
    }
417
}