Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.
   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   * Observer class containing methods monitoring various events.
  19   *
  20   * @package    tool_monitor
  21   * @copyright  2014 onwards Ankit Agarwal <ankit.agrr@gmail.com>
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  namespace tool_monitor;
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  
  29  /**
  30   * Observer class containing methods monitoring various events.
  31   *
  32   * @since      Moodle 2.8
  33   * @package    tool_monitor
  34   * @copyright  2014 onwards Ankit Agarwal <ankit.agrr@gmail.com>
  35   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  36   */
  37  class eventobservers {
  38  
  39      /** @var array $buffer buffer of events. */
  40      protected $buffer = array();
  41  
  42      /** @var int Number of entries in the buffer. */
  43      protected $count = 0;
  44  
  45      /** @var  eventobservers a reference to a self instance. */
  46      protected static $instance;
  47  
  48      /**
  49       * Course delete event observer.
  50       * This observer monitors course delete event, and when a course is deleted it deletes any rules and subscriptions associated
  51       * with it, so no orphan data is left behind.
  52       *
  53       * @param \core\event\course_deleted $event The course deleted event.
  54       */
  55      public static function course_deleted(\core\event\course_deleted $event) {
  56          // Delete rules defined inside this course and associated subscriptions.
  57          $rules = rule_manager::get_rules_by_courseid($event->courseid, 0, 0, false);
  58          foreach ($rules as $rule) {
  59              rule_manager::delete_rule($rule->id, $event->get_context());
  60          }
  61          // Delete remaining subscriptions inside this course (from site-wide rules).
  62          subscription_manager::remove_all_subscriptions_in_course($event->get_context());
  63      }
  64  
  65      /**
  66       * The observer monitoring all the events.
  67       *
  68       * This observers puts small event objects in buffer for later writing to the database. At the end of the request the buffer
  69       * is cleaned up and all data dumped into the tool_monitor_events table.
  70       *
  71       * @param \core\event\base $event event object
  72       */
  73      public static function process_event(\core\event\base $event) {
  74          if (!get_config('tool_monitor', 'enablemonitor')) {
  75              return; // The tool is disabled. Nothing to do.
  76          }
  77  
  78          if (empty(self::$instance)) {
  79              self::$instance = new static();
  80              // Register shutdown handler - this is useful for buffering, processing events, etc.
  81              \core_shutdown_manager::register_function(array(self::$instance, 'process_buffer'));
  82          }
  83  
  84          self::$instance->buffer_event($event);
  85  
  86          if (PHPUNIT_TEST) {
  87              // Process buffer after every event when unit testing.
  88              self::$instance->process_buffer();
  89  
  90          }
  91      }
  92  
  93      /**
  94       * Api to buffer events to store, to reduce db queries.
  95       *
  96       * @param \core\event\base $event
  97       */
  98      protected function buffer_event(\core\event\base $event) {
  99  
 100          // If there are no subscriptions for this event do not buffer it.
 101          if (!\tool_monitor\subscription_manager::event_has_subscriptions($event->eventname, $event->courseid)) {
 102              return;
 103          }
 104  
 105          $eventdata = $event->get_data();
 106          $eventobj = new \stdClass();
 107          $eventobj->eventname = $eventdata['eventname'];
 108          $eventobj->contextid = $eventdata['contextid'];
 109          $eventobj->contextlevel = $eventdata['contextlevel'];
 110          $eventobj->contextinstanceid = $eventdata['contextinstanceid'];
 111          if ($event->get_url()) {
 112              // Get link url if exists.
 113              $eventobj->link = $event->get_url()->out();
 114          } else {
 115              $eventobj->link = '';
 116          }
 117          $eventobj->courseid = $eventdata['courseid'];
 118          $eventobj->timecreated = $eventdata['timecreated'];
 119  
 120          $this->buffer[] = $eventobj;
 121          $this->count++;
 122      }
 123  
 124      /**
 125       * This method process all events stored in the buffer.
 126       *
 127       * This is a multi purpose api. It does the following:-
 128       * 1. Write event data to tool_monitor_events
 129       * 2. Find out users that need to be notified about rule completion and schedule a task to send them messages.
 130       */
 131      public function process_buffer() {
 132          global $DB;
 133  
 134          $events = $this->flush(); // Flush data.
 135  
 136          $select = "SELECT COUNT(id) FROM {tool_monitor_events} ";
 137          $now = time();
 138          $messagestosend = array();
 139          $allsubids = array();
 140  
 141          // Let us now process the events and check for subscriptions.
 142          foreach ($events as $eventobj) {
 143              $subscriptions = subscription_manager::get_subscriptions_by_event($eventobj);
 144              $idstosend = array();
 145              foreach ($subscriptions as $subscription) {
 146                  // Only proceed to fire events and notifications if the subscription is active.
 147                  if (!subscription_manager::subscription_is_active($subscription)) {
 148                      continue;
 149                  }
 150                  $starttime = $now - $subscription->timewindow;
 151                  $starttime = ($starttime > $subscription->lastnotificationsent) ? $starttime : $subscription->lastnotificationsent;
 152                  if ($subscription->courseid == 0) {
 153                      // Site level subscription. Count all events.
 154                      $where = "eventname = :eventname AND timecreated >  :starttime";
 155                      $params = array('eventname' => $eventobj->eventname, 'starttime' => $starttime);
 156                  } else {
 157                      // Course level subscription.
 158                      if ($subscription->cmid == 0) {
 159                          // All modules.
 160                          $where = "eventname = :eventname AND courseid = :courseid AND timecreated > :starttime";
 161                          $params = array('eventname' => $eventobj->eventname, 'courseid' => $eventobj->courseid,
 162                                  'starttime' => $starttime);
 163                      } else {
 164                          // Specific module.
 165                          $where = "eventname = :eventname AND courseid = :courseid AND contextinstanceid = :cmid
 166                                  AND timecreated > :starttime";
 167                          $params = array('eventname' => $eventobj->eventname, 'courseid' => $eventobj->courseid,
 168                                  'cmid' => $eventobj->contextinstanceid, 'starttime' => $starttime);
 169  
 170                      }
 171                  }
 172                  $sql = $select . "WHERE " . $where;
 173                  $count = $DB->count_records_sql($sql, $params);
 174                  if (!empty($count) && $count >= $subscription->frequency) {
 175                      $idstosend[] = $subscription->id;
 176  
 177                      // Trigger a subscription_criteria_met event.
 178                      // It's possible that the course has been deleted since the criteria was met, so in that case use
 179                      // the system context. Set it here and change later if needed.
 180                      $context = \context_system::instance();
 181                      // We can't perform if (!empty($subscription->courseid)) below as it uses the magic method
 182                      // __get to return the variable, which will always result in being empty.
 183                      $courseid = $subscription->courseid;
 184                      if (!empty($courseid)) {
 185                          if ($coursecontext = \context_course::instance($courseid, IGNORE_MISSING)) {
 186                              $context = $coursecontext;
 187                          }
 188                      }
 189  
 190                      $params = array(
 191                          'userid' => $subscription->userid,
 192                          'courseid' => $subscription->courseid,
 193                          'context' => $context,
 194                          'other' => array(
 195                              'subscriptionid' => $subscription->id
 196                          )
 197                      );
 198                      $event = \tool_monitor\event\subscription_criteria_met::create($params);
 199                      $event->trigger();
 200                  }
 201              }
 202              if (!empty($idstosend)) {
 203                  $messagestosend[] = array('subscriptionids' => $idstosend, 'event' => $eventobj);
 204                  $allsubids = array_merge($allsubids, $idstosend);
 205              }
 206          }
 207  
 208          if (!empty($allsubids)) {
 209              // Update the last trigger flag.
 210              list($sql, $params) = $DB->get_in_or_equal($allsubids, SQL_PARAMS_NAMED);
 211              $params['now'] = $now;
 212              $sql = "UPDATE {tool_monitor_subscriptions} SET lastnotificationsent = :now WHERE id $sql";
 213              $DB->execute($sql, $params);
 214          }
 215  
 216          // Schedule a task to send notification.
 217          if (!empty($messagestosend)) {
 218              $adhocktask = new notification_task();
 219              $adhocktask->set_custom_data($messagestosend);
 220              $adhocktask->set_component('tool_monitor');
 221              \core\task\manager::queue_adhoc_task($adhocktask);
 222          }
 223      }
 224  
 225      /**
 226       * Protected method that flushes the buffer of events and writes them to the database.
 227       *
 228       * @return array a copy of the events buffer.
 229       */
 230      protected function flush() {
 231          global $DB;
 232  
 233          // Flush the buffer to the db.
 234          $events = $this->buffer;
 235          $DB->insert_records('tool_monitor_events', $events); // Insert the whole chunk into the database.
 236          $this->buffer = array();
 237          $this->count = 0;
 238          return $events;
 239      }
 240  
 241      /**
 242       * Observer that monitors user deleted event and delete user subscriptions.
 243       *
 244       * @param \core\event\user_deleted $event the event object.
 245       */
 246      public static function user_deleted(\core\event\user_deleted $event) {
 247          $userid = $event->objectid;
 248          subscription_manager::delete_user_subscriptions($userid);
 249      }
 250  
 251      /**
 252       * Observer that monitors course module deleted event and delete user subscriptions.
 253       *
 254       * @param \core\event\course_module_deleted $event the event object.
 255       */
 256      public static function course_module_deleted(\core\event\course_module_deleted $event) {
 257          $cmid = $event->contextinstanceid;
 258          subscription_manager::delete_cm_subscriptions($cmid);
 259      }
 260  }