See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 39 and 401] [Versions 401 and 402] [Versions 401 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Advanced test case. 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 */ 25 26 27 /** 28 * Advanced PHPUnit test case customised for Moodle. 29 * 30 * @package core 31 * @category phpunit 32 * @copyright 2012 Petr Skoda {@link http://skodak.org} 33 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 34 */ 35 abstract class advanced_testcase extends base_testcase { 36 /** @var bool automatically reset everything? null means log changes */ 37 private $resetAfterTest; 38 39 /** @var moodle_transaction */ 40 private $testdbtransaction; 41 42 /** @var int timestamp used for current time asserts */ 43 private $currenttimestart; 44 45 /** 46 * Constructs a test case with the given name. 47 * 48 * Note: use setUp() or setUpBeforeClass() in your test cases. 49 * 50 * @param string $name 51 * @param array $data 52 * @param string $dataName 53 */ 54 final public function __construct($name = null, array $data = array(), $dataName = '') { 55 parent::__construct($name, $data, $dataName); 56 57 $this->setBackupGlobals(false); 58 $this->setBackupStaticAttributes(false); 59 $this->setPreserveGlobalState(false); 60 } 61 62 /** 63 * Runs the bare test sequence. 64 * @return void 65 */ 66 final public function runBare(): void { 67 global $DB; 68 69 if (phpunit_util::$lastdbwrites != $DB->perf_get_writes()) { 70 // this happens when previous test does not reset, we can not use transactions 71 $this->testdbtransaction = null; 72 73 } else if ($DB->get_dbfamily() === 'postgres' or $DB->get_dbfamily() === 'mssql') { 74 // database must allow rollback of DDL, so no mysql here 75 $this->testdbtransaction = $DB->start_delegated_transaction(); 76 } 77 78 try { 79 $this->setCurrentTimeStart(); 80 parent::runBare(); 81 // set DB reference in case somebody mocked it in test 82 $DB = phpunit_util::get_global_backup('DB'); 83 84 // Deal with any debugging messages. 85 $debugerror = phpunit_util::display_debugging_messages(true); 86 $this->resetDebugging(); 87 if (!empty($debugerror)) { 88 trigger_error('Unexpected debugging() call detected.'."\n".$debugerror, E_USER_NOTICE); 89 } 90 91 } catch (Exception $ex) { 92 $e = $ex; 93 } catch (Throwable $ex) { 94 // Engine errors in PHP7 throw exceptions of type Throwable (this "catch" will be ignored in PHP5). 95 $e = $ex; 96 } 97 98 if (isset($e)) { 99 // cleanup after failed expectation 100 self::resetAllData(); 101 throw $e; 102 } 103 104 if (!$this->testdbtransaction or $this->testdbtransaction->is_disposed()) { 105 $this->testdbtransaction = null; 106 } 107 108 if ($this->resetAfterTest === true) { 109 if ($this->testdbtransaction) { 110 $DB->force_transaction_rollback(); 111 phpunit_util::reset_all_database_sequences(); 112 phpunit_util::$lastdbwrites = $DB->perf_get_writes(); // no db reset necessary 113 } 114 self::resetAllData(null); 115 116 } else if ($this->resetAfterTest === false) { 117 if ($this->testdbtransaction) { 118 $this->testdbtransaction->allow_commit(); 119 } 120 // keep all data untouched for other tests 121 122 } else { 123 // reset but log what changed 124 if ($this->testdbtransaction) { 125 try { 126 $this->testdbtransaction->allow_commit(); 127 } catch (dml_transaction_exception $e) { 128 self::resetAllData(); 129 throw new coding_exception('Invalid transaction state detected in test '.$this->getName()); 130 } 131 } 132 self::resetAllData(true); 133 } 134 135 // Reset context cache. 136 context_helper::reset_caches(); 137 138 // make sure test did not forget to close transaction 139 if ($DB->is_transaction_started()) { 140 self::resetAllData(); 141 if ($this->getStatus() == PHPUnit\Runner\BaseTestRunner::STATUS_PASSED 142 or $this->getStatus() == PHPUnit\Runner\BaseTestRunner::STATUS_SKIPPED 143 or $this->getStatus() == PHPUnit\Runner\BaseTestRunner::STATUS_INCOMPLETE) { 144 throw new coding_exception('Test '.$this->getName().' did not close database transaction'); 145 } 146 } 147 } 148 149 /** 150 * Creates a new XMLDataSet with the given $xmlFile. (absolute path.) 151 * 152 * @deprecated since Moodle 3.10 - See MDL-67673 and MDL-64600 for more info. 153 * @todo This will be removed for Moodle 4.2 as part of MDL-69882. 154 * 155 * @param string $xmlFile 156 * @return phpunit_dataset 157 */ 158 protected function createXMLDataSet($xmlFile) { 159 debugging(__FUNCTION__ . '() is deprecated. Please use dataset_from_files() instead.', DEBUG_DEVELOPER); 160 return $this->dataset_from_files([$xmlFile]); 161 } 162 163 /** 164 * Creates a new CsvDataSet from the given array of csv files. (absolute paths.) 165 * 166 * @deprecated since Moodle 3.10 - See MDL-67673 and MDL-64600 for more info. 167 * @todo This will be removed for Moodle 4.2 as part of MDL-69882. 168 * 169 * @param array $files array tablename=>cvsfile 170 * @param string $delimiter unused 171 * @param string $enclosure unused 172 * @param string $escape unused 173 * @return phpunit_dataset 174 */ 175 protected function createCsvDataSet($files, $delimiter = ',', $enclosure = '"', $escape = '"') { 176 debugging(__FUNCTION__ . '() is deprecated. Please use dataset_from_files() instead.', DEBUG_DEVELOPER); 177 return $this->dataset_from_files($files); 178 } 179 180 /** 181 * Creates new ArrayDataSet from given array 182 * 183 * @deprecated since Moodle 3.10 - See MDL-67673 and MDL-64600 for more info. 184 * @todo This will be removed for Moodle 4.2 as part of MDL-69882. 185 * 186 * @param array $data array of tables, first row in each table is columns 187 * @return phpunit_dataset 188 */ 189 protected function createArrayDataSet(array $data) { 190 debugging(__FUNCTION__ . '() is deprecated. Please use dataset_from_array() instead.', DEBUG_DEVELOPER); 191 return $this->dataset_from_array($data); 192 } 193 194 /** 195 * Load date into moodle database tables from standard PHPUnit data set. 196 * 197 * @deprecated since Moodle 3.10 - See MDL-67673 and MDL-64600 for more info. 198 * @todo This will be removed for Moodle 4.2 as part of MDL-69882. 199 * 200 * Note: it is usually better to use data generators 201 * 202 * @param phpunit_dataset $dataset 203 * @return void 204 */ 205 protected function loadDataSet(phpunit_dataset $dataset) { 206 debugging(__FUNCTION__ . '() is deprecated. Please use dataset->to_database() instead.', DEBUG_DEVELOPER); 207 $dataset->to_database(); 208 } 209 210 /** 211 * Creates a new dataset from CVS/XML files. 212 * 213 * This method accepts an array of full paths to CSV or XML files to be loaded 214 * into the dataset. For CSV files, the name of the table which the file belongs 215 * to needs to be specified. Example: 216 * 217 * $fullpaths = [ 218 * '/path/to/users.xml', 219 * 'course' => '/path/to/courses.csv', 220 * ]; 221 * 222 * @since Moodle 3.10 223 * 224 * @param array $files full paths to CSV or XML files to load. 225 * @return phpunit_dataset 226 */ 227 protected function dataset_from_files(array $files) { 228 // We ignore $delimiter, $enclosure and $escape, use the default ones in your fixtures. 229 $dataset = new phpunit_dataset(); 230 $dataset->from_files($files); 231 return $dataset; 232 } 233 234 /** 235 * Creates a new dataset from string (CSV or XML). 236 * 237 * @since Moodle 3.10 238 * 239 * @param string $content contents (CSV or XML) to load. 240 * @param string $type format of the content to be loaded (csv or xml). 241 * @param string $table name of the table which the file belongs to (only for CSV files). 242 * @return phpunit_dataset 243 */ 244 protected function dataset_from_string(string $content, string $type, ?string $table = null) { 245 $dataset = new phpunit_dataset(); 246 $dataset->from_string($content, $type, $table); 247 return $dataset; 248 } 249 250 /** 251 * Creates a new dataset from PHP array. 252 * 253 * @since Moodle 3.10 254 * 255 * @param array $data array of tables, see {@see phpunit_dataset::from_array()} for supported formats. 256 * @return phpunit_dataset 257 */ 258 protected function dataset_from_array(array $data) { 259 $dataset = new phpunit_dataset(); 260 $dataset->from_array($data); 261 return $dataset; 262 } 263 264 /** 265 * Call this method from test if you want to make sure that 266 * the resetting of database is done the slow way without transaction 267 * rollback. 268 * 269 * This is useful especially when testing stuff that is not compatible with transactions. 270 * 271 * @return void 272 */ 273 public function preventResetByRollback() { 274 if ($this->testdbtransaction and !$this->testdbtransaction->is_disposed()) { 275 $this->testdbtransaction->allow_commit(); 276 $this->testdbtransaction = null; 277 } 278 } 279 280 /** 281 * Reset everything after current test. 282 * @param bool $reset true means reset state back, false means keep all data for the next test, 283 * null means reset state and show warnings if anything changed 284 * @return void 285 */ 286 public function resetAfterTest($reset = true) { 287 $this->resetAfterTest = $reset; 288 } 289 290 /** 291 * Return debugging messages from the current test. 292 * @return array with instances having 'message', 'level' and 'stacktrace' property. 293 */ 294 public function getDebuggingMessages() { 295 return phpunit_util::get_debugging_messages(); 296 } 297 298 /** 299 * Clear all previous debugging messages in current test 300 * and revert to default DEVELOPER_DEBUG level. 301 */ 302 public function resetDebugging() { 303 phpunit_util::reset_debugging(); 304 } 305 306 /** 307 * Assert that exactly debugging was just called once. 308 * 309 * Discards the debugging message if successful. 310 * 311 * @param null|string $debugmessage null means any 312 * @param null|string $debuglevel null means any 313 * @param string $message 314 */ 315 public function assertDebuggingCalled($debugmessage = null, $debuglevel = null, $message = '') { 316 $debugging = $this->getDebuggingMessages(); 317 $debugdisplaymessage = "\n".phpunit_util::display_debugging_messages(true); 318 $this->resetDebugging(); 319 320 $count = count($debugging); 321 322 if ($count == 0) { 323 if ($message === '') { 324 $message = 'Expectation failed, debugging() not triggered.'; 325 } 326 $this->fail($message); 327 } 328 if ($count > 1) { 329 if ($message === '') { 330 $message = 'Expectation failed, debugging() triggered '.$count.' times.'.$debugdisplaymessage; 331 } 332 $this->fail($message); 333 } 334 $this->assertEquals(1, $count); 335 336 $message .= $debugdisplaymessage; 337 $debug = reset($debugging); 338 if ($debugmessage !== null) { 339 $this->assertSame($debugmessage, $debug->message, $message); 340 } 341 if ($debuglevel !== null) { 342 $this->assertSame($debuglevel, $debug->level, $message); 343 } 344 } 345 346 /** 347 * Asserts how many times debugging has been called. 348 * 349 * @param int $expectedcount The expected number of times 350 * @param array $debugmessages Expected debugging messages, one for each expected message. 351 * @param array $debuglevels Expected debugging levels, one for each expected message. 352 * @param string $message 353 * @return void 354 */ 355 public function assertDebuggingCalledCount($expectedcount, $debugmessages = array(), $debuglevels = array(), $message = '') { 356 if (!is_int($expectedcount)) { 357 throw new coding_exception('assertDebuggingCalledCount $expectedcount argument should be an integer.'); 358 } 359 360 $debugging = $this->getDebuggingMessages(); 361 $message .= "\n".phpunit_util::display_debugging_messages(true); 362 $this->resetDebugging(); 363 364 $this->assertEquals($expectedcount, count($debugging), $message); 365 366 if ($debugmessages) { 367 if (!is_array($debugmessages) || count($debugmessages) != $expectedcount) { 368 throw new coding_exception('assertDebuggingCalledCount $debugmessages should contain ' . $expectedcount . ' messages'); 369 } 370 foreach ($debugmessages as $key => $debugmessage) { 371 $this->assertSame($debugmessage, $debugging[$key]->message, $message); 372 } 373 } 374 375 if ($debuglevels) { 376 if (!is_array($debuglevels) || count($debuglevels) != $expectedcount) { 377 throw new coding_exception('assertDebuggingCalledCount $debuglevels should contain ' . $expectedcount . ' messages'); 378 } 379 foreach ($debuglevels as $key => $debuglevel) { 380 $this->assertSame($debuglevel, $debugging[$key]->level, $message); 381 } 382 } 383 } 384 385 /** 386 * Call when no debugging() messages expected. 387 * @param string $message 388 */ 389 public function assertDebuggingNotCalled($message = '') { 390 $debugging = $this->getDebuggingMessages(); 391 $count = count($debugging); 392 393 if ($message === '') { 394 $message = 'Expectation failed, debugging() was triggered.'; 395 } 396 $message .= "\n".phpunit_util::display_debugging_messages(true); 397 $this->resetDebugging(); 398 $this->assertEquals(0, $count, $message); 399 } 400 401 /** 402 * Assert that an event legacy data is equal to the expected value. 403 * 404 * @param mixed $expected expected data. 405 * @param \core\event\base $event the event object. 406 * @param string $message 407 * @return void 408 */ 409 public function assertEventLegacyData($expected, \core\event\base $event, $message = '') { 410 $legacydata = phpunit_event_mock::testable_get_legacy_eventdata($event); 411 if ($message === '') { 412 $message = 'Event legacy data does not match expected value.'; 413 } 414 $this->assertEquals($expected, $legacydata, $message); 415 } 416 417 /** 418 * Assert that an event legacy log data is equal to the expected value. 419 * 420 * @param mixed $expected expected data. 421 * @param \core\event\base $event the event object. 422 * @param string $message 423 * @return void 424 */ 425 public function assertEventLegacyLogData($expected, \core\event\base $event, $message = '') { 426 $legacydata = phpunit_event_mock::testable_get_legacy_logdata($event); 427 if ($message === '') { 428 $message = 'Event legacy log data does not match expected value.'; 429 } 430 $this->assertEquals($expected, $legacydata, $message); 431 } 432 433 /** 434 * Assert that an event is not using event->contxet. 435 * While restoring context might not be valid and it should not be used by event url 436 * or description methods. 437 * 438 * @param \core\event\base $event the event object. 439 * @param string $message 440 * @return void 441 */ 442 public function assertEventContextNotUsed(\core\event\base $event, $message = '') { 443 // Save current event->context and set it to false. 444 $eventcontext = phpunit_event_mock::testable_get_event_context($event); 445 phpunit_event_mock::testable_set_event_context($event, false); 446 if ($message === '') { 447 $message = 'Event should not use context property of event in any method.'; 448 } 449 450 // Test event methods should not use event->context. 451 $event->get_url(); 452 $event->get_description(); 453 $event->get_legacy_eventname(); 454 phpunit_event_mock::testable_get_legacy_eventdata($event); 455 phpunit_event_mock::testable_get_legacy_logdata($event); 456 457 // Restore event->context. 458 phpunit_event_mock::testable_set_event_context($event, $eventcontext); 459 } 460 461 /** 462 * Stores current time as the base for assertTimeCurrent(). 463 * 464 * Note: this is called automatically before calling individual test methods. 465 * @return int current time 466 */ 467 public function setCurrentTimeStart() { 468 $this->currenttimestart = time(); 469 return $this->currenttimestart; 470 } 471 472 /** 473 * Assert that: start < $time < time() 474 * @param int $time 475 * @param string $message 476 * @return void 477 */ 478 public function assertTimeCurrent($time, $message = '') { 479 $msg = ($message === '') ? 'Time is lower that allowed start value' : $message; 480 $this->assertGreaterThanOrEqual($this->currenttimestart, $time, $msg); 481 $msg = ($message === '') ? 'Time is in the future' : $message; 482 $this->assertLessThanOrEqual(time(), $time, $msg); 483 } 484 485 /** 486 * Starts message redirection. 487 * 488 * You can verify if messages were sent or not by inspecting the messages 489 * array in the returned messaging sink instance. The redirection 490 * can be stopped by calling $sink->close(); 491 * 492 * @return phpunit_message_sink 493 */ 494 public function redirectMessages() { 495 return phpunit_util::start_message_redirection(); 496 } 497 498 /** 499 * Starts email redirection. 500 * 501 * You can verify if email were sent or not by inspecting the email 502 * array in the returned phpmailer sink instance. The redirection 503 * can be stopped by calling $sink->close(); 504 * 505 * @return phpunit_message_sink 506 */ 507 public function redirectEmails() { 508 return phpunit_util::start_phpmailer_redirection(); 509 } 510 511 /** 512 * Starts event redirection. 513 * 514 * You can verify if events were triggered or not by inspecting the events 515 * array in the returned event sink instance. The redirection 516 * can be stopped by calling $sink->close(); 517 * 518 * @return phpunit_event_sink 519 */ 520 public function redirectEvents() { 521 return phpunit_util::start_event_redirection(); 522 } 523 524 /** 525 * Reset all database tables, restore global state and clear caches and optionally purge dataroot dir. 526 * 527 * @param bool $detectchanges 528 * true - changes in global state and database are reported as errors 529 * false - no errors reported 530 * null - only critical problems are reported as errors 531 * @return void 532 */ 533 public static function resetAllData($detectchanges = false) { 534 phpunit_util::reset_all_data($detectchanges); 535 } 536 537 /** 538 * Set current $USER, reset access cache. 539 * @static 540 * @param null|int|stdClass $user user record, null or 0 means non-logged-in, positive integer means userid 541 * @return void 542 */ 543 public static function setUser($user = null) { 544 global $CFG, $DB; 545 546 if (is_object($user)) { 547 $user = clone($user); 548 } else if (!$user) { 549 $user = new stdClass(); 550 $user->id = 0; 551 $user->mnethostid = $CFG->mnet_localhost_id; 552 } else { 553 $user = $DB->get_record('user', array('id'=>$user)); 554 } 555 unset($user->description); 556 unset($user->access); 557 unset($user->preference); 558 559 // Enusre session is empty, as it may contain caches and user specific info. 560 \core\session\manager::init_empty_session(); 561 562 \core\session\manager::set_user($user); 563 } 564 565 /** 566 * Set current $USER to admin account, reset access cache. 567 * @static 568 * @return void 569 */ 570 public static function setAdminUser() { 571 self::setUser(2); 572 } 573 574 /** 575 * Set current $USER to guest account, reset access cache. 576 * @static 577 * @return void 578 */ 579 public static function setGuestUser() { 580 self::setUser(1); 581 } 582 583 /** 584 * Change server and default php timezones. 585 * 586 * @param string $servertimezone timezone to set in $CFG->timezone (not validated) 587 * @param string $defaultphptimezone timezone to fake default php timezone (must be valid) 588 */ 589 public static function setTimezone($servertimezone = 'Australia/Perth', $defaultphptimezone = 'Australia/Perth') { 590 global $CFG; 591 $CFG->timezone = $servertimezone; 592 core_date::phpunit_override_default_php_timezone($defaultphptimezone); 593 core_date::set_default_server_timezone(); 594 } 595 596 /** 597 * Get data generator 598 * @static 599 * @return testing_data_generator 600 */ 601 public static function getDataGenerator() { 602 return phpunit_util::get_data_generator(); 603 } 604 605 /** 606 * Returns UTL of the external test file. 607 * 608 * The result depends on the value of following constants: 609 * - TEST_EXTERNAL_FILES_HTTP_URL 610 * - TEST_EXTERNAL_FILES_HTTPS_URL 611 * 612 * They should point to standard external test files repository, 613 * it defaults to 'http://download.moodle.org/unittest'. 614 * 615 * False value means skip tests that require external files. 616 * 617 * @param string $path 618 * @param bool $https true if https required 619 * @return string url 620 */ 621 public function getExternalTestFileUrl($path, $https = false) { 622 $path = ltrim($path, '/'); 623 if ($path) { 624 $path = '/'.$path; 625 } 626 if ($https) { 627 if (defined('TEST_EXTERNAL_FILES_HTTPS_URL')) { 628 if (!TEST_EXTERNAL_FILES_HTTPS_URL) { 629 $this->markTestSkipped('Tests using external https test files are disabled'); 630 } 631 return TEST_EXTERNAL_FILES_HTTPS_URL.$path; 632 } 633 return 'https://download.moodle.org/unittest'.$path; 634 } 635 636 if (defined('TEST_EXTERNAL_FILES_HTTP_URL')) { 637 if (!TEST_EXTERNAL_FILES_HTTP_URL) { 638 $this->markTestSkipped('Tests using external http test files are disabled'); 639 } 640 return TEST_EXTERNAL_FILES_HTTP_URL.$path; 641 } 642 return 'http://download.moodle.org/unittest'.$path; 643 } 644 645 /** 646 * Recursively visit all the files in the source tree. Calls the callback 647 * function with the pathname of each file found. 648 * 649 * @param string $path the folder to start searching from. 650 * @param string $callback the method of this class to call with the name of each file found. 651 * @param string $fileregexp a regexp used to filter the search (optional). 652 * @param bool $exclude If true, pathnames that match the regexp will be ignored. If false, 653 * only files that match the regexp will be included. (default false). 654 * @param array $ignorefolders will not go into any of these folders (optional). 655 * @return void 656 */ 657 public function recurseFolders($path, $callback, $fileregexp = '/.*/', $exclude = false, $ignorefolders = array()) { 658 $files = scandir($path); 659 660 foreach ($files as $file) { 661 $filepath = $path .'/'. $file; 662 if (strpos($file, '.') === 0) { 663 /// Don't check hidden files. 664 continue; 665 } else if (is_dir($filepath)) { 666 if (!in_array($filepath, $ignorefolders)) { 667 $this->recurseFolders($filepath, $callback, $fileregexp, $exclude, $ignorefolders); 668 } 669 } else if ($exclude xor preg_match($fileregexp, $filepath)) { 670 $this->$callback($filepath); 671 } 672 } 673 } 674 675 /** 676 * Wait for a second to roll over, ensures future calls to time() return a different result. 677 * 678 * This is implemented instead of sleep() as we do not need to wait a full second. In some cases 679 * due to calls we may wait more than sleep() would have, on average it will be less. 680 */ 681 public function waitForSecond() { 682 $starttime = time(); 683 while (time() == $starttime) { 684 usleep(50000); 685 } 686 } 687 688 /** 689 * Run adhoc tasks, optionally matching the specified classname. 690 * 691 * @param string $matchclass The name of the class to match on. 692 * @param int $matchuserid The userid to match. 693 */ 694 protected function runAdhocTasks($matchclass = '', $matchuserid = null) { 695 global $CFG, $DB; 696 require_once($CFG->libdir.'/cronlib.php'); 697 698 $params = []; 699 if (!empty($matchclass)) { 700 if (strpos($matchclass, '\\') !== 0) { 701 $matchclass = '\\' . $matchclass; 702 } 703 $params['classname'] = $matchclass; 704 } 705 706 if (!empty($matchuserid)) { 707 $params['userid'] = $matchuserid; 708 } 709 710 $lock = $this->createMock(\core\lock\lock::class); 711 $cronlock = $this->createMock(\core\lock\lock::class); 712 713 $tasks = $DB->get_recordset('task_adhoc', $params); 714 foreach ($tasks as $record) { 715 // Note: This is for cron only. 716 // We do not lock the tasks. 717 $task = \core\task\manager::adhoc_task_from_record($record); 718 719 $user = null; 720 if ($userid = $task->get_userid()) { 721 // This task has a userid specified. 722 $user = \core_user::get_user($userid); 723 724 // User found. Check that they are suitable. 725 \core_user::require_active_user($user, true, true); 726 } 727 728 $task->set_lock($lock); 729 if (!$task->is_blocking()) { 730 $cronlock->release(); 731 } else { 732 $task->set_cron_lock($cronlock); 733 } 734 735 cron_prepare_core_renderer(); 736 cron_setup_user($user); 737 738 $task->execute(); 739 \core\task\manager::adhoc_task_complete($task); 740 741 unset($task); 742 } 743 $tasks->close(); 744 } 745 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body