Proyectos de Subversion Moodle

Rev

Rev 11 | | Comparar con el anterior | 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
 
11 efrain 25
use core\dataformat;
1 efrain 26
use core\report_helper;
27
 
28
defined('MOODLE_INTERNAL') || die;
11 efrain 29
 
1 efrain 30
global $CFG;
31
require_once($CFG->libdir . '/tablelib.php');
32
 
33
/**
34
 * Table log class for displaying logs.
35
 *
36
 * @package    report_log
37
 * @copyright  2014 Rajesh Taneja <rajesh.taneja@gmail.com>
38
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
39
 */
40
class report_log_table_log extends table_sql {
41
    /** @var array list of user fullnames shown in report */
42
    private $userfullnames = array();
43
 
44
    /** @var array list of context name shown in report */
45
    private $contextname = array();
46
 
47
    /** @var stdClass filters parameters */
48
    private $filterparams;
49
 
50
    /** @var int[] A list of users to filter by */
51
    private ?array $lateuseridfilter = null;
52
 
53
    /**
54
     * Sets up the table_log parameters.
55
     *
56
     * @param string $uniqueid unique id of form.
57
     * @param stdClass $filterparams (optional) filter params.
58
     *     - int courseid: id of course
59
     *     - int userid: user id
60
     *     - int|string modid: Module id or "site_errors" to view site errors
61
     *     - int groupid: Group id
62
     *     - \core\log\sql_reader logreader: reader from which data will be fetched.
63
     *     - int edulevel: educational level.
64
     *     - string action: view action
65
     *     - int date: Date from which logs to be viewed.
66
     */
67
    public function __construct($uniqueid, $filterparams = null) {
68
        parent::__construct($uniqueid);
69
 
70
        $this->set_attribute('class', 'reportlog generaltable generalbox table-sm');
71
        $this->filterparams = $filterparams;
72
        // Add course column if logs are displayed for site.
73
        $cols = array();
74
        $headers = array();
75
        if (empty($filterparams->courseid)) {
76
            $cols = array('course');
77
            $headers = array(get_string('course'));
78
        }
79
 
80
        $this->define_columns(array_merge($cols, array('time', 'fullnameuser', 'relatedfullnameuser', 'context', 'component',
81
                'eventname', 'description', 'origin', 'ip')));
82
        $this->define_headers(array_merge($headers, array(
83
                get_string('time'),
84
                get_string('fullnameuser'),
85
                get_string('eventrelatedfullnameuser', 'report_log'),
86
                get_string('eventcontext', 'report_log'),
87
                get_string('eventcomponent', 'report_log'),
88
                get_string('eventname'),
89
                get_string('description'),
90
                get_string('eventorigin', 'report_log'),
91
                get_string('ip_address')
92
                )
93
            ));
94
        $this->collapsible(false);
95
        $this->sortable(false);
96
        $this->pageable(true);
97
    }
98
 
99
    /**
100
     * Gets the user full name.
101
     *
102
     * This function is useful because, in the unlikely case that the user is
103
     * not already loaded in $this->userfullnames it will fetch it from db.
104
     *
105
     * @since Moodle 2.9
106
     * @param int $userid
107
     * @return string|false
108
     */
109
    protected function get_user_fullname($userid) {
110
        if (empty($userid)) {
111
            return false;
112
        }
113
 
114
        // Check if we already have this users' fullname.
115
        $userfullname = $this->userfullnames[$userid] ?? null;
116
        if (!empty($userfullname)) {
117
            return $userfullname;
118
        }
119
 
120
        // We already looked for the user and it does not exist.
121
        if ($userfullname === false) {
122
            return false;
123
        }
124
 
125
        // If we reach that point new users logs have been generated since the last users db query.
126
        $userfieldsapi = \core_user\fields::for_name();
127
        $fields = $userfieldsapi->get_sql('', false, '', '', false)->selects;
128
        if ($user = \core_user::get_user($userid, $fields)) {
129
            $this->userfullnames[$userid] = fullname($user, has_capability('moodle/site:viewfullnames', $this->get_context()));
130
        } else {
131
            $this->userfullnames[$userid] = false;
132
        }
133
 
134
        return $this->userfullnames[$userid];
135
    }
136
 
137
    /**
138
     * Generate the time column.
139
     *
140
     * @param stdClass $event event data.
141
     * @return string HTML for the time column
142
     */
143
    public function col_time($event) {
144
 
145
        if (empty($this->download)) {
146
            $dateformat = get_string('strftimedatetimeaccurate', 'core_langconfig');
147
        } else {
148
            $dateformat = get_string('strftimedatetimeshortaccurate', 'core_langconfig');
149
        }
150
        return userdate($event->timecreated, $dateformat);
151
    }
152
 
153
    /**
154
     * Generate the username column.
155
     *
156
     * @param \core\event\base $event event data.
157
     * @return string HTML for the username column
158
     */
159
    public function col_fullnameuser($event) {
160
        // Get extra event data for origin and realuserid.
161
        $logextra = $event->get_logextra();
162
 
163
        $eventusername = $event->userid ? $this->get_user_fullname($event->userid) : false;
164
 
165
        // Add username who did the action.
166
        if (!empty($logextra['realuserid'])) {
167
            $a = new stdClass();
168
            if (!$a->realusername = $this->get_user_fullname($logextra['realuserid'])) {
169
                $a->realusername = '-';
170
            }
171
            if (!$a->asusername = $eventusername) {
172
                $a->asusername = '-';
173
            }
174
            if (empty($this->download)) {
175
                $params = array('id' => $logextra['realuserid']);
176
                if ($event->courseid) {
177
                    $params['course'] = $event->courseid;
178
                }
179
                $a->realusername = html_writer::link(new moodle_url('/user/view.php', $params), $a->realusername);
180
                $params['id'] = $event->userid;
181
                $a->asusername = html_writer::link(new moodle_url('/user/view.php', $params), $a->asusername);
182
            }
183
            $username = get_string('eventloggedas', 'report_log', $a);
184
 
185
        } else if ($eventusername) {
186
            if (empty($this->download)) {
187
                $params = ['id' => $event->userid];
188
                if ($event->courseid) {
189
                    $params['course'] = $event->courseid;
190
                }
191
                $username = html_writer::link(new moodle_url('/user/view.php', $params), $eventusername);
192
            } else {
193
                $username = $eventusername;
194
            }
195
        } else {
196
            $username = '-';
197
        }
198
        return $username;
199
    }
200
 
201
    /**
202
     * Generate the related username column.
203
     *
204
     * @param stdClass $event event data.
205
     * @return string HTML for the related username column
206
     */
207
    public function col_relatedfullnameuser($event) {
208
        // Add affected user.
209
        if (!empty($event->relateduserid) && $username = $this->get_user_fullname($event->relateduserid)) {
210
            if (empty($this->download)) {
211
                $params = array('id' => $event->relateduserid);
212
                if ($event->courseid) {
213
                    $params['course'] = $event->courseid;
214
                }
215
                $username = html_writer::link(new moodle_url('/user/view.php', $params), $username);
216
            }
217
        } else {
218
            $username = '-';
219
        }
220
        return $username;
221
    }
222
 
223
    /**
224
     * Generate the context column.
225
     *
226
     * @param stdClass $event event data.
227
     * @return string HTML for the context column
228
     */
229
    public function col_context($event) {
230
        // Add context name.
231
        if ($event->contextid) {
232
            // If context name was fetched before then return, else get one.
233
            if (isset($this->contextname[$event->contextid])) {
234
                return $this->contextname[$event->contextid];
235
            } else {
236
                $context = context::instance_by_id($event->contextid, IGNORE_MISSING);
237
                if ($context) {
238
                    $contextname = $context->get_context_name(true);
239
                    if (empty($this->download) && $url = $context->get_url()) {
240
                        $contextname = html_writer::link($url, $contextname);
241
                    }
1441 ariadna 242
                } else if (!$contextname = \report_log\helper::get_context_fallback($event)) {
1 efrain 243
                    $contextname = get_string('other');
244
                }
245
            }
246
        } else {
247
            $contextname = get_string('other');
248
        }
249
 
250
        $this->contextname[$event->contextid] = $contextname;
251
        return $contextname;
252
    }
253
 
254
    /**
255
     * Generate the component column.
256
     *
257
     * @param stdClass $event event data.
258
     * @return string HTML for the component column
259
     */
260
    public function col_component($event) {
261
        // Component.
262
        $componentname = $event->component;
263
        if (($event->component === 'core') || ($event->component === 'legacy')) {
264
            return  get_string('coresystem');
265
        } else if (get_string_manager()->string_exists('pluginname', $event->component)) {
266
            return get_string('pluginname', $event->component);
267
        } else {
268
            return $componentname;
269
        }
270
    }
271
 
272
    /**
273
     * Generate the event name column.
274
     *
275
     * @param stdClass $event event data.
276
     * @return string HTML for the event name column
277
     */
278
    public function col_eventname($event) {
279
        // Event name.
280
        $eventname = $event->get_name();
281
        // Only encode as an action link if we're not downloading.
282
        if (($url = $event->get_url()) && empty($this->download)) {
283
            $eventname = $this->action_link($url, $eventname, 'action');
284
        }
285
        return $eventname;
286
    }
287
 
288
    /**
289
     * Generate the description column.
290
     *
291
     * @param stdClass $event event data.
292
     * @return string HTML for the description column
293
     */
294
    public function col_description($event) {
11 efrain 295
        if (empty($this->download) || dataformat::get_format_instance($this->download)->supports_html()) {
296
            return format_text($event->get_description(), FORMAT_PLAIN);
297
        } else {
298
            return $event->get_description();
299
        }
1 efrain 300
    }
301
 
302
    /**
303
     * Generate the origin column.
304
     *
305
     * @param stdClass $event event data.
306
     * @return string HTML for the origin column
307
     */
308
    public function col_origin($event) {
309
        // Get extra event data for origin and realuserid.
310
        $logextra = $event->get_logextra();
311
 
312
        // Add event origin, normally IP/cron.
313
        return $logextra['origin'];
314
    }
315
 
316
    /**
317
     * Generate the ip column.
318
     *
319
     * @param stdClass $event event data.
320
     * @return string HTML for the ip column
321
     */
322
    public function col_ip($event) {
323
        // Get extra event data for origin and realuserid.
324
        $logextra = $event->get_logextra();
325
        $ip = $logextra['ip'];
326
 
327
        if (empty($this->download)) {
328
            $url = new moodle_url("/iplookup/index.php?popup=1&ip={$ip}&user={$event->userid}");
329
            $ip = $this->action_link($url, $ip, 'ip');
330
        }
331
        return $ip;
332
    }
333
 
334
    /**
335
     * Method to create a link with popup action.
336
     *
337
     * @param moodle_url $url The url to open.
338
     * @param string $text Anchor text for the link.
339
     * @param string $name Name of the popup window.
340
     *
341
     * @return string html to use.
342
     */
343
    protected function action_link(moodle_url $url, $text, $name = 'popup') {
344
        global $OUTPUT;
345
        $link = new action_link($url, $text, new popup_action('click', $url, $name, array('height' => 440, 'width' => 700)));
346
        return $OUTPUT->render($link);
347
    }
348
 
349
    /**
350
     * Helper function to get legacy crud action.
351
     *
352
     * @param string $crud crud action
353
     * @return string legacy action.
354
     */
355
    public function get_legacy_crud_action($crud) {
356
        $legacyactionmap = array('c' => 'add', 'r' => 'view', 'u' => 'update', 'd' => 'delete');
357
        if (array_key_exists($crud, $legacyactionmap)) {
358
            return $legacyactionmap[$crud];
359
        } else {
360
            // From old legacy log.
361
            return '-view';
362
        }
363
    }
364
 
365
    /**
366
     * Helper function which is used by build logs to get action sql and param.
367
     *
368
     * @return array sql and param for action.
369
     */
370
    public function get_action_sql() {
371
        global $DB;
372
 
373
        // In new logs we have a field to pick, and in legacy try get this from action.
374
        if (!empty($this->filterparams->action)) {
375
             list($sql, $params) = $DB->get_in_or_equal(str_split($this->filterparams->action),
376
                    SQL_PARAMS_NAMED, 'crud');
377
            $sql = "crud " . $sql;
378
        } else {
379
            // Add condition for all possible values of crud (to use db index).
380
            list($sql, $params) = $DB->get_in_or_equal(array('c', 'r', 'u', 'd'),
381
                    SQL_PARAMS_NAMED, 'crud');
382
            $sql = "crud ".$sql;
383
        }
384
        return array($sql, $params);
385
    }
386
 
387
    /**
388
     * Helper function which is used by build logs to get course module sql and param.
389
     *
390
     * @return array sql and param for action.
391
     */
392
    public function get_cm_sql() {
393
        $joins = array();
394
        $params = array();
395
 
396
        $joins[] = "contextinstanceid = :contextinstanceid";
397
        $joins[] = "contextlevel = :contextmodule";
398
        $params['contextinstanceid'] = $this->filterparams->modid;
399
        $params['contextmodule'] = CONTEXT_MODULE;
400
 
401
        $sql = implode(' AND ', $joins);
402
        return array($sql, $params);
403
    }
404
 
405
    /**
406
     * Query the reader. Store results in the object for use by build_table.
407
     *
408
     * @param int $pagesize size of page for paginated displayed table.
409
     * @param bool $useinitialsbar do you want to use the initials bar.
410
     */
411
    public function query_db($pagesize, $useinitialsbar = true) {
412
        global $DB, $USER;
413
 
414
        $joins = array();
415
        $params = array();
416
 
417
        // If we filter by userid and module id we also need to filter by crud and edulevel to ensure DB index is engaged.
418
        $useextendeddbindex = !empty($this->filterparams->userid) && !empty($this->filterparams->modid);
419
 
420
        if (!empty($this->filterparams->courseid) && $this->filterparams->courseid != SITEID) {
421
            $joins[] = "courseid = :courseid";
422
            $params['courseid'] = $this->filterparams->courseid;
1441 ariadna 423
        } else if (!empty($this->filterparams->sitecoursefilter)) {
424
            // Add filters for missing/deleted courses in site context.
425
            $joins[] = "courseid = :courseid";
426
            $params['courseid'] = $this->filterparams->sitecoursefilter;
1 efrain 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
}