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