Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401]

   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   * Legacy log reader.
  19   * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead.
  20   * @todo  MDL-52805 This is to be removed in Moodle 3.10
  21   *
  22   * @package    logstore_legacy
  23   * @copyright  2013 Petr Skoda {@link http://skodak.org}
  24   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  25   */
  26  
  27  namespace logstore_legacy\log;
  28  
  29  defined('MOODLE_INTERNAL') || die();
  30  
  31  class store implements \tool_log\log\store, \core\log\sql_reader {
  32      use \tool_log\helper\store,
  33          \tool_log\helper\reader;
  34  
  35      /**
  36       * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead.
  37       * @todo  MDL-52805 This is to be removed in Moodle 3.10
  38       *
  39       * @param \tool_log\log\manager $manager
  40       */
  41      public function __construct(\tool_log\log\manager $manager) {
  42          $this->helper_setup($manager);
  43      }
  44  
  45      /** @var array list of db fields which needs to be replaced for legacy log query */
  46      protected static $standardtolegacyfields = array(
  47          'timecreated'       => 'time',
  48          'courseid'          => 'course',
  49          'contextinstanceid' => 'cmid',
  50          'origin'            => 'ip',
  51          'anonymous'         => 0,
  52      );
  53  
  54      /** @var string Regex to replace the crud params */
  55      const CRUD_REGEX = "/(crud).*?(<>|=|!=).*?'(.*?)'/s";
  56  
  57      /**
  58       * This method contains mapping required for Moodle core to make legacy store compatible with other sql_reader based
  59       * queries.
  60       *
  61       * @param string $selectwhere Select statment
  62       * @param array $params params for the sql
  63       * @param string $sort sort fields
  64       *
  65       * @return array returns an array containing the sql predicate, an array of params and sorting parameter.
  66       */
  67      protected static function replace_sql_legacy($selectwhere, array $params, $sort = '') {
  68          // Following mapping is done to make can_delete_course() compatible with legacy store.
  69          if ($selectwhere == "userid = :userid AND courseid = :courseid AND eventname = :eventname AND timecreated > :since" and
  70                  empty($sort)) {
  71              $replace = "module = 'course' AND action = 'new' AND userid = :userid AND url = :url AND time > :since";
  72              $params += array('url' => "view.php?id={$params['courseid']}");
  73              return array($replace, $params, $sort);
  74          }
  75  
  76          // Replace db field names to make it compatible with legacy log.
  77          foreach (self::$standardtolegacyfields as $from => $to) {
  78              $selectwhere = str_replace($from, $to, $selectwhere);
  79              if (!empty($sort)) {
  80                  $sort = str_replace($from, $to, $sort);
  81              }
  82              if (isset($params[$from])) {
  83                  $params[$to] = $params[$from];
  84                  unset($params[$from]);
  85              }
  86          }
  87  
  88          // Replace crud fields.
  89          $selectwhere = preg_replace_callback("/(crud).*?(<>|=|!=).*?'(.*?)'/s", 'self::replace_crud', $selectwhere);
  90  
  91          return array($selectwhere, $params, $sort);
  92      }
  93  
  94      /**
  95       * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead.
  96       * @todo MDL-52805 This will be removed in Moodle 3.10
  97       *
  98       * @param  string $selectwhere
  99       * @param  array  $params
 100       * @param  string $sort
 101       * @param  int    $limitfrom
 102       * @param  int    $limitnum
 103       * @return array
 104       */
 105      public function get_events_select($selectwhere, array $params, $sort, $limitfrom, $limitnum) {
 106          global $DB;
 107  
 108          $sort = self::tweak_sort_by_id($sort);
 109  
 110          // Replace the query with hardcoded mappings required for core.
 111          list($selectwhere, $params, $sort) = self::replace_sql_legacy($selectwhere, $params, $sort);
 112  
 113          $records = array();
 114  
 115          try {
 116              // A custom report + on the fly SQL rewriting = a possible exception.
 117              $records = $DB->get_recordset_select('log', $selectwhere, $params, $sort, '*', $limitfrom, $limitnum);
 118          } catch (\moodle_exception $ex) {
 119              debugging("error converting legacy event data " . $ex->getMessage() . $ex->debuginfo, DEBUG_DEVELOPER);
 120              return array();
 121          }
 122  
 123          $events = array();
 124  
 125          foreach ($records as $data) {
 126              $events[$data->id] = $this->get_log_event($data);
 127          }
 128  
 129          $records->close();
 130  
 131          return $events;
 132      }
 133  
 134      /**
 135       * Get whether events are present for the given select clause.
 136       * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead.
 137       *
 138       * @param string $selectwhere select conditions.
 139       * @param array $params params.
 140       *
 141       * @return bool Whether events available for the given conditions
 142       */
 143      public function get_events_select_exists(string $selectwhere, array $params): bool {
 144          global $DB;
 145  
 146          // Replace the query with hardcoded mappings required for core.
 147          list($selectwhere, $params) = self::replace_sql_legacy($selectwhere, $params);
 148  
 149          try {
 150              return $DB->record_exists_select('log', $selectwhere, $params);
 151          } catch (\moodle_exception $ex) {
 152              debugging("error converting legacy event data " . $ex->getMessage() . $ex->debuginfo, DEBUG_DEVELOPER);
 153              return false;
 154          }
 155      }
 156  
 157      /**
 158       * Fetch records using given criteria returning a Traversable object.
 159       * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead.
 160       * @todo MDL-52805 This will be removed in Moodle 3.10
 161       *
 162       * Note that the traversable object contains a moodle_recordset, so
 163       * remember that is important that you call close() once you finish
 164       * using it.
 165       *
 166       * @param string $selectwhere
 167       * @param array $params
 168       * @param string $sort
 169       * @param int $limitfrom
 170       * @param int $limitnum
 171       * @return \Traversable|\core\event\base[]
 172       */
 173      public function get_events_select_iterator($selectwhere, array $params, $sort, $limitfrom, $limitnum) {
 174          global $DB;
 175  
 176          $sort = self::tweak_sort_by_id($sort);
 177  
 178          // Replace the query with hardcoded mappings required for core.
 179          list($selectwhere, $params, $sort) = self::replace_sql_legacy($selectwhere, $params, $sort);
 180  
 181          try {
 182              $recordset = $DB->get_recordset_select('log', $selectwhere, $params, $sort, '*', $limitfrom, $limitnum);
 183          } catch (\moodle_exception $ex) {
 184              debugging("error converting legacy event data " . $ex->getMessage() . $ex->debuginfo, DEBUG_DEVELOPER);
 185              return new \EmptyIterator;
 186          }
 187  
 188          return new \core\dml\recordset_walk($recordset, array($this, 'get_log_event'));
 189      }
 190  
 191      /**
 192       * Returns an event from the log data.
 193       * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead.
 194       * @todo MDL-52805 This will be removed in Moodle 3.10
 195       *
 196       * @param stdClass $data Log data
 197       * @return \core\event\base
 198       */
 199      public function get_log_event($data) {
 200          return \logstore_legacy\event\legacy_logged::restore_legacy($data);
 201      }
 202  
 203      /**
 204       * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead.
 205       * @todo MDL-52805 This will be removed in Moodle 3.10
 206       *
 207       * @param  string $selectwhere
 208       * @param  array  $params
 209       * @return int
 210       */
 211      public function get_events_select_count($selectwhere, array $params) {
 212          global $DB;
 213  
 214          // Replace the query with hardcoded mappings required for core.
 215          list($selectwhere, $params) = self::replace_sql_legacy($selectwhere, $params);
 216  
 217          try {
 218              return $DB->count_records_select('log', $selectwhere, $params);
 219          } catch (\moodle_exception $ex) {
 220              debugging("error converting legacy event data " . $ex->getMessage() . $ex->debuginfo, DEBUG_DEVELOPER);
 221              return 0;
 222          }
 223      }
 224  
 225      /**
 226       * Are the new events appearing in the reader?
 227       * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead.
 228       * @todo MDL-52805 This will be removed in Moodle 3.10
 229       *
 230       * @return bool true means new log events are being added, false means no new data will be added
 231       */
 232      public function is_logging() {
 233          return (bool)$this->get_config('loglegacy', true);
 234      }
 235  
 236      /**
 237       * @deprecated since Moodle 3.6 MDL-52953 - Please use supported log stores such as "standard" or "external" instead.
 238       * @todo MDL-52805 This will be removed in Moodle 3.10
 239       */
 240      public function dispose() {
 241      }
 242  
 243      /**
 244       * Legacy add_to_log() code.
 245       * @deprecated since Moodle 3.1 MDL-45104 - Please use supported log stores such as "standard" or "external" instead.
 246       * @todo MDL-52805 This will be removed in Moodle 3.3
 247       *
 248       * @param    int $courseid The course id
 249       * @param    string $module The module name  e.g. forum, journal, resource, course, user etc
 250       * @param    string $action 'view', 'update', 'add' or 'delete', possibly followed by another word to clarify.
 251       * @param    string $url The file and parameters used to see the results of the action
 252       * @param    string $info Additional description information
 253       * @param    int $cm The course_module->id if there is one
 254       * @param    int|\stdClass $user If log regards $user other than $USER
 255       * @param    string $ip Override the IP, should only be used for restore.
 256       * @param    int $time Override the log time, should only be used for restore.
 257       */
 258      public function legacy_add_to_log($courseid, $module, $action, $url, $info, $cm, $user, $ip = null, $time = null) {
 259          // Note that this function intentionally does not follow the normal Moodle DB access idioms.
 260          // This is for a good reason: it is the most frequently used DB update function,
 261          // so it has been optimised for speed.
 262          global $DB, $CFG, $USER;
 263          if (!$this->is_logging()) {
 264              return;
 265          }
 266  
 267          if ($cm === '' || is_null($cm)) { // Postgres won't translate empty string to its default.
 268              $cm = 0;
 269          }
 270  
 271          if ($user) {
 272              $userid = $user;
 273          } else {
 274              if (\core\session\manager::is_loggedinas()) { // Don't log.
 275                  return;
 276              }
 277              $userid = empty($USER->id) ? '0' : $USER->id;
 278          }
 279  
 280          if (isset($CFG->logguests) and !$CFG->logguests) {
 281              if (!$userid or isguestuser($userid)) {
 282                  return;
 283              }
 284          }
 285  
 286          $remoteaddr = (is_null($ip)) ? getremoteaddr() : $ip;
 287  
 288          $timenow = (is_null($time)) ? time() : $time;
 289          if (!empty($url)) { // Could break doing html_entity_decode on an empty var.
 290              $url = html_entity_decode($url, ENT_QUOTES, 'UTF-8');
 291          } else {
 292              $url = '';
 293          }
 294  
 295          // Restrict length of log lines to the space actually available in the
 296          // database so that it doesn't cause a DB error. Log a warning so that
 297          // developers can avoid doing things which are likely to cause this on a
 298          // routine basis.
 299          if (\core_text::strlen($action) > 40) {
 300              $action = \core_text::substr($action, 0, 37) . '...';
 301              debugging('Warning: logged very long action', DEBUG_DEVELOPER);
 302          }
 303  
 304          if (!empty($info) && \core_text::strlen($info) > 255) {
 305              $info = \core_text::substr($info, 0, 252) . '...';
 306              debugging('Warning: logged very long info', DEBUG_DEVELOPER);
 307          }
 308  
 309          // If the 100 field size is changed, also need to alter print_log in course/lib.php.
 310          if (!empty($url) && \core_text::strlen($url) > 100) {
 311              $url = \core_text::substr($url, 0, 97) . '...';
 312              debugging('Warning: logged very long URL', DEBUG_DEVELOPER);
 313          }
 314  
 315          if (defined('MDL_PERFDB')) {
 316              global $PERF;
 317              $PERF->logwrites++;
 318          };
 319  
 320          $log = array('time' => $timenow, 'userid' => $userid, 'course' => $courseid, 'ip' => $remoteaddr,
 321                       'module' => $module, 'cmid' => $cm, 'action' => $action, 'url' => $url, 'info' => $info);
 322  
 323          try {
 324              $DB->insert_record_raw('log', $log, false);
 325          } catch (\dml_exception $e) {
 326              debugging('Error: Could not insert a new entry to the Moodle log. ' . $e->errorcode, DEBUG_ALL);
 327  
 328              // MDL-11893, alert $CFG->supportemail if insert into log failed.
 329              if ($CFG->supportemail and empty($CFG->noemailever)) {
 330                  // Function email_to_user is not usable because email_to_user tries to write to the logs table,
 331                  // and this will get caught in an infinite loop, if disk is full.
 332                  $site = get_site();
 333                  $subject = 'Insert into log failed at your moodle site ' . $site->fullname;
 334                  $message = "Insert into log table failed at " . date('l dS \of F Y h:i:s A') .
 335                      ".\n It is possible that your disk is full.\n\n";
 336                  $message .= "The failed query parameters are:\n\n" . var_export($log, true);
 337  
 338                  $lasttime = get_config('admin', 'lastloginserterrormail');
 339                  if (empty($lasttime) || time() - $lasttime > 60 * 60 * 24) { // Limit to 1 email per day.
 340                      // Using email directly rather than messaging as they may not be able to log in to access a message.
 341                      mail($CFG->supportemail, $subject, $message);
 342                      set_config('lastloginserterrormail', time(), 'admin');
 343                  }
 344              }
 345          }
 346      }
 347  
 348      /**
 349       * Generate a replace string for crud related sql conditions. This function is called as callback to preg_replace_callback()
 350       * on the actual sql.
 351       *
 352       * @param array $match matched string for the passed pattern
 353       *
 354       * @return string The sql string to use instead of original
 355       */
 356      protected static function replace_crud($match) {
 357          $return = '';
 358          unset($match[0]); // The first entry is the whole string.
 359          foreach ($match as $m) {
 360              // We hard code LIKE here because we are not worried about case sensitivity and want this to be fast.
 361              switch ($m) {
 362                  case 'crud' :
 363                      $replace = 'action';
 364                      break;
 365                  case 'c' :
 366                      switch ($match[2]) {
 367                          case '=' :
 368                              $replace = " LIKE '%add%'";
 369                              break;
 370                          case '!=' :
 371                          case '<>' :
 372                              $replace = " NOT LIKE '%add%'";
 373                              break;
 374                          default:
 375                              $replace = '';
 376                      }
 377                      break;
 378                  case 'r' :
 379                      switch ($match[2]) {
 380                          case '=' :
 381                              $replace = " LIKE '%view%' OR action LIKE '%report%'";
 382                              break;
 383                          case '!=' :
 384                          case '<>' :
 385                              $replace = " NOT LIKE '%view%' AND action NOT LIKE '%report%'";
 386                              break;
 387                          default:
 388                              $replace = '';
 389                      }
 390                      break;
 391                  case 'u' :
 392                      switch ($match[2]) {
 393                          case '=' :
 394                              $replace = " LIKE '%update%'";
 395                              break;
 396                          case '!=' :
 397                          case '<>' :
 398                              $replace = " NOT LIKE '%update%'";
 399                              break;
 400                          default:
 401                              $replace = '';
 402                      }
 403                      break;
 404                  case 'd' :
 405                      switch ($match[2]) {
 406                          case '=' :
 407                              $replace = " LIKE '%delete%'";
 408                              break;
 409                          case '!=' :
 410                          case '<>' :
 411                              $replace = " NOT LIKE '%delete%'";
 412                              break;
 413                          default:
 414                              $replace = '';
 415                      }
 416                      break;
 417                  default :
 418                      $replace = '';
 419              }
 420              $return .= $replace;
 421          }
 422          return $return;
 423      }
 424  }