Proyectos de Subversion Moodle

Rev

Ir a la última revisión | | 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
/**
18
 * Table log for displaying logs.
19
 *
20
 * @package    report_log
21
 * @copyright  2014 Rajesh Taneja <rajesh.taneja@gmail.com>
22
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
 
25
use core\report_helper;
26
 
27
defined('MOODLE_INTERNAL') || die;
28
global $CFG;
29
require_once($CFG->libdir . '/tablelib.php');
30
 
31
/**
32
 * Table log class for displaying logs.
33
 *
34
 * @package    report_log
35
 * @copyright  2014 Rajesh Taneja <rajesh.taneja@gmail.com>
36
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
37
 */
38
class report_log_table_log extends table_sql {
39
    /** @var array list of user fullnames shown in report */
40
    private $userfullnames = array();
41
 
42
    /** @var array list of context name shown in report */
43
    private $contextname = array();
44
 
45
    /** @var stdClass filters parameters */
46
    private $filterparams;
47
 
48
    /** @var int[] A list of users to filter by */
49
    private ?array $lateuseridfilter = null;
50
 
51
    /**
52
     * Sets up the table_log parameters.
53
     *
54
     * @param string $uniqueid unique id of form.
55
     * @param stdClass $filterparams (optional) filter params.
56
     *     - int courseid: id of course
57
     *     - int userid: user id
58
     *     - int|string modid: Module id or "site_errors" to view site errors
59
     *     - int groupid: Group id
60
     *     - \core\log\sql_reader logreader: reader from which data will be fetched.
61
     *     - int edulevel: educational level.
62
     *     - string action: view action
63
     *     - int date: Date from which logs to be viewed.
64
     */
65
    public function __construct($uniqueid, $filterparams = null) {
66
        parent::__construct($uniqueid);
67
 
68
        $this->set_attribute('class', 'reportlog generaltable generalbox table-sm');
69
        $this->filterparams = $filterparams;
70
        // Add course column if logs are displayed for site.
71
        $cols = array();
72
        $headers = array();
73
        if (empty($filterparams->courseid)) {
74
            $cols = array('course');
75
            $headers = array(get_string('course'));
76
        }
77
 
78
        $this->define_columns(array_merge($cols, array('time', 'fullnameuser', 'relatedfullnameuser', 'context', 'component',
79
                'eventname', 'description', 'origin', 'ip')));
80
        $this->define_headers(array_merge($headers, array(
81
                get_string('time'),
82
                get_string('fullnameuser'),
83
                get_string('eventrelatedfullnameuser', 'report_log'),
84
                get_string('eventcontext', 'report_log'),
85
                get_string('eventcomponent', 'report_log'),
86
                get_string('eventname'),
87
                get_string('description'),
88
                get_string('eventorigin', 'report_log'),
89
                get_string('ip_address')
90
                )
91
            ));
92
        $this->collapsible(false);
93
        $this->sortable(false);
94
        $this->pageable(true);
95
    }
96
 
97
    /**
98
     * Generate the course column.
99
     *
100
     * @deprecated since Moodle 2.9 MDL-48595 - please do not use this function any more.
101
     */
102
    public function col_course($event) {
103
        throw new coding_exception('col_course() can not be used any more, there is no such column.');
104
    }
105
 
106
    /**
107
     * Gets the user full name.
108
     *
109
     * This function is useful because, in the unlikely case that the user is
110
     * not already loaded in $this->userfullnames it will fetch it from db.
111
     *
112
     * @since Moodle 2.9
113
     * @param int $userid
114
     * @return string|false
115
     */
116
    protected function get_user_fullname($userid) {
117
        if (empty($userid)) {
118
            return false;
119
        }
120
 
121
        // Check if we already have this users' fullname.
122
        $userfullname = $this->userfullnames[$userid] ?? null;
123
        if (!empty($userfullname)) {
124
            return $userfullname;
125
        }
126
 
127
        // We already looked for the user and it does not exist.
128
        if ($userfullname === false) {
129
            return false;
130
        }
131
 
132
        // If we reach that point new users logs have been generated since the last users db query.
133
        $userfieldsapi = \core_user\fields::for_name();
134
        $fields = $userfieldsapi->get_sql('', false, '', '', false)->selects;
135
        if ($user = \core_user::get_user($userid, $fields)) {
136
            $this->userfullnames[$userid] = fullname($user, has_capability('moodle/site:viewfullnames', $this->get_context()));
137
        } else {
138
            $this->userfullnames[$userid] = false;
139
        }
140
 
141
        return $this->userfullnames[$userid];
142
    }
143
 
144
    /**
145
     * Generate the time column.
146
     *
147
     * @param stdClass $event event data.
148
     * @return string HTML for the time column
149
     */
150
    public function col_time($event) {
151
 
152
        if (empty($this->download)) {
153
            $dateformat = get_string('strftimedatetimeaccurate', 'core_langconfig');
154
        } else {
155
            $dateformat = get_string('strftimedatetimeshortaccurate', 'core_langconfig');
156
        }
157
        return userdate($event->timecreated, $dateformat);
158
    }
159
 
160
    /**
161
     * Generate the username column.
162
     *
163
     * @param \core\event\base $event event data.
164
     * @return string HTML for the username column
165
     */
166
    public function col_fullnameuser($event) {
167
        // Get extra event data for origin and realuserid.
168
        $logextra = $event->get_logextra();
169
 
170
        $eventusername = $event->userid ? $this->get_user_fullname($event->userid) : false;
171
 
172
        // Add username who did the action.
173
        if (!empty($logextra['realuserid'])) {
174
            $a = new stdClass();
175
            if (!$a->realusername = $this->get_user_fullname($logextra['realuserid'])) {
176
                $a->realusername = '-';
177
            }
178
            if (!$a->asusername = $eventusername) {
179
                $a->asusername = '-';
180
            }
181
            if (empty($this->download)) {
182
                $params = array('id' => $logextra['realuserid']);
183
                if ($event->courseid) {
184
                    $params['course'] = $event->courseid;
185
                }
186
                $a->realusername = html_writer::link(new moodle_url('/user/view.php', $params), $a->realusername);
187
                $params['id'] = $event->userid;
188
                $a->asusername = html_writer::link(new moodle_url('/user/view.php', $params), $a->asusername);
189
            }
190
            $username = get_string('eventloggedas', 'report_log', $a);
191
 
192
        } else if ($eventusername) {
193
            if (empty($this->download)) {
194
                $params = ['id' => $event->userid];
195
                if ($event->courseid) {
196
                    $params['course'] = $event->courseid;
197
                }
198
                $username = html_writer::link(new moodle_url('/user/view.php', $params), $eventusername);
199
            } else {
200
                $username = $eventusername;
201
            }
202
        } else {
203
            $username = '-';
204
        }
205
        return $username;
206
    }
207
 
208
    /**
209
     * Generate the related username column.
210
     *
211
     * @param stdClass $event event data.
212
     * @return string HTML for the related username column
213
     */
214
    public function col_relatedfullnameuser($event) {
215
        // Add affected user.
216
        if (!empty($event->relateduserid) && $username = $this->get_user_fullname($event->relateduserid)) {
217
            if (empty($this->download)) {
218
                $params = array('id' => $event->relateduserid);
219
                if ($event->courseid) {
220
                    $params['course'] = $event->courseid;
221
                }
222
                $username = html_writer::link(new moodle_url('/user/view.php', $params), $username);
223
            }
224
        } else {
225
            $username = '-';
226
        }
227
        return $username;
228
    }
229
 
230
    /**
231
     * Generate the context column.
232
     *
233
     * @param stdClass $event event data.
234
     * @return string HTML for the context column
235
     */
236
    public function col_context($event) {
237
        // Add context name.
238
        if ($event->contextid) {
239
            // If context name was fetched before then return, else get one.
240
            if (isset($this->contextname[$event->contextid])) {
241
                return $this->contextname[$event->contextid];
242
            } else {
243
                $context = context::instance_by_id($event->contextid, IGNORE_MISSING);
244
                if ($context) {
245
                    $contextname = $context->get_context_name(true);
246
                    if (empty($this->download) && $url = $context->get_url()) {
247
                        $contextname = html_writer::link($url, $contextname);
248
                    }
249
                } else {
250
                    $contextname = get_string('other');
251
                }
252
            }
253
        } else {
254
            $contextname = get_string('other');
255
        }
256
 
257
        $this->contextname[$event->contextid] = $contextname;
258
        return $contextname;
259
    }
260
 
261
    /**
262
     * Generate the component column.
263
     *
264
     * @param stdClass $event event data.
265
     * @return string HTML for the component column
266
     */
267
    public function col_component($event) {
268
        // Component.
269
        $componentname = $event->component;
270
        if (($event->component === 'core') || ($event->component === 'legacy')) {
271
            return  get_string('coresystem');
272
        } else if (get_string_manager()->string_exists('pluginname', $event->component)) {
273
            return get_string('pluginname', $event->component);
274
        } else {
275
            return $componentname;
276
        }
277
    }
278
 
279
    /**
280
     * Generate the event name column.
281
     *
282
     * @param stdClass $event event data.
283
     * @return string HTML for the event name column
284
     */
285
    public function col_eventname($event) {
286
        // Event name.
287
        $eventname = $event->get_name();
288
        // Only encode as an action link if we're not downloading.
289
        if (($url = $event->get_url()) && empty($this->download)) {
290
            $eventname = $this->action_link($url, $eventname, 'action');
291
        }
292
        return $eventname;
293
    }
294
 
295
    /**
296
     * Generate the description column.
297
     *
298
     * @param stdClass $event event data.
299
     * @return string HTML for the description column
300
     */
301
    public function col_description($event) {
302
        // Description.
303
        return format_text($event->get_description(), FORMAT_PLAIN);
304
    }
305
 
306
    /**
307
     * Generate the origin column.
308
     *
309
     * @param stdClass $event event data.
310
     * @return string HTML for the origin column
311
     */
312
    public function col_origin($event) {
313
        // Get extra event data for origin and realuserid.
314
        $logextra = $event->get_logextra();
315
 
316
        // Add event origin, normally IP/cron.
317
        return $logextra['origin'];
318
    }
319
 
320
    /**
321
     * Generate the ip column.
322
     *
323
     * @param stdClass $event event data.
324
     * @return string HTML for the ip column
325
     */
326
    public function col_ip($event) {
327
        // Get extra event data for origin and realuserid.
328
        $logextra = $event->get_logextra();
329
        $ip = $logextra['ip'];
330
 
331
        if (empty($this->download)) {
332
            $url = new moodle_url("/iplookup/index.php?popup=1&ip={$ip}&user={$event->userid}");
333
            $ip = $this->action_link($url, $ip, 'ip');
334
        }
335
        return $ip;
336
    }
337
 
338
    /**
339
     * Method to create a link with popup action.
340
     *
341
     * @param moodle_url $url The url to open.
342
     * @param string $text Anchor text for the link.
343
     * @param string $name Name of the popup window.
344
     *
345
     * @return string html to use.
346
     */
347
    protected function action_link(moodle_url $url, $text, $name = 'popup') {
348
        global $OUTPUT;
349
        $link = new action_link($url, $text, new popup_action('click', $url, $name, array('height' => 440, 'width' => 700)));
350
        return $OUTPUT->render($link);
351
    }
352
 
353
    /**
354
     * Helper function to get legacy crud action.
355
     *
356
     * @param string $crud crud action
357
     * @return string legacy action.
358
     */
359
    public function get_legacy_crud_action($crud) {
360
        $legacyactionmap = array('c' => 'add', 'r' => 'view', 'u' => 'update', 'd' => 'delete');
361
        if (array_key_exists($crud, $legacyactionmap)) {
362
            return $legacyactionmap[$crud];
363
        } else {
364
            // From old legacy log.
365
            return '-view';
366
        }
367
    }
368
 
369
    /**
370
     * Helper function which is used by build logs to get action sql and param.
371
     *
372
     * @return array sql and param for action.
373
     */
374
    public function get_action_sql() {
375
        global $DB;
376
 
377
        // In new logs we have a field to pick, and in legacy try get this from action.
378
        if (!empty($this->filterparams->action)) {
379
             list($sql, $params) = $DB->get_in_or_equal(str_split($this->filterparams->action),
380
                    SQL_PARAMS_NAMED, 'crud');
381
            $sql = "crud " . $sql;
382
        } else {
383
            // Add condition for all possible values of crud (to use db index).
384
            list($sql, $params) = $DB->get_in_or_equal(array('c', 'r', 'u', 'd'),
385
                    SQL_PARAMS_NAMED, 'crud');
386
            $sql = "crud ".$sql;
387
        }
388
        return array($sql, $params);
389
    }
390
 
391
    /**
392
     * Helper function which is used by build logs to get course module sql and param.
393
     *
394
     * @return array sql and param for action.
395
     */
396
    public function get_cm_sql() {
397
        $joins = array();
398
        $params = array();
399
 
400
        $joins[] = "contextinstanceid = :contextinstanceid";
401
        $joins[] = "contextlevel = :contextmodule";
402
        $params['contextinstanceid'] = $this->filterparams->modid;
403
        $params['contextmodule'] = CONTEXT_MODULE;
404
 
405
        $sql = implode(' AND ', $joins);
406
        return array($sql, $params);
407
    }
408
 
409
    /**
410
     * Query the reader. Store results in the object for use by build_table.
411
     *
412
     * @param int $pagesize size of page for paginated displayed table.
413
     * @param bool $useinitialsbar do you want to use the initials bar.
414
     */
415
    public function query_db($pagesize, $useinitialsbar = true) {
416
        global $DB, $USER;
417
 
418
        $joins = array();
419
        $params = array();
420
 
421
        // If we filter by userid and module id we also need to filter by crud and edulevel to ensure DB index is engaged.
422
        $useextendeddbindex = !empty($this->filterparams->userid) && !empty($this->filterparams->modid);
423
 
424
        if (!empty($this->filterparams->courseid) && $this->filterparams->courseid != SITEID) {
425
            $joins[] = "courseid = :courseid";
426
            $params['courseid'] = $this->filterparams->courseid;
427
        }
428
 
429
        if (!empty($this->filterparams->siteerrors)) {
430
            $joins[] = "( action='error' OR action='infected' OR action='failed' )";
431
        }
432
 
433
        if (!empty($this->filterparams->modid)) {
434
            list($actionsql, $actionparams) = $this->get_cm_sql();
435
            $joins[] = $actionsql;
436
            $params = array_merge($params, $actionparams);
437
        }
438
 
439
        if (!empty($this->filterparams->action) || $useextendeddbindex) {
440
            list($actionsql, $actionparams) = $this->get_action_sql();
441
            $joins[] = $actionsql;
442
            $params = array_merge($params, $actionparams);
443
        }
444
 
445
        // Getting all members of a group.
446
        [
447
            'joins' => $groupjoins,
448
            'params' => $groupparams,
449
            'useridfilter' => $this->lateuseridfilter,
450
        ] = report_helper::get_group_filter($this->filterparams);
451
 
452
        $joins = array_merge($joins, $groupjoins);
453
        $params = array_merge($params, $groupparams);
454
 
455
        if (!empty($this->filterparams->date)) {
456
            $joins[] = "timecreated > :date AND timecreated < :enddate";
457
            $params['date'] = $this->filterparams->date;
458
            $params['enddate'] = $this->filterparams->date + DAYSECS; // Show logs only for the selected date.
459
        }
460
 
461
        if (isset($this->filterparams->edulevel) && ($this->filterparams->edulevel >= 0)) {
462
            $joins[] = "edulevel = :edulevel";
463
            $params['edulevel'] = $this->filterparams->edulevel;
464
        } else if ($useextendeddbindex) {
465
            list($edulevelsql, $edulevelparams) = $DB->get_in_or_equal(array(\core\event\base::LEVEL_OTHER,
466
                \core\event\base::LEVEL_PARTICIPATING, \core\event\base::LEVEL_TEACHING), SQL_PARAMS_NAMED, 'edulevel');
467
            $joins[] = "edulevel ".$edulevelsql;
468
            $params = array_merge($params, $edulevelparams);
469
        }
470
 
471
        // Origin.
472
        if (isset($this->filterparams->origin) && ($this->filterparams->origin != '')) {
473
            if ($this->filterparams->origin !== '---') {
474
                // Filter by a single origin.
475
                $joins[] = "origin = :origin";
476
                $params['origin'] = $this->filterparams->origin;
477
            } else {
478
                // Filter by everything else.
479
                list($originsql, $originparams) = $DB->get_in_or_equal(array('cli', 'restore', 'ws', 'web'),
480
                    SQL_PARAMS_NAMED, 'origin', false);
481
                $joins[] = "origin " . $originsql;
482
                $params = array_merge($params, $originparams);
483
            }
484
        }
485
 
486
        // Filter out anonymous actions, this is N/A for legacy log because it never stores them.
487
        if ($this->filterparams->modid) {
488
            $context = context_module::instance($this->filterparams->modid);
489
        } else {
490
            $context = context_course::instance($this->filterparams->courseid);
491
        }
492
        if (!has_capability('moodle/site:viewanonymousevents', $context)) {
493
            $joins[] = "anonymous = 0";
494
        }
495
 
496
        $selector = implode(' AND ', $joins);
497
 
498
        if (!$this->is_downloading()) {
499
            $total = $this->filterparams->logreader->get_events_select_count($selector, $params);
500
            $this->pagesize($pagesize, $total);
501
        } else {
502
            $this->pageable(false);
503
        }
504
 
505
        // Get the events. Same query than before; even if it is not likely, logs from new users
506
        // may be added since last query so we will need to work around later to prevent problems.
507
        // In almost most of the cases this will be better than having two opened recordsets.
508
        $this->rawdata = new \CallbackFilterIterator(
509
            $this->filterparams->logreader->get_events_select_iterator(
510
                $selector,
511
                $params,
512
                $this->filterparams->orderby,
513
                $this->get_page_start(),
514
                $this->get_page_size(),
515
            ),
516
            function ($event) {
517
                if ($this->lateuseridfilter === null) {
518
                    return true;
519
                }
520
                return isset($this->lateuseridfilter[$event->userid]);
521
            },
522
        );
523
 
524
        // Set initial bars.
525
        if ($useinitialsbar && !$this->is_downloading()) {
526
            $this->initialbars($total > $pagesize);
527
        }
528
    }
529
 
530
    /**
531
     * Helper function to create list of course shortname and user fullname shown in log report.
532
     *
533
     * This will update $this->userfullnames and $this->courseshortnames array with userfullname and courseshortname (with link),
534
     * which will be used to render logs in table.
535
     *
536
     * @deprecated since Moodle 2.9 MDL-48595 - please do not use this function any more.
537
     */
538
    public function update_users_and_courses_used() {
539
        throw new coding_exception('update_users_and_courses_used() can not be used any more.');
540
    }
541
}