| 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 | namespace core\event;
 | 
        
           |  |  | 18 |   | 
        
           |  |  | 19 | defined('MOODLE_INTERNAL') || die();
 | 
        
           |  |  | 20 |   | 
        
           |  |  | 21 | /**
 | 
        
           |  |  | 22 |  * Base event class.
 | 
        
           |  |  | 23 |  *
 | 
        
           |  |  | 24 |  * @package    core
 | 
        
           |  |  | 25 |  * @copyright  2013 Petr Skoda {@link http://skodak.org}
 | 
        
           |  |  | 26 |  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 | 
        
           |  |  | 27 |  */
 | 
        
           |  |  | 28 |   | 
        
           |  |  | 29 | /**
 | 
        
           |  |  | 30 |  * All other event classes must extend this class.
 | 
        
           |  |  | 31 |  *
 | 
        
           |  |  | 32 |  * @package    core
 | 
        
           |  |  | 33 |  * @since      Moodle 2.6
 | 
        
           |  |  | 34 |  * @copyright  2013 Petr Skoda {@link http://skodak.org}
 | 
        
           |  |  | 35 |  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 | 
        
           |  |  | 36 |  *
 | 
        
           |  |  | 37 |  * @property-read string $eventname Name of the event (=== class name with leading \)
 | 
        
           |  |  | 38 |  * @property-read string $component Full frankenstyle component name
 | 
        
           |  |  | 39 |  * @property-read string $action what happened
 | 
        
           |  |  | 40 |  * @property-read string $target what/who was target of the action
 | 
        
           |  |  | 41 |  * @property-read string $objecttable name of database table where is object record stored
 | 
        
           |  |  | 42 |  * @property-read int $objectid optional id of the object
 | 
        
           |  |  | 43 |  * @property-read string $crud letter indicating event type
 | 
        
           |  |  | 44 |  * @property-read int $edulevel log level (one of the constants LEVEL_)
 | 
        
           |  |  | 45 |  * @property-read int $contextid
 | 
        
           |  |  | 46 |  * @property-read int $contextlevel
 | 
        
           |  |  | 47 |  * @property-read int $contextinstanceid
 | 
        
           |  |  | 48 |  * @property-read int $userid who did this?
 | 
        
           |  |  | 49 |  * @property-read int $courseid the courseid of the event context, 0 for contexts above course
 | 
        
           |  |  | 50 |  * @property-read int $relateduserid
 | 
        
           |  |  | 51 |  * @property-read int $anonymous 1 means event should not be visible in reports, 0 means normal event,
 | 
        
           |  |  | 52 |  *                    create() argument may be also true/false.
 | 
        
           |  |  | 53 |  * @property-read mixed $other array or scalar, can not contain objects
 | 
        
           |  |  | 54 |  * @property-read int $timecreated
 | 
        
           |  |  | 55 |  */
 | 
        
           |  |  | 56 | abstract class base implements \IteratorAggregate {
 | 
        
           |  |  | 57 |   | 
        
           |  |  | 58 |     /**
 | 
        
           |  |  | 59 |      * Other level.
 | 
        
           |  |  | 60 |      */
 | 
        
           |  |  | 61 |     const LEVEL_OTHER = 0;
 | 
        
           |  |  | 62 |   | 
        
           |  |  | 63 |     /**
 | 
        
           |  |  | 64 |      * Teaching level.
 | 
        
           |  |  | 65 |      *
 | 
        
           |  |  | 66 |      * Any event that is performed by someone (typically a teacher) and has a teaching value,
 | 
        
           |  |  | 67 |      * anything that is affecting the learning experience/environment of the students.
 | 
        
           |  |  | 68 |      */
 | 
        
           |  |  | 69 |     const LEVEL_TEACHING = 1;
 | 
        
           |  |  | 70 |   | 
        
           |  |  | 71 |     /**
 | 
        
           |  |  | 72 |      * Participating level.
 | 
        
           |  |  | 73 |      *
 | 
        
           |  |  | 74 |      * Any event that is performed by a user, and is related (or could be related) to his learning experience.
 | 
        
           |  |  | 75 |      */
 | 
        
           |  |  | 76 |     const LEVEL_PARTICIPATING = 2;
 | 
        
           |  |  | 77 |   | 
        
           |  |  | 78 |     /**
 | 
        
           |  |  | 79 |      * The value used when an id can not be mapped during a restore.
 | 
        
           |  |  | 80 |      */
 | 
        
           |  |  | 81 |     const NOT_MAPPED = -31337;
 | 
        
           |  |  | 82 |   | 
        
           |  |  | 83 |     /**
 | 
        
           |  |  | 84 |      * The value used when an id can not be found during a restore.
 | 
        
           |  |  | 85 |      */
 | 
        
           |  |  | 86 |     const NOT_FOUND = -31338;
 | 
        
           |  |  | 87 |   | 
        
           |  |  | 88 |     /**
 | 
        
           |  |  | 89 |      * User id to use when the user is not logged in.
 | 
        
           |  |  | 90 |      */
 | 
        
           |  |  | 91 |     const USER_NOTLOGGEDIN = 0;
 | 
        
           |  |  | 92 |   | 
        
           |  |  | 93 |     /**
 | 
        
           |  |  | 94 |      * User id to use when actor is not an actual user but system, cli or cron.
 | 
        
           |  |  | 95 |      */
 | 
        
           |  |  | 96 |     const USER_OTHER = -1;
 | 
        
           |  |  | 97 |   | 
        
           |  |  | 98 |     /** @var array event data */
 | 
        
           |  |  | 99 |     protected $data;
 | 
        
           |  |  | 100 |   | 
        
           |  |  | 101 |     /** @var array the format is standardised by logging API */
 | 
        
           |  |  | 102 |     protected $logextra;
 | 
        
           |  |  | 103 |   | 
        
           |  |  | 104 |     /** @var \context of this event */
 | 
        
           |  |  | 105 |     protected $context;
 | 
        
           |  |  | 106 |   | 
        
           |  |  | 107 |     /**
 | 
        
           |  |  | 108 |      * @var bool indicates if event was already triggered,
 | 
        
           |  |  | 109 |      *           this prevents second attempt to trigger event.
 | 
        
           |  |  | 110 |      */
 | 
        
           |  |  | 111 |     private $triggered;
 | 
        
           |  |  | 112 |   | 
        
           |  |  | 113 |     /**
 | 
        
           |  |  | 114 |      * @var bool indicates if event was already dispatched,
 | 
        
           |  |  | 115 |      *           this prevents direct calling of manager::dispatch($event).
 | 
        
           |  |  | 116 |      */
 | 
        
           |  |  | 117 |     private $dispatched;
 | 
        
           |  |  | 118 |   | 
        
           |  |  | 119 |     /**
 | 
        
           |  |  | 120 |      * @var bool indicates if event was restored from storage,
 | 
        
           |  |  | 121 |      *           this prevents triggering of restored events.
 | 
        
           |  |  | 122 |      */
 | 
        
           |  |  | 123 |     private $restored;
 | 
        
           |  |  | 124 |   | 
        
           |  |  | 125 |     /** @var array list of event properties */
 | 
        
           |  |  | 126 |     private static $fields = array(
 | 
        
           |  |  | 127 |         'eventname', 'component', 'action', 'target', 'objecttable', 'objectid', 'crud', 'edulevel', 'contextid',
 | 
        
           |  |  | 128 |         'contextlevel', 'contextinstanceid', 'userid', 'courseid', 'relateduserid', 'anonymous', 'other',
 | 
        
           |  |  | 129 |         'timecreated');
 | 
        
           |  |  | 130 |   | 
        
           |  |  | 131 |     /** @var array simple record cache */
 | 
        
           |  |  | 132 |     private $recordsnapshots = array();
 | 
        
           |  |  | 133 |   | 
        
           |  |  | 134 |     /**
 | 
        
           |  |  | 135 |      * Private constructor, use create() or restore() methods instead.
 | 
        
           |  |  | 136 |      */
 | 
        
           |  |  | 137 |     final private function __construct() {
 | 
        
           |  |  | 138 |         $this->data = array_fill_keys(self::$fields, null);
 | 
        
           |  |  | 139 |   | 
        
           |  |  | 140 |         // Define some basic details.
 | 
        
           |  |  | 141 |         $classname = get_called_class();
 | 
        
           |  |  | 142 |         $parts = explode('\\', $classname);
 | 
        
           |  |  | 143 |         if (count($parts) !== 3 or $parts[1] !== 'event') {
 | 
        
           |  |  | 144 |             throw new \coding_exception("Invalid event class name '$classname', it must be defined in component\\event\\
 | 
        
           |  |  | 145 |                     namespace");
 | 
        
           |  |  | 146 |         }
 | 
        
           |  |  | 147 |         $this->data['eventname'] = '\\'.$classname;
 | 
        
           |  |  | 148 |         $this->data['component'] = $parts[0];
 | 
        
           |  |  | 149 |   | 
        
           |  |  | 150 |         $pos = strrpos($parts[2], '_');
 | 
        
           |  |  | 151 |         if ($pos === false) {
 | 
        
           |  |  | 152 |             throw new \coding_exception("Invalid event class name '$classname', there must be at least one underscore separating
 | 
        
           |  |  | 153 |                     object and action words");
 | 
        
           |  |  | 154 |         }
 | 
        
           |  |  | 155 |         $this->data['target'] = substr($parts[2], 0, $pos);
 | 
        
           |  |  | 156 |         $this->data['action'] = substr($parts[2], $pos + 1);
 | 
        
           |  |  | 157 |     }
 | 
        
           |  |  | 158 |   | 
        
           |  |  | 159 |     /**
 | 
        
           |  |  | 160 |      * Create new event.
 | 
        
           |  |  | 161 |      *
 | 
        
           |  |  | 162 |      * The optional data keys as:
 | 
        
           |  |  | 163 |      * 1/ objectid - the id of the object specified in class name
 | 
        
           |  |  | 164 |      * 2/ context - the context of this event
 | 
        
           |  |  | 165 |      * 3/ other - the other data describing the event, can not contain objects
 | 
        
           |  |  | 166 |      * 4/ relateduserid - the id of user which is somehow related to this event
 | 
        
           |  |  | 167 |      *
 | 
        
           |  |  | 168 |      * @param array $data
 | 
        
           |  |  | 169 |      * @return \core\event\base returns instance of new event
 | 
        
           |  |  | 170 |      *
 | 
        
           |  |  | 171 |      * @throws \coding_exception
 | 
        
           |  |  | 172 |      */
 | 
        
           | 1441 | ariadna | 173 |     final public static function create(?array $data = null) {
 | 
        
           | 1 | efrain | 174 |         global $USER, $CFG;
 | 
        
           |  |  | 175 |   | 
        
           |  |  | 176 |         $data = (array)$data;
 | 
        
           |  |  | 177 |   | 
        
           |  |  | 178 |         /** @var \core\event\base $event */
 | 
        
           |  |  | 179 |         $event = new static();
 | 
        
           |  |  | 180 |         $event->triggered = false;
 | 
        
           |  |  | 181 |         $event->restored = false;
 | 
        
           |  |  | 182 |         $event->dispatched = false;
 | 
        
           |  |  | 183 |   | 
        
           |  |  | 184 |         // By default all events are visible in logs.
 | 
        
           |  |  | 185 |         $event->data['anonymous'] = 0;
 | 
        
           |  |  | 186 |   | 
        
           |  |  | 187 |         // Set static event data specific for child class.
 | 
        
           |  |  | 188 |         $event->init();
 | 
        
           |  |  | 189 |   | 
        
           |  |  | 190 |         if (isset($event->data['level'])) {
 | 
        
           |  |  | 191 |             if (!isset($event->data['edulevel'])) {
 | 
        
           |  |  | 192 |                 debugging('level property is deprecated, use edulevel property instead', DEBUG_DEVELOPER);
 | 
        
           |  |  | 193 |                 $event->data['edulevel'] = $event->data['level'];
 | 
        
           |  |  | 194 |             }
 | 
        
           |  |  | 195 |             unset($event->data['level']);
 | 
        
           |  |  | 196 |         }
 | 
        
           |  |  | 197 |   | 
        
           |  |  | 198 |         // Set automatic data.
 | 
        
           |  |  | 199 |         $event->data['timecreated'] = time();
 | 
        
           |  |  | 200 |   | 
        
           |  |  | 201 |         // Set optional data or use defaults.
 | 
        
           |  |  | 202 |         $event->data['objectid'] = isset($data['objectid']) ? $data['objectid'] : null;
 | 
        
           |  |  | 203 |         $event->data['courseid'] = isset($data['courseid']) ? $data['courseid'] : null;
 | 
        
           |  |  | 204 |         $event->data['userid'] = isset($data['userid']) ? $data['userid'] : $USER->id;
 | 
        
           |  |  | 205 |         $event->data['other'] = isset($data['other']) ? $data['other'] : null;
 | 
        
           |  |  | 206 |         $event->data['relateduserid'] = isset($data['relateduserid']) ? $data['relateduserid'] : null;
 | 
        
           |  |  | 207 |         if (isset($data['anonymous'])) {
 | 
        
           |  |  | 208 |             $event->data['anonymous'] = $data['anonymous'];
 | 
        
           |  |  | 209 |         }
 | 
        
           |  |  | 210 |         $event->data['anonymous'] = (int)(bool)$event->data['anonymous'];
 | 
        
           |  |  | 211 |   | 
        
           |  |  | 212 |         if (isset($event->context)) {
 | 
        
           |  |  | 213 |             if (isset($data['context'])) {
 | 
        
           |  |  | 214 |                 debugging('Context was already set in init() method, ignoring context parameter', DEBUG_DEVELOPER);
 | 
        
           |  |  | 215 |             }
 | 
        
           |  |  | 216 |   | 
        
           |  |  | 217 |         } else if (!empty($data['context'])) {
 | 
        
           |  |  | 218 |             $event->context = $data['context'];
 | 
        
           |  |  | 219 |   | 
        
           |  |  | 220 |         } else if (!empty($data['contextid'])) {
 | 
        
           |  |  | 221 |             $event->context = \context::instance_by_id($data['contextid'], MUST_EXIST);
 | 
        
           |  |  | 222 |   | 
        
           |  |  | 223 |         } else {
 | 
        
           |  |  | 224 |             throw new \coding_exception('context (or contextid) is a required event property, system context may be hardcoded in init() method.');
 | 
        
           |  |  | 225 |         }
 | 
        
           |  |  | 226 |   | 
        
           |  |  | 227 |         $event->data['contextid'] = $event->context->id;
 | 
        
           |  |  | 228 |         $event->data['contextlevel'] = $event->context->contextlevel;
 | 
        
           |  |  | 229 |         $event->data['contextinstanceid'] = $event->context->instanceid;
 | 
        
           |  |  | 230 |   | 
        
           |  |  | 231 |         if (!isset($event->data['courseid'])) {
 | 
        
           |  |  | 232 |             if ($coursecontext = $event->context->get_course_context(false)) {
 | 
        
           |  |  | 233 |                 $event->data['courseid'] = $coursecontext->instanceid;
 | 
        
           |  |  | 234 |             } else {
 | 
        
           |  |  | 235 |                 $event->data['courseid'] = 0;
 | 
        
           |  |  | 236 |             }
 | 
        
           |  |  | 237 |         }
 | 
        
           |  |  | 238 |   | 
        
           |  |  | 239 |         if (!array_key_exists('relateduserid', $data) and $event->context->contextlevel == CONTEXT_USER) {
 | 
        
           |  |  | 240 |             $event->data['relateduserid'] = $event->context->instanceid;
 | 
        
           |  |  | 241 |         }
 | 
        
           |  |  | 242 |   | 
        
           |  |  | 243 |         // Warn developers if they do something wrong.
 | 
        
           |  |  | 244 |         if ($CFG->debugdeveloper) {
 | 
        
           |  |  | 245 |             static $automatickeys = array('eventname', 'component', 'action', 'target', 'contextlevel', 'contextinstanceid', 'timecreated');
 | 
        
           |  |  | 246 |             static $initkeys = array('crud', 'level', 'objecttable', 'edulevel');
 | 
        
           |  |  | 247 |   | 
        
           |  |  | 248 |             foreach ($data as $key => $ignored) {
 | 
        
           |  |  | 249 |                 if ($key === 'context') {
 | 
        
           |  |  | 250 |                     continue;
 | 
        
           |  |  | 251 |   | 
        
           |  |  | 252 |                 } else if (in_array($key, $automatickeys)) {
 | 
        
           |  |  | 253 |                     debugging("Data key '$key' is not allowed in \\core\\event\\base::create() method, it is set automatically", DEBUG_DEVELOPER);
 | 
        
           |  |  | 254 |   | 
        
           |  |  | 255 |                 } else if (in_array($key, $initkeys)) {
 | 
        
           |  |  | 256 |                     debugging("Data key '$key' is not allowed in \\core\\event\\base::create() method, you need to set it in init() method", DEBUG_DEVELOPER);
 | 
        
           |  |  | 257 |   | 
        
           |  |  | 258 |                 } else if (!in_array($key, self::$fields)) {
 | 
        
           |  |  | 259 |                     debugging("Data key '$key' does not exist in \\core\\event\\base");
 | 
        
           |  |  | 260 |                 }
 | 
        
           |  |  | 261 |             }
 | 
        
           |  |  | 262 |             $expectedcourseid = 0;
 | 
        
           |  |  | 263 |             if ($coursecontext = $event->context->get_course_context(false)) {
 | 
        
           |  |  | 264 |                 $expectedcourseid = $coursecontext->instanceid;
 | 
        
           |  |  | 265 |             }
 | 
        
           |  |  | 266 |             if ($expectedcourseid != $event->data['courseid']) {
 | 
        
           |  |  | 267 |                 debugging("Inconsistent courseid - context combination detected.", DEBUG_DEVELOPER);
 | 
        
           |  |  | 268 |             }
 | 
        
           |  |  | 269 |   | 
        
           |  |  | 270 |             if (method_exists($event, 'get_legacy_logdata') ||
 | 
        
           |  |  | 271 |                 method_exists($event, 'set_legacy_logdata') ||
 | 
        
           |  |  | 272 |                 method_exists($event, 'get_legacy_eventname') ||
 | 
        
           |  |  | 273 |                 method_exists($event, 'get_legacy_eventdata')
 | 
        
           |  |  | 274 |             ) {
 | 
        
           |  |  | 275 |                 debugging("Invalid event functions defined in " . $event->data['eventname'], DEBUG_DEVELOPER);
 | 
        
           |  |  | 276 |             }
 | 
        
           |  |  | 277 |   | 
        
           |  |  | 278 |         }
 | 
        
           |  |  | 279 |   | 
        
           |  |  | 280 |         // Let developers validate their custom data (such as $this->data['other'], contextlevel, etc.).
 | 
        
           |  |  | 281 |         $event->validate_data();
 | 
        
           |  |  | 282 |   | 
        
           |  |  | 283 |         return $event;
 | 
        
           |  |  | 284 |     }
 | 
        
           |  |  | 285 |   | 
        
           |  |  | 286 |     /**
 | 
        
           |  |  | 287 |      * Override in subclass.
 | 
        
           |  |  | 288 |      *
 | 
        
           |  |  | 289 |      * Set all required data properties:
 | 
        
           |  |  | 290 |      *  1/ crud - letter [crud]
 | 
        
           |  |  | 291 |      *  2/ edulevel - using a constant self::LEVEL_*.
 | 
        
           |  |  | 292 |      *  3/ objecttable - name of database table if objectid specified
 | 
        
           |  |  | 293 |      *
 | 
        
           |  |  | 294 |      * Optionally it can set:
 | 
        
           |  |  | 295 |      * a/ fixed system context
 | 
        
           |  |  | 296 |      *
 | 
        
           |  |  | 297 |      * @return void
 | 
        
           |  |  | 298 |      */
 | 
        
           |  |  | 299 |     abstract protected function init();
 | 
        
           |  |  | 300 |   | 
        
           |  |  | 301 |     /**
 | 
        
           |  |  | 302 |      * Let developers validate their custom data (such as $this->data['other'], contextlevel, etc.).
 | 
        
           |  |  | 303 |      *
 | 
        
           |  |  | 304 |      * Throw \coding_exception or debugging() notice in case of any problems.
 | 
        
           |  |  | 305 |      */
 | 
        
           |  |  | 306 |     protected function validate_data() {
 | 
        
           |  |  | 307 |         // Override if you want to validate event properties when
 | 
        
           |  |  | 308 |         // creating new events.
 | 
        
           |  |  | 309 |     }
 | 
        
           |  |  | 310 |   | 
        
           |  |  | 311 |     /**
 | 
        
           |  |  | 312 |      * Returns localised general event name.
 | 
        
           |  |  | 313 |      *
 | 
        
           |  |  | 314 |      * Override in subclass, we can not make it static and abstract at the same time.
 | 
        
           |  |  | 315 |      *
 | 
        
           |  |  | 316 |      * @return string
 | 
        
           |  |  | 317 |      */
 | 
        
           |  |  | 318 |     public static function get_name() {
 | 
        
           |  |  | 319 |         // Override in subclass with real lang string.
 | 
        
           |  |  | 320 |         $parts = explode('\\', get_called_class());
 | 
        
           |  |  | 321 |         if (count($parts) !== 3) {
 | 
        
           |  |  | 322 |             return get_string('unknownevent', 'error');
 | 
        
           |  |  | 323 |         }
 | 
        
           |  |  | 324 |         return $parts[0].': '.str_replace('_', ' ', $parts[2]);
 | 
        
           |  |  | 325 |     }
 | 
        
           |  |  | 326 |   | 
        
           |  |  | 327 |     /**
 | 
        
           |  |  | 328 |      * Returns the event name complete with metadata information.
 | 
        
           |  |  | 329 |      *
 | 
        
           |  |  | 330 |      * This includes information about whether the event has been deprecated so should not be used in all situations -
 | 
        
           |  |  | 331 |      * for example within reports themselves.
 | 
        
           |  |  | 332 |      *
 | 
        
           |  |  | 333 |      * If overriding this function, please ensure that you call the parent version too.
 | 
        
           |  |  | 334 |      *
 | 
        
           |  |  | 335 |      * @return string
 | 
        
           |  |  | 336 |      */
 | 
        
           |  |  | 337 |     public static function get_name_with_info() {
 | 
        
           |  |  | 338 |         $return = static::get_name();
 | 
        
           |  |  | 339 |   | 
        
           |  |  | 340 |         if (static::is_deprecated()) {
 | 
        
           |  |  | 341 |             $return = get_string('deprecatedeventname', 'core', $return);
 | 
        
           |  |  | 342 |         }
 | 
        
           |  |  | 343 |   | 
        
           |  |  | 344 |         return $return;
 | 
        
           |  |  | 345 |     }
 | 
        
           |  |  | 346 |   | 
        
           |  |  | 347 |     /**
 | 
        
           |  |  | 348 |      * Returns non-localised event description with id's for admin use only.
 | 
        
           |  |  | 349 |      *
 | 
        
           |  |  | 350 |      * @return string
 | 
        
           |  |  | 351 |      */
 | 
        
           |  |  | 352 |     public function get_description() {
 | 
        
           |  |  | 353 |         return null;
 | 
        
           |  |  | 354 |     }
 | 
        
           |  |  | 355 |   | 
        
           |  |  | 356 |     /**
 | 
        
           |  |  | 357 |      * This method was originally intended for granular
 | 
        
           |  |  | 358 |      * access control on the event level, unfortunately
 | 
        
           |  |  | 359 |      * the proper implementation would be too expensive
 | 
        
           |  |  | 360 |      * in many cases.
 | 
        
           |  |  | 361 |      *
 | 
        
           |  |  | 362 |      * @deprecated since 2.7
 | 
        
           |  |  | 363 |      *
 | 
        
           |  |  | 364 |      * @param int|\stdClass $user_or_id ID of the user.
 | 
        
           |  |  | 365 |      * @return bool True if the user can view the event, false otherwise.
 | 
        
           |  |  | 366 |      */
 | 
        
           |  |  | 367 |     public function can_view($user_or_id = null) {
 | 
        
           |  |  | 368 |         debugging('can_view() method is deprecated, use anonymous flag instead if necessary.', DEBUG_DEVELOPER);
 | 
        
           |  |  | 369 |         return is_siteadmin($user_or_id);
 | 
        
           |  |  | 370 |     }
 | 
        
           |  |  | 371 |   | 
        
           |  |  | 372 |     /**
 | 
        
           |  |  | 373 |      * Restore event from existing historic data.
 | 
        
           |  |  | 374 |      *
 | 
        
           |  |  | 375 |      * @param array $data
 | 
        
           |  |  | 376 |      * @param array $logextra the format is standardised by logging API
 | 
        
           |  |  | 377 |      * @return bool|\core\event\base
 | 
        
           |  |  | 378 |      */
 | 
        
           |  |  | 379 |     final public static function restore(array $data, array $logextra) {
 | 
        
           |  |  | 380 |         $classname = $data['eventname'];
 | 
        
           |  |  | 381 |         $component = $data['component'];
 | 
        
           |  |  | 382 |         $action = $data['action'];
 | 
        
           |  |  | 383 |         $target = $data['target'];
 | 
        
           |  |  | 384 |   | 
        
           |  |  | 385 |         // Security: make 100% sure this really is an event class.
 | 
        
           |  |  | 386 |         if ($classname !== "\\{$component}\\event\\{$target}_{$action}") {
 | 
        
           |  |  | 387 |             return false;
 | 
        
           |  |  | 388 |         }
 | 
        
           |  |  | 389 |   | 
        
           |  |  | 390 |         if (!class_exists($classname)) {
 | 
        
           |  |  | 391 |             return self::restore_unknown($data, $logextra);
 | 
        
           |  |  | 392 |         }
 | 
        
           |  |  | 393 |         $event = new $classname();
 | 
        
           |  |  | 394 |         if (!($event instanceof \core\event\base)) {
 | 
        
           |  |  | 395 |             return false;
 | 
        
           |  |  | 396 |         }
 | 
        
           |  |  | 397 |   | 
        
           |  |  | 398 |         $event->init(); // Init method of events could be setting custom properties.
 | 
        
           |  |  | 399 |         $event->restored = true;
 | 
        
           |  |  | 400 |         $event->triggered = true;
 | 
        
           |  |  | 401 |         $event->dispatched = true;
 | 
        
           |  |  | 402 |         $event->logextra = $logextra;
 | 
        
           |  |  | 403 |   | 
        
           |  |  | 404 |         foreach (self::$fields as $key) {
 | 
        
           |  |  | 405 |             if (!array_key_exists($key, $data)) {
 | 
        
           |  |  | 406 |                 debugging("Event restore data must contain key $key");
 | 
        
           |  |  | 407 |                 $data[$key] = null;
 | 
        
           |  |  | 408 |             }
 | 
        
           |  |  | 409 |         }
 | 
        
           |  |  | 410 |         if (count($data) != count(self::$fields)) {
 | 
        
           |  |  | 411 |             foreach ($data as $key => $value) {
 | 
        
           |  |  | 412 |                 if (!in_array($key, self::$fields)) {
 | 
        
           |  |  | 413 |                     debugging("Event restore data cannot contain key $key");
 | 
        
           |  |  | 414 |                     unset($data[$key]);
 | 
        
           |  |  | 415 |                 }
 | 
        
           |  |  | 416 |             }
 | 
        
           |  |  | 417 |         }
 | 
        
           |  |  | 418 |         $event->data = $data;
 | 
        
           |  |  | 419 |   | 
        
           |  |  | 420 |         return $event;
 | 
        
           |  |  | 421 |     }
 | 
        
           |  |  | 422 |   | 
        
           |  |  | 423 |     /**
 | 
        
           |  |  | 424 |      * Restore unknown event.
 | 
        
           |  |  | 425 |      *
 | 
        
           |  |  | 426 |      * @param array $data
 | 
        
           |  |  | 427 |      * @param array $logextra
 | 
        
           |  |  | 428 |      * @return unknown_logged
 | 
        
           |  |  | 429 |      */
 | 
        
           |  |  | 430 |     final protected static function restore_unknown(array $data, array $logextra) {
 | 
        
           |  |  | 431 |         $classname = '\core\event\unknown_logged';
 | 
        
           |  |  | 432 |   | 
        
           |  |  | 433 |         /** @var unknown_logged $event */
 | 
        
           |  |  | 434 |         $event = new $classname();
 | 
        
           |  |  | 435 |         $event->restored = true;
 | 
        
           |  |  | 436 |         $event->triggered = true;
 | 
        
           |  |  | 437 |         $event->dispatched = true;
 | 
        
           |  |  | 438 |         $event->data = $data;
 | 
        
           |  |  | 439 |         $event->logextra = $logextra;
 | 
        
           |  |  | 440 |   | 
        
           |  |  | 441 |         return $event;
 | 
        
           |  |  | 442 |     }
 | 
        
           |  |  | 443 |   | 
        
           |  |  | 444 |     /**
 | 
        
           |  |  | 445 |      * Create fake event from legacy log data.
 | 
        
           |  |  | 446 |      *
 | 
        
           |  |  | 447 |      * @param \stdClass $legacy
 | 
        
           |  |  | 448 |      * @return base
 | 
        
           |  |  | 449 |      */
 | 
        
           |  |  | 450 |     final public static function restore_legacy($legacy) {
 | 
        
           |  |  | 451 |         $classname = get_called_class();
 | 
        
           |  |  | 452 |         /** @var base $event */
 | 
        
           |  |  | 453 |         $event = new $classname();
 | 
        
           |  |  | 454 |         $event->restored = true;
 | 
        
           |  |  | 455 |         $event->triggered = true;
 | 
        
           |  |  | 456 |         $event->dispatched = true;
 | 
        
           |  |  | 457 |   | 
        
           |  |  | 458 |         $context = false;
 | 
        
           |  |  | 459 |         $component = 'legacy';
 | 
        
           |  |  | 460 |         if ($legacy->cmid) {
 | 
        
           |  |  | 461 |             $context = \context_module::instance($legacy->cmid, IGNORE_MISSING);
 | 
        
           |  |  | 462 |             $component = 'mod_'.$legacy->module;
 | 
        
           |  |  | 463 |         } else if ($legacy->course) {
 | 
        
           |  |  | 464 |             $context = \context_course::instance($legacy->course, IGNORE_MISSING);
 | 
        
           |  |  | 465 |         }
 | 
        
           |  |  | 466 |         if (!$context) {
 | 
        
           |  |  | 467 |             $context = \context_system::instance();
 | 
        
           |  |  | 468 |         }
 | 
        
           |  |  | 469 |   | 
        
           |  |  | 470 |         $event->data = array();
 | 
        
           |  |  | 471 |   | 
        
           |  |  | 472 |         $event->data['eventname'] = $legacy->module.'_'.$legacy->action;
 | 
        
           |  |  | 473 |         $event->data['component'] = $component;
 | 
        
           |  |  | 474 |         $event->data['action'] = $legacy->action;
 | 
        
           |  |  | 475 |         $event->data['target'] = null;
 | 
        
           |  |  | 476 |         $event->data['objecttable'] = null;
 | 
        
           |  |  | 477 |         $event->data['objectid'] = null;
 | 
        
           |  |  | 478 |         if (strpos($legacy->action, 'view') !== false) {
 | 
        
           |  |  | 479 |             $event->data['crud'] = 'r';
 | 
        
           |  |  | 480 |         } else if (strpos($legacy->action, 'print') !== false) {
 | 
        
           |  |  | 481 |             $event->data['crud'] = 'r';
 | 
        
           |  |  | 482 |         } else if (strpos($legacy->action, 'update') !== false) {
 | 
        
           |  |  | 483 |             $event->data['crud'] = 'u';
 | 
        
           |  |  | 484 |         } else if (strpos($legacy->action, 'hide') !== false) {
 | 
        
           |  |  | 485 |             $event->data['crud'] = 'u';
 | 
        
           |  |  | 486 |         } else if (strpos($legacy->action, 'move') !== false) {
 | 
        
           |  |  | 487 |             $event->data['crud'] = 'u';
 | 
        
           |  |  | 488 |         } else if (strpos($legacy->action, 'write') !== false) {
 | 
        
           |  |  | 489 |             $event->data['crud'] = 'u';
 | 
        
           |  |  | 490 |         } else if (strpos($legacy->action, 'tag') !== false) {
 | 
        
           |  |  | 491 |             $event->data['crud'] = 'u';
 | 
        
           |  |  | 492 |         } else if (strpos($legacy->action, 'remove') !== false) {
 | 
        
           |  |  | 493 |             $event->data['crud'] = 'u';
 | 
        
           |  |  | 494 |         } else if (strpos($legacy->action, 'delete') !== false) {
 | 
        
           |  |  | 495 |             $event->data['crud'] = 'p';
 | 
        
           |  |  | 496 |         } else if (strpos($legacy->action, 'create') !== false) {
 | 
        
           |  |  | 497 |             $event->data['crud'] = 'c';
 | 
        
           |  |  | 498 |         } else if (strpos($legacy->action, 'post') !== false) {
 | 
        
           |  |  | 499 |             $event->data['crud'] = 'c';
 | 
        
           |  |  | 500 |         } else if (strpos($legacy->action, 'add') !== false) {
 | 
        
           |  |  | 501 |             $event->data['crud'] = 'c';
 | 
        
           |  |  | 502 |         } else {
 | 
        
           |  |  | 503 |             // End of guessing...
 | 
        
           |  |  | 504 |             $event->data['crud'] = 'r';
 | 
        
           |  |  | 505 |         }
 | 
        
           |  |  | 506 |         $event->data['edulevel'] = $event::LEVEL_OTHER;
 | 
        
           |  |  | 507 |         $event->data['contextid'] = $context->id;
 | 
        
           |  |  | 508 |         $event->data['contextlevel'] = $context->contextlevel;
 | 
        
           |  |  | 509 |         $event->data['contextinstanceid'] = $context->instanceid;
 | 
        
           |  |  | 510 |         $event->data['userid'] = ($legacy->userid ? $legacy->userid : null);
 | 
        
           |  |  | 511 |         $event->data['courseid'] = ($legacy->course ? $legacy->course : null);
 | 
        
           |  |  | 512 |         $event->data['relateduserid'] = ($legacy->userid ? $legacy->userid : null);
 | 
        
           |  |  | 513 |         $event->data['timecreated'] = $legacy->time;
 | 
        
           |  |  | 514 |   | 
        
           |  |  | 515 |         $event->logextra = array();
 | 
        
           |  |  | 516 |         if ($legacy->ip) {
 | 
        
           |  |  | 517 |             $event->logextra['origin'] = 'web';
 | 
        
           |  |  | 518 |             $event->logextra['ip'] = $legacy->ip;
 | 
        
           |  |  | 519 |         } else {
 | 
        
           |  |  | 520 |             $event->logextra['origin'] = 'cli';
 | 
        
           |  |  | 521 |             $event->logextra['ip'] = null;
 | 
        
           |  |  | 522 |         }
 | 
        
           |  |  | 523 |         $event->logextra['realuserid'] = null;
 | 
        
           |  |  | 524 |   | 
        
           |  |  | 525 |         $event->data['other'] = (array)$legacy;
 | 
        
           |  |  | 526 |   | 
        
           |  |  | 527 |         return $event;
 | 
        
           |  |  | 528 |     }
 | 
        
           |  |  | 529 |   | 
        
           |  |  | 530 |     /**
 | 
        
           |  |  | 531 |      * This is used when restoring course logs where it is required that we
 | 
        
           |  |  | 532 |      * map the objectid to it's new value in the new course.
 | 
        
           |  |  | 533 |      *
 | 
        
           |  |  | 534 |      * Does nothing in the base class except display a debugging message warning
 | 
        
           |  |  | 535 |      * the user that the event does not contain the required functionality to
 | 
        
           |  |  | 536 |      * map this information. For events that do not store an objectid this won't
 | 
        
           |  |  | 537 |      * be called, so no debugging message will be displayed.
 | 
        
           |  |  | 538 |      *
 | 
        
           |  |  | 539 |      * Example of usage:
 | 
        
           |  |  | 540 |      *
 | 
        
           |  |  | 541 |      * return array('db' => 'assign_submissions', 'restore' => 'submission');
 | 
        
           |  |  | 542 |      *
 | 
        
           |  |  | 543 |      * If the objectid can not be mapped during restore set the value to \core\event\base::NOT_MAPPED, example -
 | 
        
           |  |  | 544 |      *
 | 
        
           |  |  | 545 |      * return array('db' => 'some_table', 'restore' => \core\event\base::NOT_MAPPED);
 | 
        
           |  |  | 546 |      *
 | 
        
           |  |  | 547 |      * Note - it isn't necessary to specify the 'db' and 'restore' values in this case, so you can also use -
 | 
        
           |  |  | 548 |      *
 | 
        
           |  |  | 549 |      * return \core\event\base::NOT_MAPPED;
 | 
        
           |  |  | 550 |      *
 | 
        
           |  |  | 551 |      * The 'db' key refers to the database table and the 'restore' key refers to
 | 
        
           |  |  | 552 |      * the name of the restore element the objectid is associated with. In many
 | 
        
           |  |  | 553 |      * cases these will be the same.
 | 
        
           |  |  | 554 |      *
 | 
        
           |  |  | 555 |      * @return string the name of the restore mapping the objectid links to
 | 
        
           |  |  | 556 |      */
 | 
        
           |  |  | 557 |     public static function get_objectid_mapping() {
 | 
        
           |  |  | 558 |         debugging('In order to restore course logs accurately the event "' . get_called_class() . '" must define the
 | 
        
           |  |  | 559 |             function get_objectid_mapping().', DEBUG_DEVELOPER);
 | 
        
           |  |  | 560 |   | 
        
           |  |  | 561 |         return false;
 | 
        
           |  |  | 562 |     }
 | 
        
           |  |  | 563 |   | 
        
           |  |  | 564 |     /**
 | 
        
           |  |  | 565 |      * This is used when restoring course logs where it is required that we
 | 
        
           |  |  | 566 |      * map the information in 'other' to it's new value in the new course.
 | 
        
           |  |  | 567 |      *
 | 
        
           |  |  | 568 |      * Does nothing in the base class except display a debugging message warning
 | 
        
           |  |  | 569 |      * the user that the event does not contain the required functionality to
 | 
        
           |  |  | 570 |      * map this information. For events that do not store any other information this
 | 
        
           |  |  | 571 |      * won't be called, so no debugging message will be displayed.
 | 
        
           |  |  | 572 |      *
 | 
        
           |  |  | 573 |      * Example of usage:
 | 
        
           |  |  | 574 |      *
 | 
        
           |  |  | 575 |      * $othermapped = array();
 | 
        
           |  |  | 576 |      * $othermapped['discussionid'] = array('db' => 'forum_discussions', 'restore' => 'forum_discussion');
 | 
        
           |  |  | 577 |      * $othermapped['forumid'] = array('db' => 'forum', 'restore' => 'forum');
 | 
        
           |  |  | 578 |      * return $othermapped;
 | 
        
           |  |  | 579 |      *
 | 
        
           |  |  | 580 |      * If an id can not be mapped during restore we set it to \core\event\base::NOT_MAPPED, example -
 | 
        
           |  |  | 581 |      *
 | 
        
           |  |  | 582 |      * $othermapped = array();
 | 
        
           |  |  | 583 |      * $othermapped['someid'] = array('db' => 'some_table', 'restore' => \core\event\base::NOT_MAPPED);
 | 
        
           |  |  | 584 |      * return $othermapped;
 | 
        
           |  |  | 585 |      *
 | 
        
           |  |  | 586 |      * Note - it isn't necessary to specify the 'db' and 'restore' values in this case, so you can also use -
 | 
        
           |  |  | 587 |      *
 | 
        
           |  |  | 588 |      * $othermapped = array();
 | 
        
           |  |  | 589 |      * $othermapped['someid'] = \core\event\base::NOT_MAPPED;
 | 
        
           |  |  | 590 |      * return $othermapped;
 | 
        
           |  |  | 591 |      *
 | 
        
           |  |  | 592 |      * The 'db' key refers to the database table and the 'restore' key refers to
 | 
        
           |  |  | 593 |      * the name of the restore element the other value is associated with. In many
 | 
        
           |  |  | 594 |      * cases these will be the same.
 | 
        
           |  |  | 595 |      *
 | 
        
           |  |  | 596 |      * @return array an array of other values and their corresponding mapping
 | 
        
           |  |  | 597 |      */
 | 
        
           |  |  | 598 |     public static function get_other_mapping() {
 | 
        
           |  |  | 599 |         debugging('In order to restore course logs accurately the event "' . get_called_class() . '" must define the
 | 
        
           |  |  | 600 |             function get_other_mapping().', DEBUG_DEVELOPER);
 | 
        
           |  |  | 601 |     }
 | 
        
           |  |  | 602 |   | 
        
           |  |  | 603 |     /**
 | 
        
           |  |  | 604 |      * Get static information about an event.
 | 
        
           |  |  | 605 |      * This is used in reports and is not for general use.
 | 
        
           |  |  | 606 |      *
 | 
        
           |  |  | 607 |      * @return array Static information about the event.
 | 
        
           |  |  | 608 |      */
 | 
        
           |  |  | 609 |     final public static function get_static_info() {
 | 
        
           |  |  | 610 |         /** Var \core\event\base $event. */
 | 
        
           |  |  | 611 |         $event = new static();
 | 
        
           |  |  | 612 |         // Set static event data specific for child class.
 | 
        
           |  |  | 613 |         $event->init();
 | 
        
           |  |  | 614 |         return array(
 | 
        
           |  |  | 615 |             'eventname' => $event->data['eventname'],
 | 
        
           |  |  | 616 |             'component' => $event->data['component'],
 | 
        
           |  |  | 617 |             'target' => $event->data['target'],
 | 
        
           |  |  | 618 |             'action' => $event->data['action'],
 | 
        
           |  |  | 619 |             'crud' => $event->data['crud'],
 | 
        
           |  |  | 620 |             'edulevel' => $event->data['edulevel'],
 | 
        
           |  |  | 621 |             'objecttable' => $event->data['objecttable'],
 | 
        
           |  |  | 622 |         );
 | 
        
           |  |  | 623 |     }
 | 
        
           |  |  | 624 |   | 
        
           |  |  | 625 |     /**
 | 
        
           |  |  | 626 |      * Get an explanation of what the class does.
 | 
        
           |  |  | 627 |      * By default returns the phpdocs from the child event class. Ideally this should
 | 
        
           |  |  | 628 |      * be overridden to return a translatable get_string style markdown.
 | 
        
           |  |  | 629 |      * e.g. return new lang_string('eventyourspecialevent', 'plugin_type');
 | 
        
           |  |  | 630 |      *
 | 
        
           |  |  | 631 |      * @return string An explanation of the event formatted in markdown style.
 | 
        
           |  |  | 632 |      */
 | 
        
           |  |  | 633 |     public static function get_explanation() {
 | 
        
           |  |  | 634 |         $ref = new \ReflectionClass(get_called_class());
 | 
        
           |  |  | 635 |         $docblock = $ref->getDocComment();
 | 
        
           |  |  | 636 |   | 
        
           |  |  | 637 |         // Check that there is something to work on.
 | 
        
           |  |  | 638 |         if (empty($docblock)) {
 | 
        
           |  |  | 639 |             return null;
 | 
        
           |  |  | 640 |         }
 | 
        
           |  |  | 641 |   | 
        
           |  |  | 642 |         $docblocklines = explode("\n", $docblock);
 | 
        
           |  |  | 643 |         // Remove the bulk of the comment characters.
 | 
        
           |  |  | 644 |         $pattern = "/(^\s*\/\*\*|^\s+\*\s|^\s+\*)/";
 | 
        
           |  |  | 645 |         $cleanline = array();
 | 
        
           |  |  | 646 |         foreach ($docblocklines as $line) {
 | 
        
           |  |  | 647 |             $templine = preg_replace($pattern, '', $line);
 | 
        
           |  |  | 648 |             // If there is nothing on the line then don't add it to the array.
 | 
        
           |  |  | 649 |             if (!empty($templine)) {
 | 
        
           |  |  | 650 |                 $cleanline[] = rtrim($templine);
 | 
        
           |  |  | 651 |             }
 | 
        
           |  |  | 652 |             // If we get to a line starting with an @ symbol then we don't want the rest.
 | 
        
           |  |  | 653 |             if (preg_match("/^@|\//", $templine)) {
 | 
        
           |  |  | 654 |                 // Get rid of the last entry (it contains an @ symbol).
 | 
        
           |  |  | 655 |                 array_pop($cleanline);
 | 
        
           |  |  | 656 |                 // Break out of this foreach loop.
 | 
        
           |  |  | 657 |                 break;
 | 
        
           |  |  | 658 |             }
 | 
        
           |  |  | 659 |         }
 | 
        
           |  |  | 660 |         // Add a line break to the sanitised lines.
 | 
        
           |  |  | 661 |         $explanation = implode("\n", $cleanline);
 | 
        
           |  |  | 662 |   | 
        
           |  |  | 663 |         return $explanation;
 | 
        
           |  |  | 664 |     }
 | 
        
           |  |  | 665 |   | 
        
           |  |  | 666 |     /**
 | 
        
           |  |  | 667 |      * Returns event context.
 | 
        
           |  |  | 668 |      * @return \context
 | 
        
           |  |  | 669 |      */
 | 
        
           |  |  | 670 |     public function get_context() {
 | 
        
           |  |  | 671 |         if (isset($this->context)) {
 | 
        
           |  |  | 672 |             return $this->context;
 | 
        
           |  |  | 673 |         }
 | 
        
           |  |  | 674 |         $this->context = \context::instance_by_id($this->data['contextid'], IGNORE_MISSING);
 | 
        
           |  |  | 675 |         return $this->context;
 | 
        
           |  |  | 676 |     }
 | 
        
           |  |  | 677 |   | 
        
           |  |  | 678 |     /**
 | 
        
           |  |  | 679 |      * Returns relevant URL, override in subclasses.
 | 
        
           |  |  | 680 |      * @return \moodle_url
 | 
        
           |  |  | 681 |      */
 | 
        
           |  |  | 682 |     public function get_url() {
 | 
        
           |  |  | 683 |         return null;
 | 
        
           |  |  | 684 |     }
 | 
        
           |  |  | 685 |   | 
        
           |  |  | 686 |     /**
 | 
        
           |  |  | 687 |      * Return standardised event data as array.
 | 
        
           |  |  | 688 |      *
 | 
        
           |  |  | 689 |      * @return array All elements are scalars except the 'other' field which is array.
 | 
        
           |  |  | 690 |      */
 | 
        
           |  |  | 691 |     public function get_data() {
 | 
        
           |  |  | 692 |         return $this->data;
 | 
        
           |  |  | 693 |     }
 | 
        
           |  |  | 694 |   | 
        
           |  |  | 695 |     /**
 | 
        
           |  |  | 696 |      * Return auxiliary data that was stored in logs.
 | 
        
           |  |  | 697 |      *
 | 
        
           |  |  | 698 |      * List of standard properties:
 | 
        
           |  |  | 699 |      *  - origin: IP number, cli,cron
 | 
        
           |  |  | 700 |      *  - realuserid: id of the user when logged-in-as
 | 
        
           |  |  | 701 |      *
 | 
        
           |  |  | 702 |      * @return array the format is standardised by logging API
 | 
        
           |  |  | 703 |      */
 | 
        
           |  |  | 704 |     public function get_logextra() {
 | 
        
           |  |  | 705 |         return $this->logextra;
 | 
        
           |  |  | 706 |     }
 | 
        
           |  |  | 707 |   | 
        
           |  |  | 708 |     /**
 | 
        
           |  |  | 709 |      * Validate all properties right before triggering the event.
 | 
        
           |  |  | 710 |      *
 | 
        
           |  |  | 711 |      * This throws coding exceptions for fatal problems and debugging for minor problems.
 | 
        
           |  |  | 712 |      *
 | 
        
           |  |  | 713 |      * @throws \coding_exception
 | 
        
           |  |  | 714 |      */
 | 
        
           |  |  | 715 |     protected function validate_before_trigger() {
 | 
        
           |  |  | 716 |         global $DB, $CFG;
 | 
        
           |  |  | 717 |   | 
        
           |  |  | 718 |         if (empty($this->data['crud'])) {
 | 
        
           |  |  | 719 |             throw new \coding_exception('crud must be specified in init() method of each method');
 | 
        
           |  |  | 720 |         }
 | 
        
           |  |  | 721 |         if (!isset($this->data['edulevel'])) {
 | 
        
           |  |  | 722 |             throw new \coding_exception('edulevel must be specified in init() method of each method');
 | 
        
           |  |  | 723 |         }
 | 
        
           |  |  | 724 |         if (!empty($this->data['objectid']) and empty($this->data['objecttable'])) {
 | 
        
           |  |  | 725 |             throw new \coding_exception('objecttable must be specified in init() method if objectid present');
 | 
        
           |  |  | 726 |         }
 | 
        
           |  |  | 727 |   | 
        
           |  |  | 728 |         if ($CFG->debugdeveloper) {
 | 
        
           |  |  | 729 |             // Ideally these should be coding exceptions, but we need to skip these for performance reasons
 | 
        
           |  |  | 730 |             // on production servers.
 | 
        
           |  |  | 731 |   | 
        
           |  |  | 732 |             if (!in_array($this->data['crud'], array('c', 'r', 'u', 'd'), true)) {
 | 
        
           |  |  | 733 |                 debugging("Invalid event crud value specified.", DEBUG_DEVELOPER);
 | 
        
           |  |  | 734 |             }
 | 
        
           |  |  | 735 |             if (!in_array($this->data['edulevel'], array(self::LEVEL_OTHER, self::LEVEL_TEACHING, self::LEVEL_PARTICIPATING))) {
 | 
        
           |  |  | 736 |                 // Bitwise combination of levels is not allowed at this stage.
 | 
        
           |  |  | 737 |                 debugging('Event property edulevel must a constant value, see event_base::LEVEL_*', DEBUG_DEVELOPER);
 | 
        
           |  |  | 738 |             }
 | 
        
           |  |  | 739 |             if (self::$fields !== array_keys($this->data)) {
 | 
        
           |  |  | 740 |                 debugging('Number of event data fields must not be changed in event classes', DEBUG_DEVELOPER);
 | 
        
           |  |  | 741 |             }
 | 
        
           |  |  | 742 |             $encoded = json_encode($this->data['other']);
 | 
        
           |  |  | 743 |             // The comparison here is not set to strict. We just need to check if the data is compatible with the JSON encoding
 | 
        
           |  |  | 744 |             // or not and we don't need to worry about how the data is encoded. Because in some cases, the data can contain
 | 
        
           |  |  | 745 |             // objects, and objects can be converted to a different format during encoding and decoding.
 | 
        
           |  |  | 746 |             if ($encoded === false) {
 | 
        
           |  |  | 747 |                 debugging('other event data must be compatible with json encoding', DEBUG_DEVELOPER);
 | 
        
           |  |  | 748 |             }
 | 
        
           |  |  | 749 |             if ($this->data['userid'] and !is_number($this->data['userid'])) {
 | 
        
           |  |  | 750 |                 debugging('Event property userid must be a number', DEBUG_DEVELOPER);
 | 
        
           |  |  | 751 |             }
 | 
        
           |  |  | 752 |             if ($this->data['courseid'] and !is_number($this->data['courseid'])) {
 | 
        
           |  |  | 753 |                 debugging('Event property courseid must be a number', DEBUG_DEVELOPER);
 | 
        
           |  |  | 754 |             }
 | 
        
           |  |  | 755 |             if ($this->data['objectid'] and !is_number($this->data['objectid'])) {
 | 
        
           |  |  | 756 |                 debugging('Event property objectid must be a number', DEBUG_DEVELOPER);
 | 
        
           |  |  | 757 |             }
 | 
        
           |  |  | 758 |             if ($this->data['relateduserid'] and !is_number($this->data['relateduserid'])) {
 | 
        
           |  |  | 759 |                 debugging('Event property relateduserid must be a number', DEBUG_DEVELOPER);
 | 
        
           |  |  | 760 |             }
 | 
        
           |  |  | 761 |             if ($this->data['objecttable']) {
 | 
        
           |  |  | 762 |                 if (!$DB->get_manager()->table_exists($this->data['objecttable'])) {
 | 
        
           |  |  | 763 |                     debugging('Unknown table specified in objecttable field', DEBUG_DEVELOPER);
 | 
        
           |  |  | 764 |                 }
 | 
        
           |  |  | 765 |                 if (!isset($this->data['objectid'])) {
 | 
        
           |  |  | 766 |                     debugging('Event property objectid must be set when objecttable is defined', DEBUG_DEVELOPER);
 | 
        
           |  |  | 767 |                 }
 | 
        
           |  |  | 768 |             }
 | 
        
           |  |  | 769 |         }
 | 
        
           |  |  | 770 |     }
 | 
        
           |  |  | 771 |   | 
        
           |  |  | 772 |     /**
 | 
        
           |  |  | 773 |      * Trigger event.
 | 
        
           |  |  | 774 |      */
 | 
        
           |  |  | 775 |     final public function trigger() {
 | 
        
           |  |  | 776 |         global $CFG;
 | 
        
           |  |  | 777 |   | 
        
           |  |  | 778 |         if ($this->restored) {
 | 
        
           |  |  | 779 |             throw new \coding_exception('Can not trigger restored event');
 | 
        
           |  |  | 780 |         }
 | 
        
           |  |  | 781 |         if ($this->triggered or $this->dispatched) {
 | 
        
           |  |  | 782 |             throw new \coding_exception('Can not trigger event twice');
 | 
        
           |  |  | 783 |         }
 | 
        
           |  |  | 784 |   | 
        
           |  |  | 785 |         $this->validate_before_trigger();
 | 
        
           |  |  | 786 |   | 
        
           |  |  | 787 |         $this->triggered = true;
 | 
        
           |  |  | 788 |   | 
        
           |  |  | 789 |         if (PHPUNIT_TEST and \phpunit_util::is_redirecting_events()) {
 | 
        
           |  |  | 790 |             $this->dispatched = true;
 | 
        
           |  |  | 791 |             \phpunit_util::event_triggered($this);
 | 
        
           |  |  | 792 |             return;
 | 
        
           |  |  | 793 |         }
 | 
        
           |  |  | 794 |   | 
        
           |  |  | 795 |         \core\event\manager::dispatch($this);
 | 
        
           |  |  | 796 |   | 
        
           |  |  | 797 |         $this->dispatched = true;
 | 
        
           |  |  | 798 |     }
 | 
        
           |  |  | 799 |   | 
        
           |  |  | 800 |     /**
 | 
        
           |  |  | 801 |      * Was this event already triggered?
 | 
        
           |  |  | 802 |      *
 | 
        
           |  |  | 803 |      * @return bool
 | 
        
           |  |  | 804 |      */
 | 
        
           |  |  | 805 |     final public function is_triggered() {
 | 
        
           |  |  | 806 |         return $this->triggered;
 | 
        
           |  |  | 807 |     }
 | 
        
           |  |  | 808 |   | 
        
           |  |  | 809 |     /**
 | 
        
           |  |  | 810 |      * Used from event manager to prevent direct access.
 | 
        
           |  |  | 811 |      *
 | 
        
           |  |  | 812 |      * @return bool
 | 
        
           |  |  | 813 |      */
 | 
        
           |  |  | 814 |     final public function is_dispatched() {
 | 
        
           |  |  | 815 |         return $this->dispatched;
 | 
        
           |  |  | 816 |     }
 | 
        
           |  |  | 817 |   | 
        
           |  |  | 818 |     /**
 | 
        
           |  |  | 819 |      * Was this event restored?
 | 
        
           |  |  | 820 |      *
 | 
        
           |  |  | 821 |      * @return bool
 | 
        
           |  |  | 822 |      */
 | 
        
           |  |  | 823 |     final public function is_restored() {
 | 
        
           |  |  | 824 |         return $this->restored;
 | 
        
           |  |  | 825 |     }
 | 
        
           |  |  | 826 |   | 
        
           |  |  | 827 |     /**
 | 
        
           |  |  | 828 |      * Add cached data that will be most probably used in event observers.
 | 
        
           |  |  | 829 |      *
 | 
        
           |  |  | 830 |      * This is used to improve performance, but it is required for data
 | 
        
           |  |  | 831 |      * that was just deleted.
 | 
        
           |  |  | 832 |      *
 | 
        
           |  |  | 833 |      * @param string $tablename
 | 
        
           |  |  | 834 |      * @param \stdClass $record
 | 
        
           |  |  | 835 |      *
 | 
        
           |  |  | 836 |      * @throws \coding_exception if used after ::trigger()
 | 
        
           |  |  | 837 |      */
 | 
        
           |  |  | 838 |     final public function add_record_snapshot($tablename, $record) {
 | 
        
           |  |  | 839 |         global $DB, $CFG;
 | 
        
           |  |  | 840 |   | 
        
           |  |  | 841 |         if ($this->triggered) {
 | 
        
           |  |  | 842 |             throw new \coding_exception('It is not possible to add snapshots after triggering of events');
 | 
        
           |  |  | 843 |         }
 | 
        
           |  |  | 844 |   | 
        
           |  |  | 845 |         // Special case for course module, allow instance of cm_info to be passed instead of stdClass.
 | 
        
           |  |  | 846 |         if ($tablename === 'course_modules' && $record instanceof \cm_info) {
 | 
        
           |  |  | 847 |             $record = $record->get_course_module_record();
 | 
        
           |  |  | 848 |         }
 | 
        
           |  |  | 849 |   | 
        
           |  |  | 850 |         // NOTE: this might use some kind of MUC cache,
 | 
        
           |  |  | 851 |         //       hopefully we will not run out of memory here...
 | 
        
           |  |  | 852 |         if ($CFG->debugdeveloper) {
 | 
        
           |  |  | 853 |             if (!($record instanceof \stdClass)) {
 | 
        
           |  |  | 854 |                 debugging('Argument $record must be an instance of stdClass.', DEBUG_DEVELOPER);
 | 
        
           |  |  | 855 |             }
 | 
        
           |  |  | 856 |             if (!$DB->get_manager()->table_exists($tablename)) {
 | 
        
           |  |  | 857 |                 debugging("Invalid table name '$tablename' specified, database table does not exist.", DEBUG_DEVELOPER);
 | 
        
           |  |  | 858 |             } else {
 | 
        
           |  |  | 859 |                 $columns = $DB->get_columns($tablename);
 | 
        
           |  |  | 860 |                 $missingfields = array_diff(array_keys($columns), array_keys((array)$record));
 | 
        
           |  |  | 861 |                 if (!empty($missingfields)) {
 | 
        
           |  |  | 862 |                     debugging("Fields list in snapshot record does not match fields list in '$tablename'. Record is missing fields: ".
 | 
        
           |  |  | 863 |                             join(', ', $missingfields), DEBUG_DEVELOPER);
 | 
        
           |  |  | 864 |                 }
 | 
        
           |  |  | 865 |             }
 | 
        
           |  |  | 866 |         }
 | 
        
           |  |  | 867 |         $this->recordsnapshots[$tablename][$record->id] = $record;
 | 
        
           |  |  | 868 |     }
 | 
        
           |  |  | 869 |   | 
        
           |  |  | 870 |     /**
 | 
        
           |  |  | 871 |      * Returns cached record or fetches data from database if not cached.
 | 
        
           |  |  | 872 |      *
 | 
        
           |  |  | 873 |      * @param string $tablename
 | 
        
           |  |  | 874 |      * @param int $id
 | 
        
           |  |  | 875 |      * @return \stdClass
 | 
        
           |  |  | 876 |      *
 | 
        
           |  |  | 877 |      * @throws \coding_exception if used after ::restore()
 | 
        
           |  |  | 878 |      */
 | 
        
           |  |  | 879 |     final public function get_record_snapshot($tablename, $id) {
 | 
        
           |  |  | 880 |         global $DB;
 | 
        
           |  |  | 881 |   | 
        
           |  |  | 882 |         if ($this->restored) {
 | 
        
           |  |  | 883 |             throw new \coding_exception('It is not possible to get snapshots from restored events');
 | 
        
           |  |  | 884 |         }
 | 
        
           |  |  | 885 |   | 
        
           |  |  | 886 |         if (isset($this->recordsnapshots[$tablename][$id])) {
 | 
        
           |  |  | 887 |             return clone($this->recordsnapshots[$tablename][$id]);
 | 
        
           |  |  | 888 |         }
 | 
        
           |  |  | 889 |   | 
        
           |  |  | 890 |         $record = $DB->get_record($tablename, array('id'=>$id));
 | 
        
           |  |  | 891 |         $this->recordsnapshots[$tablename][$id] = $record;
 | 
        
           |  |  | 892 |   | 
        
           |  |  | 893 |         return $record;
 | 
        
           |  |  | 894 |     }
 | 
        
           |  |  | 895 |   | 
        
           |  |  | 896 |     /**
 | 
        
           |  |  | 897 |      * Magic getter for read only access.
 | 
        
           |  |  | 898 |      *
 | 
        
           |  |  | 899 |      * @param string $name
 | 
        
           |  |  | 900 |      * @return mixed
 | 
        
           |  |  | 901 |      */
 | 
        
           |  |  | 902 |     public function __get($name) {
 | 
        
           |  |  | 903 |         if ($name === 'level') {
 | 
        
           |  |  | 904 |             debugging('level property is deprecated, use edulevel property instead', DEBUG_DEVELOPER);
 | 
        
           |  |  | 905 |             return $this->data['edulevel'];
 | 
        
           |  |  | 906 |         }
 | 
        
           |  |  | 907 |         if (array_key_exists($name, $this->data)) {
 | 
        
           |  |  | 908 |             return $this->data[$name];
 | 
        
           |  |  | 909 |         }
 | 
        
           |  |  | 910 |   | 
        
           |  |  | 911 |         debugging("Accessing non-existent event property '$name'");
 | 
        
           |  |  | 912 |     }
 | 
        
           |  |  | 913 |   | 
        
           |  |  | 914 |     /**
 | 
        
           |  |  | 915 |      * Magic setter.
 | 
        
           |  |  | 916 |      *
 | 
        
           |  |  | 917 |      * Note: we must not allow modification of data from outside,
 | 
        
           |  |  | 918 |      *       after trigger() the data MUST NOT CHANGE!!!
 | 
        
           |  |  | 919 |      *
 | 
        
           |  |  | 920 |      * @param string $name
 | 
        
           |  |  | 921 |      * @param mixed $value
 | 
        
           |  |  | 922 |      *
 | 
        
           |  |  | 923 |      * @throws \coding_exception
 | 
        
           |  |  | 924 |      */
 | 
        
           |  |  | 925 |     public function __set($name, $value) {
 | 
        
           |  |  | 926 |         throw new \coding_exception('Event properties must not be modified.');
 | 
        
           |  |  | 927 |     }
 | 
        
           |  |  | 928 |   | 
        
           |  |  | 929 |     /**
 | 
        
           |  |  | 930 |      * Is data property set?
 | 
        
           |  |  | 931 |      *
 | 
        
           |  |  | 932 |      * @param string $name
 | 
        
           |  |  | 933 |      * @return bool
 | 
        
           |  |  | 934 |      */
 | 
        
           |  |  | 935 |     public function __isset($name) {
 | 
        
           |  |  | 936 |         if ($name === 'level') {
 | 
        
           |  |  | 937 |             debugging('level property is deprecated, use edulevel property instead', DEBUG_DEVELOPER);
 | 
        
           |  |  | 938 |             return isset($this->data['edulevel']);
 | 
        
           |  |  | 939 |         }
 | 
        
           |  |  | 940 |         return isset($this->data[$name]);
 | 
        
           |  |  | 941 |     }
 | 
        
           |  |  | 942 |   | 
        
           |  |  | 943 |     /**
 | 
        
           |  |  | 944 |      * Create an iterator because magic vars can't be seen by 'foreach'.
 | 
        
           |  |  | 945 |      *
 | 
        
           |  |  | 946 |      * @return \ArrayIterator
 | 
        
           |  |  | 947 |      */
 | 
        
           |  |  | 948 |     public function getIterator(): \Traversable {
 | 
        
           |  |  | 949 |         return new \ArrayIterator($this->data);
 | 
        
           |  |  | 950 |     }
 | 
        
           |  |  | 951 |   | 
        
           |  |  | 952 |     /**
 | 
        
           |  |  | 953 |      * Whether this event has been marked as deprecated.
 | 
        
           |  |  | 954 |      *
 | 
        
           |  |  | 955 |      * Events cannot be deprecated in the normal fashion as they must remain to support historical data.
 | 
        
           |  |  | 956 |      * Once they are deprecated, there is no way to trigger the event, so it does not make sense to list it in some
 | 
        
           |  |  | 957 |      * parts of the UI (e.g. Event Monitor).
 | 
        
           |  |  | 958 |      *
 | 
        
           |  |  | 959 |      * @return boolean
 | 
        
           |  |  | 960 |      */
 | 
        
           |  |  | 961 |     public static function is_deprecated() {
 | 
        
           |  |  | 962 |         return false;
 | 
        
           |  |  | 963 |     }
 | 
        
           |  |  | 964 | }
 |