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