7e89055901701372bcf3f5c37cb53f9d7c857dbd
[moodle.git] / lib / phpunit / classes / util.php
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/>.
17 /**
18  * Utility class.
19  *
20  * @package    core
21  * @category   phpunit
22  * @copyright  2012 Petr Skoda {@link http://skodak.org}
23  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24  */
26 require_once(__DIR__.'/../../testing/classes/util.php');
28 /**
29  * Collection of utility methods.
30  *
31  * @package    core
32  * @category   phpunit
33  * @copyright  2012 Petr Skoda {@link http://skodak.org}
34  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
35  */
36 class phpunit_util extends testing_util {
37     /**
38      * @var int last value of db writes counter, used for db resetting
39      */
40     public static $lastdbwrites = null;
42     /** @var array An array of original globals, restored after each test */
43     protected static $globals = array();
45     /** @var array list of debugging messages triggered during the last test execution */
46     protected static $debuggings = array();
48     /** @var phpunit_message_sink alternative target for moodle messaging */
49     protected static $messagesink = null;
51     /** @var phpunit_phpmailer_sink alternative target for phpmailer messaging */
52     protected static $phpmailersink = null;
54     /** @var phpunit_message_sink alternative target for moodle messaging */
55     protected static $eventsink = null;
57     /**
58      * @var array Files to skip when resetting dataroot folder
59      */
60     protected static $datarootskiponreset = array('.', '..', 'phpunittestdir.txt', 'phpunit', '.htaccess');
62     /**
63      * @var array Files to skip when dropping dataroot folder
64      */
65     protected static $datarootskipondrop = array('.', '..', 'lock', 'webrunner.xml');
67     /**
68      * Load global $CFG;
69      * @internal
70      * @static
71      * @return void
72      */
73     public static function initialise_cfg() {
74         global $DB;
75         $dbhash = false;
76         try {
77             $dbhash = $DB->get_field('config', 'value', array('name'=>'phpunittest'));
78         } catch (Exception $e) {
79             // not installed yet
80             initialise_cfg();
81             return;
82         }
83         if ($dbhash !== core_component::get_all_versions_hash()) {
84             // do not set CFG - the only way forward is to drop and reinstall
85             return;
86         }
87         // standard CFG init
88         initialise_cfg();
89     }
91     /**
92      * Reset contents of all database tables to initial values, reset caches, etc.
93      *
94      * Note: this is relatively slow (cca 2 seconds for pg and 7 for mysql) - please use with care!
95      *
96      * @static
97      * @param bool $detectchanges
98      *      true  - changes in global state and database are reported as errors
99      *      false - no errors reported
100      *      null  - only critical problems are reported as errors
101      * @return void
102      */
103     public static function reset_all_data($detectchanges = false) {
104         global $DB, $CFG, $USER, $SITE, $COURSE, $PAGE, $OUTPUT, $SESSION;
106         // Stop any message redirection.
107         phpunit_util::stop_message_redirection();
109         // Stop any message redirection.
110         phpunit_util::stop_event_redirection();
112         // Start a new email redirection.
113         // This will clear any existing phpmailer redirection.
114         // We redirect all phpmailer output to this message sink which is
115         // called instead of phpmailer actually sending the message.
116         phpunit_util::start_phpmailer_redirection();
118         // We used to call gc_collect_cycles here to ensure desctructors were called between tests.
119         // This accounted for 25% of the total time running phpunit - so we removed it.
121         // Show any unhandled debugging messages, the runbare() could already reset it.
122         self::display_debugging_messages();
123         self::reset_debugging();
125         // reset global $DB in case somebody mocked it
126         $DB = self::get_global_backup('DB');
128         if ($DB->is_transaction_started()) {
129             // we can not reset inside transaction
130             $DB->force_transaction_rollback();
131         }
133         $resetdb = self::reset_database();
134         $warnings = array();
136         if ($detectchanges === true) {
137             if ($resetdb) {
138                 $warnings[] = 'Warning: unexpected database modification, resetting DB state';
139             }
141             $oldcfg = self::get_global_backup('CFG');
142             $oldsite = self::get_global_backup('SITE');
143             foreach($CFG as $k=>$v) {
144                 if (!property_exists($oldcfg, $k)) {
145                     $warnings[] = 'Warning: unexpected new $CFG->'.$k.' value';
146                 } else if ($oldcfg->$k !== $CFG->$k) {
147                     $warnings[] = 'Warning: unexpected change of $CFG->'.$k.' value';
148                 }
149                 unset($oldcfg->$k);
151             }
152             if ($oldcfg) {
153                 foreach($oldcfg as $k=>$v) {
154                     $warnings[] = 'Warning: unexpected removal of $CFG->'.$k;
155                 }
156             }
158             if ($USER->id != 0) {
159                 $warnings[] = 'Warning: unexpected change of $USER';
160             }
162             if ($COURSE->id != $oldsite->id) {
163                 $warnings[] = 'Warning: unexpected change of $COURSE';
164             }
166             if ($CFG->ostype === 'WINDOWS') {
167                 if (setlocale(LC_TIME, 0) !== 'English_Australia.1252') {
168                     $warnings[] = 'Warning: unexpected change of locale';
169                 }
170             } else {
171                 if (setlocale(LC_TIME, 0) !== 'en_AU.UTF-8') {
172                     $warnings[] = 'Warning: unexpected change of locale';
173                 }
174             }
175         }
177         if (ini_get('max_execution_time') != 0) {
178             // This is special warning for all resets because we do not want any
179             // libraries to mess with timeouts unintentionally.
180             // Our PHPUnit integration is not supposed to change it either.
182             if ($detectchanges !== false) {
183                 $warnings[] = 'Warning: max_execution_time was changed to '.ini_get('max_execution_time');
184             }
185             set_time_limit(0);
186         }
188         // restore original globals
189         $_SERVER = self::get_global_backup('_SERVER');
190         $CFG = self::get_global_backup('CFG');
191         $SITE = self::get_global_backup('SITE');
192         $_GET = array();
193         $_POST = array();
194         $_FILES = array();
195         $_REQUEST = array();
196         $COURSE = $SITE;
198         // reinitialise following globals
199         $OUTPUT = new bootstrap_renderer();
200         $PAGE = new moodle_page();
201         $FULLME = null;
202         $ME = null;
203         $SCRIPT = null;
205         // Empty sessison and set fresh new not-logged-in user.
206         \core\session\manager::init_empty_session();
208         // reset all static caches
209         \core\event\manager::phpunit_reset();
210         accesslib_clear_all_caches(true);
211         get_string_manager()->reset_caches(true);
212         reset_text_filters_cache(true);
213         events_get_handlers('reset');
214         core_text::reset_caches();
215         get_message_processors(false, true);
216         filter_manager::reset_caches();
217         core_filetypes::reset_caches();
219         // Reset internal users.
220         core_user::reset_internal_users();
222         //TODO MDL-25290: add more resets here and probably refactor them to new core function
224         // Reset course and module caches.
225         if (class_exists('format_base')) {
226             // If file containing class is not loaded, there is no cache there anyway.
227             format_base::reset_course_cache(0);
228         }
229         get_fast_modinfo(0, 0, true);
231         // Reset other singletons.
232         if (class_exists('core_plugin_manager')) {
233             core_plugin_manager::reset_caches(true);
234         }
235         if (class_exists('\core\update\checker')) {
236             \core\update\checker::reset_caches(true);
237         }
238         if (class_exists('\core\update\deployer')) {
239             \core\update\deployer::reset_caches(true);
240         }
242         // Clear static cache within restore.
243         if (class_exists('restore_section_structure_step')) {
244             restore_section_structure_step::reset_caches();
245         }
247         // purge dataroot directory
248         self::reset_dataroot();
250         // restore original config once more in case resetting of caches changed CFG
251         $CFG = self::get_global_backup('CFG');
253         // inform data generator
254         self::get_data_generator()->reset();
256         // fix PHP settings
257         error_reporting($CFG->debug);
259         // Reset the date/time class.
260         core_date::phpunit_reset();
262         // Make sure the time locale is consistent - that is Australian English.
263         if ($CFG->ostype === 'WINDOWS') {
264             setlocale(LC_TIME, 'English_Australia.1252');
265         } else {
266             setlocale(LC_TIME, 'en_AU.UTF-8');
267         }
269         // verify db writes just in case something goes wrong in reset
270         if (self::$lastdbwrites != $DB->perf_get_writes()) {
271             error_log('Unexpected DB writes in phpunit_util::reset_all_data()');
272             self::$lastdbwrites = $DB->perf_get_writes();
273         }
275         if ($warnings) {
276             $warnings = implode("\n", $warnings);
277             trigger_error($warnings, E_USER_WARNING);
278         }
279     }
281     /**
282      * Reset all database tables to default values.
283      * @static
284      * @return bool true if reset done, false if skipped
285      */
286     public static function reset_database() {
287         global $DB;
289         if (!is_null(self::$lastdbwrites) and self::$lastdbwrites == $DB->perf_get_writes()) {
290             return false;
291         }
293         if (!parent::reset_database()) {
294             return false;
295         }
297         self::$lastdbwrites = $DB->perf_get_writes();
299         return true;
300     }
302     /**
303      * Called during bootstrap only!
304      * @internal
305      * @static
306      * @return void
307      */
308     public static function bootstrap_init() {
309         global $CFG, $SITE, $DB;
311         // backup the globals
312         self::$globals['_SERVER'] = $_SERVER;
313         self::$globals['CFG'] = clone($CFG);
314         self::$globals['SITE'] = clone($SITE);
315         self::$globals['DB'] = $DB;
317         // refresh data in all tables, clear caches, etc.
318         phpunit_util::reset_all_data();
319     }
321     /**
322      * Print some Moodle related info to console.
323      * @internal
324      * @static
325      * @return void
326      */
327     public static function bootstrap_moodle_info() {
328         echo self::get_site_info();
329     }
331     /**
332      * Returns original state of global variable.
333      * @static
334      * @param string $name
335      * @return mixed
336      */
337     public static function get_global_backup($name) {
338         if ($name === 'DB') {
339             // no cloning of database object,
340             // we just need the original reference, not original state
341             return self::$globals['DB'];
342         }
343         if (isset(self::$globals[$name])) {
344             if (is_object(self::$globals[$name])) {
345                 $return = clone(self::$globals[$name]);
346                 return $return;
347             } else {
348                 return self::$globals[$name];
349             }
350         }
351         return null;
352     }
354     /**
355      * Is this site initialised to run unit tests?
356      *
357      * @static
358      * @return int array errorcode=>message, 0 means ok
359      */
360     public static function testing_ready_problem() {
361         global $DB;
363         if (!self::is_test_site()) {
364             // dataroot was verified in bootstrap, so it must be DB
365             return array(PHPUNIT_EXITCODE_CONFIGERROR, 'Can not use database for testing, try different prefix');
366         }
368         $tables = $DB->get_tables(false);
369         if (empty($tables)) {
370             return array(PHPUNIT_EXITCODE_INSTALL, '');
371         }
373         if (!self::is_test_data_updated()) {
374             return array(PHPUNIT_EXITCODE_REINSTALL, '');
375         }
377         return array(0, '');
378     }
380     /**
381      * Drop all test site data.
382      *
383      * Note: To be used from CLI scripts only.
384      *
385      * @static
386      * @param bool $displayprogress if true, this method will echo progress information.
387      * @return void may terminate execution with exit code
388      */
389     public static function drop_site($displayprogress = false) {
390         global $DB, $CFG;
392         if (!self::is_test_site()) {
393             phpunit_bootstrap_error(PHPUNIT_EXITCODE_CONFIGERROR, 'Can not drop non-test site!!');
394         }
396         // Purge dataroot
397         if ($displayprogress) {
398             echo "Purging dataroot:\n";
399         }
401         self::reset_dataroot();
402         testing_initdataroot($CFG->dataroot, 'phpunit');
403         self::drop_dataroot();
405         // drop all tables
406         self::drop_database($displayprogress);
407     }
409     /**
410      * Perform a fresh test site installation
411      *
412      * Note: To be used from CLI scripts only.
413      *
414      * @static
415      * @return void may terminate execution with exit code
416      */
417     public static function install_site() {
418         global $DB, $CFG;
420         if (!self::is_test_site()) {
421             phpunit_bootstrap_error(PHPUNIT_EXITCODE_CONFIGERROR, 'Can not install on non-test site!!');
422         }
424         if ($DB->get_tables()) {
425             list($errorcode, $message) = phpunit_util::testing_ready_problem();
426             if ($errorcode) {
427                 phpunit_bootstrap_error(PHPUNIT_EXITCODE_REINSTALL, 'Database tables already present, Moodle PHPUnit test environment can not be initialised');
428             } else {
429                 phpunit_bootstrap_error(0, 'Moodle PHPUnit test environment is already initialised');
430             }
431         }
433         $options = array();
434         $options['adminpass'] = 'admin';
435         $options['shortname'] = 'phpunit';
436         $options['fullname'] = 'PHPUnit test site';
438         install_cli_database($options, false);
440         // Set the admin email address.
441         $DB->set_field('user', 'email', 'admin@example.com', array('username' => 'admin'));
443         // Disable all logging for performance and sanity reasons.
444         set_config('enabled_stores', '', 'tool_log');
446         // We need to keep the installed dataroot filedir files.
447         // So each time we reset the dataroot before running a test, the default files are still installed.
448         self::save_original_data_files();
450         // Store version hash in the database and in a file.
451         self::store_versions_hash();
453         // Store database data and structure.
454         self::store_database_state();
455     }
457     /**
458      * Builds dirroot/phpunit.xml and dataroot/phpunit/webrunner.xml files using defaults from /phpunit.xml.dist
459      * @static
460      * @return bool true means main config file created, false means only dataroot file created
461      */
462     public static function build_config_file() {
463         global $CFG;
465         $template = '
466         <testsuite name="@component@_testsuite">
467             <directory suffix="_test.php">@dir@</directory>
468         </testsuite>';
469         $data = file_get_contents("$CFG->dirroot/phpunit.xml.dist");
471         $suites = '';
473         $plugintypes = core_component::get_plugin_types();
474         ksort($plugintypes);
475         foreach ($plugintypes as $type=>$unused) {
476             $plugs = core_component::get_plugin_list($type);
477             ksort($plugs);
478             foreach ($plugs as $plug=>$fullplug) {
479                 if (!file_exists("$fullplug/tests/")) {
480                     continue;
481                 }
482                 $dir = substr($fullplug, strlen($CFG->dirroot)+1);
483                 $dir .= '/tests';
484                 $component = $type.'_'.$plug;
486                 $suite = str_replace('@component@', $component, $template);
487                 $suite = str_replace('@dir@', $dir, $suite);
489                 $suites .= $suite;
490             }
491         }
492         // Start a sequence between 100000 and 199000 to ensure each call to init produces
493         // different ids in the database.  This reduces the risk that hard coded values will
494         // end up being placed in phpunit or behat test code.
495         $sequencestart = 100000 + mt_rand(0, 99) * 1000;
497         $data = preg_replace('|<!--@plugin_suites_start@-->.*<!--@plugin_suites_end@-->|s', $suites, $data, 1);
498         $data = str_replace(
499             '<const name="PHPUNIT_SEQUENCE_START" value=""/>',
500             '<const name="PHPUNIT_SEQUENCE_START" value="' . $sequencestart . '"/>',
501             $data);
503         $result = false;
504         if (is_writable($CFG->dirroot)) {
505             if ($result = file_put_contents("$CFG->dirroot/phpunit.xml", $data)) {
506                 testing_fix_file_permissions("$CFG->dirroot/phpunit.xml");
507             }
508         }
510         // relink - it seems that xml:base does not work in phpunit xml files, remove this nasty hack if you find a way to set xml base for relative refs
511         $data = str_replace('lib/phpunit/', $CFG->dirroot.DIRECTORY_SEPARATOR.'lib'.DIRECTORY_SEPARATOR.'phpunit'.DIRECTORY_SEPARATOR, $data);
512         $data = preg_replace('|<directory suffix="_test.php">([^<]+)</directory>|',
513             '<directory suffix="_test.php">'.$CFG->dirroot.(DIRECTORY_SEPARATOR === '\\' ? '\\\\' : DIRECTORY_SEPARATOR).'$1</directory>',
514             $data);
515         file_put_contents("$CFG->dataroot/phpunit/webrunner.xml", $data);
516         testing_fix_file_permissions("$CFG->dataroot/phpunit/webrunner.xml");
518         return (bool)$result;
519     }
521     /**
522      * Builds phpunit.xml files for all components using defaults from /phpunit.xml.dist
523      *
524      * @static
525      * @return void, stops if can not write files
526      */
527     public static function build_component_config_files() {
528         global $CFG;
530         $template = '
531         <testsuites>
532             <testsuite name="@component@">
533                 <directory suffix="_test.php">.</directory>
534             </testsuite>
535         </testsuites>';
537         // Start a sequence between 100000 and 199000 to ensure each call to init produces
538         // different ids in the database.  This reduces the risk that hard coded values will
539         // end up being placed in phpunit or behat test code.
540         $sequencestart = 100000 + mt_rand(0, 99) * 1000;
542         // Use the upstream file as source for the distributed configurations
543         $ftemplate = file_get_contents("$CFG->dirroot/phpunit.xml.dist");
544         $ftemplate = preg_replace('|<!--All core suites.*</testsuites>|s', '<!--@component_suite@-->', $ftemplate);
546         // Gets all the components with tests
547         $components = tests_finder::get_components_with_tests('phpunit');
549         // Create the corresponding phpunit.xml file for each component
550         foreach ($components as $cname => $cpath) {
551             // Calculate the component suite
552             $ctemplate = $template;
553             $ctemplate = str_replace('@component@', $cname, $ctemplate);
555             // Apply it to the file template
556             $fcontents = str_replace('<!--@component_suite@-->', $ctemplate, $ftemplate);
557             $fcontents = str_replace(
558                 '<const name="PHPUNIT_SEQUENCE_START" value=""/>',
559                 '<const name="PHPUNIT_SEQUENCE_START" value="' . $sequencestart . '"/>',
560                 $fcontents);
562             // fix link to schema
563             $level = substr_count(str_replace('\\', '/', $cpath), '/') - substr_count(str_replace('\\', '/', $CFG->dirroot), '/');
564             $fcontents = str_replace('lib/phpunit/', str_repeat('../', $level).'lib/phpunit/', $fcontents);
566             // Write the file
567             $result = false;
568             if (is_writable($cpath)) {
569                 if ($result = (bool)file_put_contents("$cpath/phpunit.xml", $fcontents)) {
570                     testing_fix_file_permissions("$cpath/phpunit.xml");
571                 }
572             }
573             // Problems writing file, throw error
574             if (!$result) {
575                 phpunit_bootstrap_error(PHPUNIT_EXITCODE_CONFIGWARNING, "Can not create $cpath/phpunit.xml configuration file, verify dir permissions");
576             }
577         }
578     }
580     /**
581      * To be called from debugging() only.
582      * @param string $message
583      * @param int $level
584      * @param string $from
585      */
586     public static function debugging_triggered($message, $level, $from) {
587         // Store only if debugging triggered from actual test,
588         // we need normal debugging outside of tests to find problems in our phpunit integration.
589         $backtrace = debug_backtrace();
591         foreach ($backtrace as $bt) {
592             $intest = false;
593             if (isset($bt['object']) and is_object($bt['object'])) {
594                 if ($bt['object'] instanceof PHPUnit_Framework_TestCase) {
595                     if (strpos($bt['function'], 'test') === 0) {
596                         $intest = true;
597                         break;
598                     }
599                 }
600             }
601         }
602         if (!$intest) {
603             return false;
604         }
606         $debug = new stdClass();
607         $debug->message = $message;
608         $debug->level   = $level;
609         $debug->from    = $from;
611         self::$debuggings[] = $debug;
613         return true;
614     }
616     /**
617      * Resets the list of debugging messages.
618      */
619     public static function reset_debugging() {
620         self::$debuggings = array();
621         set_debugging(DEBUG_DEVELOPER);
622     }
624     /**
625      * Returns all debugging messages triggered during test.
626      * @return array with instances having message, level and stacktrace property.
627      */
628     public static function get_debugging_messages() {
629         return self::$debuggings;
630     }
632     /**
633      * Prints out any debug messages accumulated during test execution.
634      * @return bool false if no debug messages, true if debug triggered
635      */
636     public static function display_debugging_messages() {
637         if (empty(self::$debuggings)) {
638             return false;
639         }
640         foreach(self::$debuggings as $debug) {
641             echo 'Debugging: ' . $debug->message . "\n" . trim($debug->from) . "\n";
642         }
644         return true;
645     }
647     /**
648      * Start message redirection.
649      *
650      * Note: Do not call directly from tests,
651      *       use $sink = $this->redirectMessages() instead.
652      *
653      * @return phpunit_message_sink
654      */
655     public static function start_message_redirection() {
656         if (self::$messagesink) {
657             self::stop_message_redirection();
658         }
659         self::$messagesink = new phpunit_message_sink();
660         return self::$messagesink;
661     }
663     /**
664      * End message redirection.
665      *
666      * Note: Do not call directly from tests,
667      *       use $sink->close() instead.
668      */
669     public static function stop_message_redirection() {
670         self::$messagesink = null;
671     }
673     /**
674      * Are messages redirected to some sink?
675      *
676      * Note: to be called from messagelib.php only!
677      *
678      * @return bool
679      */
680     public static function is_redirecting_messages() {
681         return !empty(self::$messagesink);
682     }
684     /**
685      * To be called from messagelib.php only!
686      *
687      * @param stdClass $message record from message_read table
688      * @return bool true means send message, false means message "sent" to sink.
689      */
690     public static function message_sent($message) {
691         if (self::$messagesink) {
692             self::$messagesink->add_message($message);
693         }
694     }
696     /**
697      * Start phpmailer redirection.
698      *
699      * Note: Do not call directly from tests,
700      *       use $sink = $this->redirectEmails() instead.
701      *
702      * @return phpunit_phpmailer_sink
703      */
704     public static function start_phpmailer_redirection() {
705         if (self::$phpmailersink) {
706             // If an existing mailer sink is active, just clear it.
707             self::$phpmailersink->clear();
708         } else {
709             self::$phpmailersink = new phpunit_phpmailer_sink();
710         }
711         return self::$phpmailersink;
712     }
714     /**
715      * End phpmailer redirection.
716      *
717      * Note: Do not call directly from tests,
718      *       use $sink->close() instead.
719      */
720     public static function stop_phpmailer_redirection() {
721         self::$phpmailersink = null;
722     }
724     /**
725      * Are messages for phpmailer redirected to some sink?
726      *
727      * Note: to be called from moodle_phpmailer.php only!
728      *
729      * @return bool
730      */
731     public static function is_redirecting_phpmailer() {
732         return !empty(self::$phpmailersink);
733     }
735     /**
736      * To be called from messagelib.php only!
737      *
738      * @param stdClass $message record from message_read table
739      * @return bool true means send message, false means message "sent" to sink.
740      */
741     public static function phpmailer_sent($message) {
742         if (self::$phpmailersink) {
743             self::$phpmailersink->add_message($message);
744         }
745     }
747     /**
748      * Start event redirection.
749      *
750      * @private
751      * Note: Do not call directly from tests,
752      *       use $sink = $this->redirectEvents() instead.
753      *
754      * @return phpunit_event_sink
755      */
756     public static function start_event_redirection() {
757         if (self::$eventsink) {
758             self::stop_event_redirection();
759         }
760         self::$eventsink = new phpunit_event_sink();
761         return self::$eventsink;
762     }
764     /**
765      * End event redirection.
766      *
767      * @private
768      * Note: Do not call directly from tests,
769      *       use $sink->close() instead.
770      */
771     public static function stop_event_redirection() {
772         self::$eventsink = null;
773     }
775     /**
776      * Are events redirected to some sink?
777      *
778      * Note: to be called from \core\event\base only!
779      *
780      * @private
781      * @return bool
782      */
783     public static function is_redirecting_events() {
784         return !empty(self::$eventsink);
785     }
787     /**
788      * To be called from \core\event\base only!
789      *
790      * @private
791      * @param \core\event\base $event record from event_read table
792      * @return bool true means send event, false means event "sent" to sink.
793      */
794     public static function event_triggered(\core\event\base $event) {
795         if (self::$eventsink) {
796             self::$eventsink->add_event($event);
797         }
798     }