MDL-49329 admin: Get rid of mdeploy and \core\update\deployer class
[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         self::stop_message_redirection();
109         // Stop any message redirection.
110         self::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         self::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 static unit test options.
220         if (class_exists('\availability_date\condition', false)) {
221             \availability_date\condition::set_current_time_for_test(0);
222         }
224         // Reset internal users.
225         core_user::reset_internal_users();
227         //TODO MDL-25290: add more resets here and probably refactor them to new core function
229         // Reset course and module caches.
230         if (class_exists('format_base')) {
231             // If file containing class is not loaded, there is no cache there anyway.
232             format_base::reset_course_cache(0);
233         }
234         get_fast_modinfo(0, 0, true);
236         // Reset other singletons.
237         if (class_exists('core_plugin_manager')) {
238             core_plugin_manager::reset_caches(true);
239         }
240         if (class_exists('\core\update\checker')) {
241             \core\update\checker::reset_caches(true);
242         }
244         // Clear static cache within restore.
245         if (class_exists('restore_section_structure_step')) {
246             restore_section_structure_step::reset_caches();
247         }
249         // purge dataroot directory
250         self::reset_dataroot();
252         // restore original config once more in case resetting of caches changed CFG
253         $CFG = self::get_global_backup('CFG');
255         // inform data generator
256         self::get_data_generator()->reset();
258         // fix PHP settings
259         error_reporting($CFG->debug);
261         // Reset the date/time class.
262         core_date::phpunit_reset();
264         // Make sure the time locale is consistent - that is Australian English.
265         if ($CFG->ostype === 'WINDOWS') {
266             setlocale(LC_TIME, 'English_Australia.1252');
267         } else {
268             setlocale(LC_TIME, 'en_AU.UTF-8');
269         }
271         // verify db writes just in case something goes wrong in reset
272         if (self::$lastdbwrites != $DB->perf_get_writes()) {
273             error_log('Unexpected DB writes in phpunit_util::reset_all_data()');
274             self::$lastdbwrites = $DB->perf_get_writes();
275         }
277         if ($warnings) {
278             $warnings = implode("\n", $warnings);
279             trigger_error($warnings, E_USER_WARNING);
280         }
281     }
283     /**
284      * Reset all database tables to default values.
285      * @static
286      * @return bool true if reset done, false if skipped
287      */
288     public static function reset_database() {
289         global $DB;
291         if (!is_null(self::$lastdbwrites) and self::$lastdbwrites == $DB->perf_get_writes()) {
292             return false;
293         }
295         if (!parent::reset_database()) {
296             return false;
297         }
299         self::$lastdbwrites = $DB->perf_get_writes();
301         return true;
302     }
304     /**
305      * Called during bootstrap only!
306      * @internal
307      * @static
308      * @return void
309      */
310     public static function bootstrap_init() {
311         global $CFG, $SITE, $DB;
313         // backup the globals
314         self::$globals['_SERVER'] = $_SERVER;
315         self::$globals['CFG'] = clone($CFG);
316         self::$globals['SITE'] = clone($SITE);
317         self::$globals['DB'] = $DB;
319         // refresh data in all tables, clear caches, etc.
320         self::reset_all_data();
321     }
323     /**
324      * Print some Moodle related info to console.
325      * @internal
326      * @static
327      * @return void
328      */
329     public static function bootstrap_moodle_info() {
330         echo self::get_site_info();
331     }
333     /**
334      * Returns original state of global variable.
335      * @static
336      * @param string $name
337      * @return mixed
338      */
339     public static function get_global_backup($name) {
340         if ($name === 'DB') {
341             // no cloning of database object,
342             // we just need the original reference, not original state
343             return self::$globals['DB'];
344         }
345         if (isset(self::$globals[$name])) {
346             if (is_object(self::$globals[$name])) {
347                 $return = clone(self::$globals[$name]);
348                 return $return;
349             } else {
350                 return self::$globals[$name];
351             }
352         }
353         return null;
354     }
356     /**
357      * Is this site initialised to run unit tests?
358      *
359      * @static
360      * @return int array errorcode=>message, 0 means ok
361      */
362     public static function testing_ready_problem() {
363         global $DB;
365         if (!self::is_test_site()) {
366             // dataroot was verified in bootstrap, so it must be DB
367             return array(PHPUNIT_EXITCODE_CONFIGERROR, 'Can not use database for testing, try different prefix');
368         }
370         $tables = $DB->get_tables(false);
371         if (empty($tables)) {
372             return array(PHPUNIT_EXITCODE_INSTALL, '');
373         }
375         if (!self::is_test_data_updated()) {
376             return array(PHPUNIT_EXITCODE_REINSTALL, '');
377         }
379         return array(0, '');
380     }
382     /**
383      * Drop all test site data.
384      *
385      * Note: To be used from CLI scripts only.
386      *
387      * @static
388      * @param bool $displayprogress if true, this method will echo progress information.
389      * @return void may terminate execution with exit code
390      */
391     public static function drop_site($displayprogress = false) {
392         global $DB, $CFG;
394         if (!self::is_test_site()) {
395             phpunit_bootstrap_error(PHPUNIT_EXITCODE_CONFIGERROR, 'Can not drop non-test site!!');
396         }
398         // Purge dataroot
399         if ($displayprogress) {
400             echo "Purging dataroot:\n";
401         }
403         self::reset_dataroot();
404         testing_initdataroot($CFG->dataroot, 'phpunit');
405         self::drop_dataroot();
407         // drop all tables
408         self::drop_database($displayprogress);
409     }
411     /**
412      * Perform a fresh test site installation
413      *
414      * Note: To be used from CLI scripts only.
415      *
416      * @static
417      * @return void may terminate execution with exit code
418      */
419     public static function install_site() {
420         global $DB, $CFG;
422         if (!self::is_test_site()) {
423             phpunit_bootstrap_error(PHPUNIT_EXITCODE_CONFIGERROR, 'Can not install on non-test site!!');
424         }
426         if ($DB->get_tables()) {
427             list($errorcode, $message) = self::testing_ready_problem();
428             if ($errorcode) {
429                 phpunit_bootstrap_error(PHPUNIT_EXITCODE_REINSTALL, 'Database tables already present, Moodle PHPUnit test environment can not be initialised');
430             } else {
431                 phpunit_bootstrap_error(0, 'Moodle PHPUnit test environment is already initialised');
432             }
433         }
435         $options = array();
436         $options['adminpass'] = 'admin';
437         $options['shortname'] = 'phpunit';
438         $options['fullname'] = 'PHPUnit test site';
440         install_cli_database($options, false);
442         // Set the admin email address.
443         $DB->set_field('user', 'email', 'admin@example.com', array('username' => 'admin'));
445         // Disable all logging for performance and sanity reasons.
446         set_config('enabled_stores', '', 'tool_log');
448         // We need to keep the installed dataroot filedir files.
449         // So each time we reset the dataroot before running a test, the default files are still installed.
450         self::save_original_data_files();
452         // Store version hash in the database and in a file.
453         self::store_versions_hash();
455         // Store database data and structure.
456         self::store_database_state();
457     }
459     /**
460      * Builds dirroot/phpunit.xml and dataroot/phpunit/webrunner.xml files using defaults from /phpunit.xml.dist
461      * @static
462      * @return bool true means main config file created, false means only dataroot file created
463      */
464     public static function build_config_file() {
465         global $CFG;
467         $template = '
468         <testsuite name="@component@_testsuite">
469             <directory suffix="_test.php">@dir@</directory>
470         </testsuite>';
471         $data = file_get_contents("$CFG->dirroot/phpunit.xml.dist");
473         $suites = '';
475         $plugintypes = core_component::get_plugin_types();
476         ksort($plugintypes);
477         foreach ($plugintypes as $type=>$unused) {
478             $plugs = core_component::get_plugin_list($type);
479             ksort($plugs);
480             foreach ($plugs as $plug=>$fullplug) {
481                 if (!file_exists("$fullplug/tests/")) {
482                     continue;
483                 }
484                 $dir = substr($fullplug, strlen($CFG->dirroot)+1);
485                 $dir .= '/tests';
486                 $component = $type.'_'.$plug;
488                 $suite = str_replace('@component@', $component, $template);
489                 $suite = str_replace('@dir@', $dir, $suite);
491                 $suites .= $suite;
492             }
493         }
494         // Start a sequence between 100000 and 199000 to ensure each call to init produces
495         // different ids in the database.  This reduces the risk that hard coded values will
496         // end up being placed in phpunit or behat test code.
497         $sequencestart = 100000 + mt_rand(0, 99) * 1000;
499         $data = preg_replace('|<!--@plugin_suites_start@-->.*<!--@plugin_suites_end@-->|s', $suites, $data, 1);
500         $data = str_replace(
501             '<const name="PHPUNIT_SEQUENCE_START" value=""/>',
502             '<const name="PHPUNIT_SEQUENCE_START" value="' . $sequencestart . '"/>',
503             $data);
505         $result = false;
506         if (is_writable($CFG->dirroot)) {
507             if ($result = file_put_contents("$CFG->dirroot/phpunit.xml", $data)) {
508                 testing_fix_file_permissions("$CFG->dirroot/phpunit.xml");
509             }
510         }
512         // 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
513         $data = str_replace('lib/phpunit/', $CFG->dirroot.DIRECTORY_SEPARATOR.'lib'.DIRECTORY_SEPARATOR.'phpunit'.DIRECTORY_SEPARATOR, $data);
514         $data = preg_replace('|<directory suffix="_test.php">([^<]+)</directory>|',
515             '<directory suffix="_test.php">'.$CFG->dirroot.(DIRECTORY_SEPARATOR === '\\' ? '\\\\' : DIRECTORY_SEPARATOR).'$1</directory>',
516             $data);
517         file_put_contents("$CFG->dataroot/phpunit/webrunner.xml", $data);
518         testing_fix_file_permissions("$CFG->dataroot/phpunit/webrunner.xml");
520         return (bool)$result;
521     }
523     /**
524      * Builds phpunit.xml files for all components using defaults from /phpunit.xml.dist
525      *
526      * @static
527      * @return void, stops if can not write files
528      */
529     public static function build_component_config_files() {
530         global $CFG;
532         $template = '
533         <testsuites>
534             <testsuite name="@component@_testsuite">
535                 <directory suffix="_test.php">.</directory>
536             </testsuite>
537         </testsuites>';
539         // Start a sequence between 100000 and 199000 to ensure each call to init produces
540         // different ids in the database.  This reduces the risk that hard coded values will
541         // end up being placed in phpunit or behat test code.
542         $sequencestart = 100000 + mt_rand(0, 99) * 1000;
544         // Use the upstream file as source for the distributed configurations
545         $ftemplate = file_get_contents("$CFG->dirroot/phpunit.xml.dist");
546         $ftemplate = preg_replace('|<!--All core suites.*</testsuites>|s', '<!--@component_suite@-->', $ftemplate);
548         // Gets all the components with tests
549         $components = tests_finder::get_components_with_tests('phpunit');
551         // Create the corresponding phpunit.xml file for each component
552         foreach ($components as $cname => $cpath) {
553             // Calculate the component suite
554             $ctemplate = $template;
555             $ctemplate = str_replace('@component@', $cname, $ctemplate);
557             // Apply it to the file template
558             $fcontents = str_replace('<!--@component_suite@-->', $ctemplate, $ftemplate);
559             $fcontents = str_replace(
560                 '<const name="PHPUNIT_SEQUENCE_START" value=""/>',
561                 '<const name="PHPUNIT_SEQUENCE_START" value="' . $sequencestart . '"/>',
562                 $fcontents);
564             // fix link to schema
565             $level = substr_count(str_replace('\\', '/', $cpath), '/') - substr_count(str_replace('\\', '/', $CFG->dirroot), '/');
566             $fcontents = str_replace('lib/phpunit/', str_repeat('../', $level).'lib/phpunit/', $fcontents);
568             // Write the file
569             $result = false;
570             if (is_writable($cpath)) {
571                 if ($result = (bool)file_put_contents("$cpath/phpunit.xml", $fcontents)) {
572                     testing_fix_file_permissions("$cpath/phpunit.xml");
573                 }
574             }
575             // Problems writing file, throw error
576             if (!$result) {
577                 phpunit_bootstrap_error(PHPUNIT_EXITCODE_CONFIGWARNING, "Can not create $cpath/phpunit.xml configuration file, verify dir permissions");
578             }
579         }
580     }
582     /**
583      * To be called from debugging() only.
584      * @param string $message
585      * @param int $level
586      * @param string $from
587      */
588     public static function debugging_triggered($message, $level, $from) {
589         // Store only if debugging triggered from actual test,
590         // we need normal debugging outside of tests to find problems in our phpunit integration.
591         $backtrace = debug_backtrace();
593         foreach ($backtrace as $bt) {
594             $intest = false;
595             if (isset($bt['object']) and is_object($bt['object'])) {
596                 if ($bt['object'] instanceof PHPUnit_Framework_TestCase) {
597                     if (strpos($bt['function'], 'test') === 0) {
598                         $intest = true;
599                         break;
600                     }
601                 }
602             }
603         }
604         if (!$intest) {
605             return false;
606         }
608         $debug = new stdClass();
609         $debug->message = $message;
610         $debug->level   = $level;
611         $debug->from    = $from;
613         self::$debuggings[] = $debug;
615         return true;
616     }
618     /**
619      * Resets the list of debugging messages.
620      */
621     public static function reset_debugging() {
622         self::$debuggings = array();
623         set_debugging(DEBUG_DEVELOPER);
624     }
626     /**
627      * Returns all debugging messages triggered during test.
628      * @return array with instances having message, level and stacktrace property.
629      */
630     public static function get_debugging_messages() {
631         return self::$debuggings;
632     }
634     /**
635      * Prints out any debug messages accumulated during test execution.
636      * @return bool false if no debug messages, true if debug triggered
637      */
638     public static function display_debugging_messages() {
639         if (empty(self::$debuggings)) {
640             return false;
641         }
642         foreach(self::$debuggings as $debug) {
643             echo 'Debugging: ' . $debug->message . "\n" . trim($debug->from) . "\n";
644         }
646         return true;
647     }
649     /**
650      * Start message redirection.
651      *
652      * Note: Do not call directly from tests,
653      *       use $sink = $this->redirectMessages() instead.
654      *
655      * @return phpunit_message_sink
656      */
657     public static function start_message_redirection() {
658         if (self::$messagesink) {
659             self::stop_message_redirection();
660         }
661         self::$messagesink = new phpunit_message_sink();
662         return self::$messagesink;
663     }
665     /**
666      * End message redirection.
667      *
668      * Note: Do not call directly from tests,
669      *       use $sink->close() instead.
670      */
671     public static function stop_message_redirection() {
672         self::$messagesink = null;
673     }
675     /**
676      * Are messages redirected to some sink?
677      *
678      * Note: to be called from messagelib.php only!
679      *
680      * @return bool
681      */
682     public static function is_redirecting_messages() {
683         return !empty(self::$messagesink);
684     }
686     /**
687      * To be called from messagelib.php only!
688      *
689      * @param stdClass $message record from message_read table
690      * @return bool true means send message, false means message "sent" to sink.
691      */
692     public static function message_sent($message) {
693         if (self::$messagesink) {
694             self::$messagesink->add_message($message);
695         }
696     }
698     /**
699      * Start phpmailer redirection.
700      *
701      * Note: Do not call directly from tests,
702      *       use $sink = $this->redirectEmails() instead.
703      *
704      * @return phpunit_phpmailer_sink
705      */
706     public static function start_phpmailer_redirection() {
707         if (self::$phpmailersink) {
708             // If an existing mailer sink is active, just clear it.
709             self::$phpmailersink->clear();
710         } else {
711             self::$phpmailersink = new phpunit_phpmailer_sink();
712         }
713         return self::$phpmailersink;
714     }
716     /**
717      * End phpmailer redirection.
718      *
719      * Note: Do not call directly from tests,
720      *       use $sink->close() instead.
721      */
722     public static function stop_phpmailer_redirection() {
723         self::$phpmailersink = null;
724     }
726     /**
727      * Are messages for phpmailer redirected to some sink?
728      *
729      * Note: to be called from moodle_phpmailer.php only!
730      *
731      * @return bool
732      */
733     public static function is_redirecting_phpmailer() {
734         return !empty(self::$phpmailersink);
735     }
737     /**
738      * To be called from messagelib.php only!
739      *
740      * @param stdClass $message record from message_read table
741      * @return bool true means send message, false means message "sent" to sink.
742      */
743     public static function phpmailer_sent($message) {
744         if (self::$phpmailersink) {
745             self::$phpmailersink->add_message($message);
746         }
747     }
749     /**
750      * Start event redirection.
751      *
752      * @private
753      * Note: Do not call directly from tests,
754      *       use $sink = $this->redirectEvents() instead.
755      *
756      * @return phpunit_event_sink
757      */
758     public static function start_event_redirection() {
759         if (self::$eventsink) {
760             self::stop_event_redirection();
761         }
762         self::$eventsink = new phpunit_event_sink();
763         return self::$eventsink;
764     }
766     /**
767      * End event redirection.
768      *
769      * @private
770      * Note: Do not call directly from tests,
771      *       use $sink->close() instead.
772      */
773     public static function stop_event_redirection() {
774         self::$eventsink = null;
775     }
777     /**
778      * Are events redirected to some sink?
779      *
780      * Note: to be called from \core\event\base only!
781      *
782      * @private
783      * @return bool
784      */
785     public static function is_redirecting_events() {
786         return !empty(self::$eventsink);
787     }
789     /**
790      * To be called from \core\event\base only!
791      *
792      * @private
793      * @param \core\event\base $event record from event_read table
794      * @return bool true means send event, false means event "sent" to sink.
795      */
796     public static function event_triggered(\core\event\base $event) {
797         if (self::$eventsink) {
798             self::$eventsink->add_event($event);
799         }
800     }