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