ILIAS  release_6 Revision v6.24-5-g0c8bfefb3b8
class.ilCronManager.php
Go to the documentation of this file.
1<?php
2/* Copyright (c) 1998-2010 ILIAS open source, Extended GPL, see docs/LICENSE */
3
11{
15 protected $settings;
16
20 protected $logger;
21
28 {
29 $this->settings = $settings;
30 $this->logger = $logger;
31 }
32
36 public function runActiveJobs()
37 {
38 $this->logger->info("CRON - batch start");
39
40 $ts = time();
41 $this->settings->set("last_cronjob_start_ts", $ts);
42
43 $useRelativeDates = ilDatePresentation::useRelativeDates();
45 $this->logger->info(sprintf('Set last datetime to: %s', ilDatePresentation::formatDate(new ilDateTime($ts, IL_CAL_UNIX))));
46 $this->logger->info(sprintf(
47 'Verification of last run datetime (read from database): %s',
49 new ilDateTime(ilSetting::_lookupValue('common', 'last_cronjob_start_ts'), IL_CAL_UNIX)
50 )
51 ));
52 ilDatePresentation::setUseRelativeDates((bool) $useRelativeDates);
53
54 // ilLink::_getStaticLink() should work in crons
55 if (!defined("ILIAS_HTTP_PATH")) {
56 define("ILIAS_HTTP_PATH", ilUtil::_getHttpPath());
57 }
58
59 // system
60 foreach (self::getCronJobData(null, false) as $row) {
61 $job = self::getJobInstanceById($row["job_id"]);
62 if ($job) {
63 // #18411 - we are NOT using the initial job data as it might be outdated at this point
64 self::runJob($job);
65 }
66 }
67
68 // plugins
69 foreach (self::getPluginJobs(true) as $item) {
70 self::runJob($item[0], $item[1]);
71 }
72
73 $this->logger->info("CRON - batch end");
74 }
75
82 public static function runJobManual($a_job_id)
83 {
84 global $DIC;
85
86 $ilLog = $DIC->logger()->root();
87
88 $result = false;
89
90 $ilLog->write("CRON - manual start (" . $a_job_id . ")");
91
92 $job = self::getJobInstanceById($a_job_id);
93 if ($job) {
94 if ($job->isManuallyExecutable()) {
95 $result = self::runJob($job, null, true);
96 } else {
97 $ilLog->write("CRON - job " . $a_job_id . " is not intended to be executed manually");
98 }
99 } else {
100 $ilLog->write("CRON - job " . $a_job_id . " seems invalid or is inactive");
101 }
102
103 $ilLog->write("CRON - manual end (" . $a_job_id . ")");
104
105 return $result;
106 }
107
116 protected static function runJob(ilCronJob $a_job, array $a_job_data = null, $a_manual = false)
117 {
118 global $DIC;
119
120 $ilLog = $DIC->logger()->root();
121 $ilDB = $DIC->database();
122
123 $did_run = false;
124
125 include_once "Services/Cron/classes/class.ilCronJobResult.php";
126
127 if ($a_job_data === null) {
128 // aquire "fresh" job (status) data
129 $jobData = self::getCronJobData($a_job->getId());
130 $a_job_data = array_pop($jobData);
131 }
132
133 // already running?
134 if ($a_job_data["alive_ts"]) {
135 $ilLog->write("CRON - job " . $a_job_data["job_id"] . " still running");
136
137 $cut = 60 * 60 * 3; // 3h
138
139 // is running (and has not pinged) for 3 hours straight, we assume it crashed
140 if (time() - $a_job_data["alive_ts"] > $cut) {
141 $ilDB->manipulate("UPDATE cron_job SET" .
142 " running_ts = " . $ilDB->quote(0, "integer") .
143 " , alive_ts = " . $ilDB->quote(0, "integer") .
144 " WHERE job_id = " . $ilDB->quote($a_job_data["job_id"], "text"));
145
146 self::deactivateJob($a_job); // #13082
147
148 $result = new ilCronJobResult();
151 $result->setMessage("Cron job deactivated because it has been inactive for 3 hours");
152
153 if (!$a_manual) {
155 }
156
157 self::updateJobResult($a_job, $result, $a_manual);
158
159 $ilLog->write("CRON - job " . $a_job_data["job_id"] . " deactivated (assumed crash)");
160 }
161 }
162 // initiate run?
163 elseif ($a_job->isActive(
164 $a_job_data["job_result_ts"],
165 $a_job_data["schedule_type"],
166 $a_job_data["schedule_value"],
167 $a_manual
168 )) {
169 $ilLog->write("CRON - job " . $a_job_data["job_id"] . " started");
170
171 $ilDB->manipulate("UPDATE cron_job SET" .
172 " running_ts = " . $ilDB->quote(time(), "integer") .
173 " , alive_ts = " . $ilDB->quote(time(), "integer") .
174 " WHERE job_id = " . $ilDB->quote($a_job_data["job_id"], "text"));
175
176 $ts_in = self::getMicrotime();
177 try {
178 $result = $a_job->run();
179 } catch (\Exception $e) {
180 $result = new \ilCronJobResult();
182 $result->setMessage(sprintf("Exception: %s", $e->getMessage()));
183
184 $ilLog->error($e->getMessage());
185 $ilLog->error($e->getTraceAsString());
186 } catch (\Throwable $e) { // Could be appended to the catch block with a | in PHP 7.1
187 $result = new \ilCronJobResult();
189 $result->setMessage(sprintf("Exception: %s", $e->getMessage()));
190
191 $ilLog->error($e->getMessage());
192 $ilLog->error($e->getTraceAsString());
193 }
194 $ts_dur = self::getMicrotime() - $ts_in;
195
196 // no proper result
197 if (!$result instanceof ilCronJobResult) {
198 $result = new ilCronJobResult();
201 $result->setMessage("Cron job did not return a proper result");
202
203 if (!$a_manual) {
205 }
206
207 $ilLog->write("CRON - job " . $a_job_data["job_id"] . " no result");
208 }
209 // no valid configuration, job won't work
211 self::deactivateJob($a_job);
212
213 if (!$a_manual) {
215 }
216
217 $ilLog->write("CRON - job " . $a_job_data["job_id"] . " invalid configuration");
218 }
219 // success!
220 else {
221 $did_run = true;
222 }
223
224 $result->setDuration($ts_dur);
225
226 self::updateJobResult($a_job, $result, $a_manual);
227
228 $ilDB->manipulate("UPDATE cron_job SET" .
229 " running_ts = " . $ilDB->quote(0, "integer") .
230 " , alive_ts = " . $ilDB->quote(0, "integer") .
231 " WHERE job_id = " . $ilDB->quote($a_job_data["job_id"], "text"));
232
233 $ilLog->write("CRON - job " . $a_job_data["job_id"] . " finished");
234 } else {
235 $ilLog->write("CRON - job " . $a_job_data["job_id"] . " returned status inactive");
236 }
237
238 return $did_run;
239 }
240
247 public static function getJobInstanceById($a_job_id)
248 {
249 global $DIC;
250
251 $ilLog = $DIC->logger()->root();
252 $ilPluginAdmin = $DIC['ilPluginAdmin'];
253
254 // plugin
255 if (substr($a_job_id, 0, 4) == "pl__") {
256 $parts = explode("__", $a_job_id);
257 $pl_name = $parts[1];
258 $job_id = $parts[2];
259 if ($ilPluginAdmin->isActive(IL_COMP_SERVICE, "Cron", "crnhk", $pl_name)) {
260 $plugin_obj = $ilPluginAdmin->getPluginObject(
262 "Cron",
263 "crnhk",
264 $pl_name
265 );
266 $job = $plugin_obj->getCronJobInstance($job_id);
267 if ($job instanceof ilCronJob) {
268 // should never happen but who knows...
269 if (!sizeof(ilCronManager::getCronJobData($job_id))) {
270 // as job is not "imported" from xml
272 }
273 return $job;
274 }
275 }
276
277 return null;
278 }
279 // system
280 else {
281 $job_data = self::getCronJobData($a_job_id);
282 $job_data = array_pop($job_data);
283 if ($job_data["job_id"] == $a_job_id) {
285 $job_data["job_id"],
286 $job_data["component"],
287 $job_data["class"],
288 $job_data["path"]
289 );
290 }
291 }
292
293 $ilLog->write("CRON - job " . $a_job_id . " seems invalid or is inactive");
294 }
295
304 public static function getJobInstance($a_id, $a_component, $a_class, $a_path = null)
305 {
306 global $DIC;
307
308 $ilLog = $DIC->logger()->root();
309
310 if (!$a_path) {
311 $a_path = $a_component . "/classes/";
312 }
313 $class_file = $a_path . "class." . $a_class . ".php";
314 if (file_exists($class_file)) {
315 include_once $class_file;
316 if (class_exists($a_class)) {
317 $refl = new \ReflectionClass($a_class);
318 $job = $refl->newInstanceWithoutConstructor();
319 if ($refl->isSubclassOf(\ilCronJob::class)) {
320 if (0 === strlen($job->getId()) || !isset($_SERVER['PHP_SELF']) || basename($_SERVER['PHP_SELF']) !== 'setup.php') {
321 $job = new $a_class;
322 }
323
324 if ($job->getId() === $a_id) {
325 return $job;
326 } else {
327 $mess .= " - job id mismatch";
328 }
329 } else {
330 $mess .= " - does not extend ilCronJob";
331 }
332 } else {
333 $mess = "- class not found in file";
334 }
335 } else {
336 $mess = " - class file not found";
337 }
338
339 $ilLog->write("Cron XML - Job " . $a_id . " in class " . $a_class . " (" .
340 $class_file . ") is invalid." . $mess);
341 }
342
349 protected static function sendNotification(ilCronJob $a_job, $a_message)
350 {
351 // :TODO:
352 }
353
354 public static function createDefaultEntry(ilCronJob $a_job, $a_component, $a_class, $a_path)
355 {
356 global $DIC;
357
358 $ilLog = $DIC->logger()->root();
359 $ilDB = $DIC->database();
360
361 if (!isset($DIC["ilSetting"])) {
362 $DIC["ilSetting"] = function ($c) {
363 return new ilSetting();
364 };
365 }
366
367 $ilSetting = $DIC->settings();
368
369 // already exists?
370 $sql = "SELECT job_id, schedule_type, component, class, path FROM cron_job" .
371 " WHERE job_id = " . $ilDB->quote($a_job->getId(), "text");
372 $set = $ilDB->query($sql);
373 $row = $ilDB->fetchAssoc($set);
374 $job_exists = ($row["job_id"] == $a_job->getId());
375 $schedule_type = $row["schedule_type"];
376
377 if ($job_exists && (
378 $row['component'] != $a_component ||
379 $row['class'] != $a_class ||
380 $row['path'] != $a_path
381 )) {
382 $ilDB->manipulateF(
383 'UPDATE cron_job SET component = %s, class = %s, path = %s WHERE job_id = %s',
384 ['text', 'text', 'text', 'text'],
385 [$a_component, $a_class, $a_path, $a_job->getId()]
386 );
387 }
388
389 // new job
390 if (!$job_exists) {
391 $sql = "INSERT INTO cron_job (job_id, component, class, path)" .
392 " VALUES (" . $ilDB->quote($a_job->getId(), "text") . ", " .
393 $ilDB->quote($a_component, "text") . ", " .
394 $ilDB->quote($a_class, "text") . ", " .
395 $ilDB->quote($a_path, "text") . ")";
396 $ilDB->manipulate($sql);
397
398 $ilLog->write("Cron XML - Job " . $a_job->getId() . " in class " . $a_class .
399 " added.");
400
401 // only if flexible
403 $a_job,
404 $a_job->getDefaultScheduleType(),
406 );
407
408 // #12221
409 if (!is_object($ilSetting)) {
410 include_once "Services/Administration/classes/class.ilSetting.php";
411 $ilSetting = new ilSetting();
412 }
413
414 if ($a_job->hasAutoActivation()) {
415 self::activateJob($a_job);
416 } else {
417 // to overwrite dependent settings
418 $a_job->activationWasToggled(false);
419 }
420 }
421 // existing job - but schedule is flexible now
422 elseif ($a_job->hasFlexibleSchedule() && !$schedule_type) {
424 $a_job,
425 $a_job->getDefaultScheduleType(),
427 );
428 }
429 // existing job - but schedule is static now
430 elseif (!$a_job->hasFlexibleSchedule() && $schedule_type) {
431 self::updateJobSchedule($a_job, null, null);
432 }
433 }
434
443 public static function updateFromXML($a_component, $a_id, $a_class, $a_path = null)
444 {
445 global $DIC;
446
447 $ilDB = $DIC->database();
448
449 if (!$ilDB->tableExists("cron_job")) {
450 return;
451 }
452
453 // only if job seems valid
454 $job = self::getJobInstance($a_id, $a_component, $a_class, $a_path);
455 if ($job) {
456 self::createDefaultEntry($job, $a_component, $a_class, $a_path);
457 }
458 }
459
466 public static function clearFromXML($a_component, array $a_xml_job_ids)
467 {
468 global $DIC;
469
470 $ilDB = $DIC->database();
471 $ilLog = $DIC->logger()->root();
472
473 if (!$ilDB->tableExists("cron_job")) {
474 return;
475 }
476
477 // gather existing jobs
478 $all_jobs = array();
479 $sql = "SELECT job_id FROM cron_job" .
480 " WHERE component = " . $ilDB->quote($a_component, "text");
481 $set = $ilDB->query($sql);
482 while ($row = $ilDB->fetchAssoc($set)) {
483 $all_jobs[] = $row["job_id"];
484 }
485
486 if (sizeof($all_jobs)) {
487 if (sizeof($a_xml_job_ids)) {
488 // delete obsolete job data
489 foreach ($all_jobs as $job_id) {
490 if (!in_array($job_id, $a_xml_job_ids)) {
491 $ilDB->manipulate("DELETE FROM cron_job" .
492 " WHERE component = " . $ilDB->quote($a_component, "text") .
493 " AND job_id = " . $ilDB->quote($job_id, "text"));
494
495 $ilLog->write("Cron XML - Job " . $job_id . " in class " . $a_component .
496 " deleted.");
497 }
498 }
499 } else {
500 $ilDB->manipulate("DELETE FROM cron_job" .
501 " WHERE component = " . $ilDB->quote($a_component, "text"));
502
503 $ilLog->write("Cron XML - All jobs deleted for " . $a_component . " as component is inactive.");
504 }
505 }
506 }
507
508 public static function getPluginJobs($a_only_active = false)
509 {
510 global $DIC;
511
512 $ilPluginAdmin = $DIC['ilPluginAdmin'];
513
514 $res = array();
515
516 foreach ($ilPluginAdmin->getActivePluginsForSlot(IL_COMP_SERVICE, "Cron", "crnhk") as $pl_name) {
517 $plugin_obj = $ilPluginAdmin->getPluginObject(IL_COMP_SERVICE, "Cron", "crnhk", $pl_name);
518
519 foreach ((array) $plugin_obj->getCronJobInstances() as $job) {
520 $jobData = ilCronManager::getCronJobData($job->getId());
521 $item = array_pop($jobData);
522 if (!is_array($item) || 0 === count($item)) {
523 // as job is not "imported" from xml
525 }
526
527 $jobData = ilCronManager::getCronJobData($job->getId());
528 $item = array_pop($jobData);
529
530 // #17941
531 if (!$a_only_active ||
532 $item["job_status"] == 1) {
533 $res[$job->getId()] = array($job, $item);
534 }
535 }
536 }
537
538 return $res;
539 }
540
548 public static function getCronJobData($a_id = null, $a_include_inactive = true)
549 {
550 global $DIC;
551 $ilDB = $DIC->database();
552
553 $res = array();
554
555 if ($a_id && !is_array($a_id)) {
556 $a_id = array($a_id);
557 }
558
559 $sql = "SELECT * FROM cron_job";
560
561 $where = array();
562 if ($a_id) {
563 $where[] = $ilDB->in("job_id", $a_id, "", "text");
564 } else {
565 $where[] = "class <> " . $ilDB->quote(IL_COMP_PLUGIN, "text");
566 }
567 if (!$a_include_inactive) {
568 $where[] = "job_status = " . $ilDB->quote(1, "integer");
569 }
570 if (sizeof($where)) {
571 $sql .= " WHERE " . implode(" AND ", $where);
572 }
573
574 // :TODO: discuss job execution order
575 $sql .= " ORDER BY job_id";
576
577 $set = $ilDB->query($sql);
578 while ($row = $ilDB->fetchAssoc($set)) {
579 $res[] = $row;
580 }
581
582 return $res;
583 }
584
590 public static function resetJob(ilCronJob $a_job)
591 {
592 global $DIC;
593 $ilDB = $DIC->database();
594
595 include_once "Services/Cron/classes/class.ilCronJobResult.php";
596 $result = new ilCronJobResult();
599 $result->setMessage("Cron job re-activated by admin");
600 self::updateJobResult($a_job, $result, true);
601
602 $ilDB->manipulate("UPDATE cron_job" .
603 " SET running_ts = " . $ilDB->quote(0, "integer") .
604 " , alive_ts = " . $ilDB->quote(0, "integer") .
605 " , job_result_ts = " . $ilDB->quote(0, "integer") .
606 " WHERE job_id = " . $ilDB->quote($a_job->getId(), "text"));
607
608 self::activateJob($a_job, true);
609 }
610
617 public static function activateJob(ilCronJob $a_job, $a_manual = false)
618 {
619 global $DIC;
620 $ilDB = $DIC->database();
621
622 $user_id = 0;
623 if ($DIC->isDependencyAvailable('user')) {
624 $user = $DIC->user();
625 $user_id = $a_manual ? $user->getId() : 0;
626 }
627
628 $sql = "UPDATE cron_job SET " .
629 " job_status = " . $ilDB->quote(1, "integer") .
630 " , job_status_user_id = " . $ilDB->quote($user_id, "integer") .
631 " , job_status_type = " . $ilDB->quote($a_manual, "integer") .
632 " , job_status_ts = " . $ilDB->quote(time(), "integer") .
633 " WHERE job_id = " . $ilDB->quote($a_job->getId(), "text");
634 $ilDB->manipulate($sql);
635
636 $a_job->activationWasToggled(true);
637 }
638
645 public static function deactivateJob(ilCronJob $a_job, $a_manual = false)
646 {
647 global $DIC;
648 $ilDB = $DIC->database();
649 $ilUser = $DIC->user();
650
651 $user_id = $a_manual ? $ilUser->getId() : 0;
652
653 $sql = "UPDATE cron_job SET " .
654 " job_status = " . $ilDB->quote(0, "integer") .
655 " , job_status_user_id = " . $ilDB->quote($user_id, "integer") .
656 " , job_status_type = " . $ilDB->quote($a_manual, "integer") .
657 " , job_status_ts = " . $ilDB->quote(time(), "integer") .
658 " WHERE job_id = " . $ilDB->quote($a_job->getId(), "text");
659 $ilDB->manipulate($sql);
660
661 $a_job->activationWasToggled(false);
662 }
663
670 public static function isJobActive($a_job_id)
671 {
672 $job = self::getCronJobData($a_job_id);
673 if ((bool) $job[0]["job_status"]) {
674 return true;
675 }
676 return false;
677 }
678
685 public static function isJobInactive($a_job_id)
686 {
687 $job = self::getCronJobData($a_job_id);
688 if (!(bool) $job[0]["job_status"]) {
689 return true;
690 }
691 return false;
692 }
693
701 protected static function updateJobResult(ilCronJob $a_job, ilCronJobResult $a_result, $a_manual = false)
702 {
703 global $DIC;
704 $ilDB = $DIC->database();
705 $ilUser = $DIC->user();
706
707 $user_id = $a_manual ? $ilUser->getId() : 0;
708
709 $sql = "UPDATE cron_job SET " .
710 " job_result_status = " . $ilDB->quote($a_result->getStatus(), "integer") .
711 " , job_result_user_id = " . $ilDB->quote($user_id, "integer") .
712 " , job_result_code = " . $ilDB->quote($a_result->getCode(), "text") .
713 " , job_result_message = " . $ilDB->quote($a_result->getMessage(), "text") .
714 " , job_result_type = " . $ilDB->quote($a_manual, "integer") .
715 " , job_result_ts = " . $ilDB->quote(time(), "integer") .
716 " , job_result_dur = " . $ilDB->quote($a_result->getDuration() * 1000, "integer") .
717 " WHERE job_id = " . $ilDB->quote($a_job->getId(), "text");
718 $ilDB->manipulate($sql);
719 }
720
728 public static function updateJobSchedule(ilCronJob $a_job, $a_schedule_type, $a_schedule_value)
729 {
730 global $DIC;
731 $ilDB = $DIC->database();
732
733 if ($a_schedule_type === null ||
734 ($a_job->hasFlexibleSchedule() &&
735 in_array($a_schedule_type, $a_job->getValidScheduleTypes()))) {
736 $sql = "UPDATE cron_job SET " .
737 " schedule_type = " . $ilDB->quote($a_schedule_type, "integer") .
738 " , schedule_value = " . $ilDB->quote($a_schedule_value, "integer") .
739 " WHERE job_id = " . $ilDB->quote($a_job->getId(), "text");
740 $ilDB->manipulate($sql);
741 }
742 }
743
749 protected static function getMicrotime()
750 {
751 list($usec, $sec) = explode(" ", microtime());
752 return ((float) $usec + (float) $sec);
753 }
754
760 public static function ping($a_job_id)
761 {
762 global $DIC;
763 $ilDB = $DIC->database();
764
765 $ilDB->manipulate("UPDATE cron_job SET " .
766 " alive_ts = " . $ilDB->quote(time(), "integer") .
767 " WHERE job_id = " . $ilDB->quote($a_job_id, "text"));
768 }
769}
$result
An exception for terminatinating execution or to throw for unit testing.
const IL_COMP_SERVICE
const IL_COMP_PLUGIN
const IL_CAL_UNIX
Cron job result data container.
Cron job application base class.
getDefaultScheduleType()
Get schedule type.
activationWasToggled($a_currently_active)
Cron job status was changed.
run()
Run job.
getDefaultScheduleValue()
Get schedule value.
getId()
Get id.
isActive($a_ts_last_run, $a_schedule_type, $a_schedule_value, $a_manual=false)
Is job currently active?
hasAutoActivation()
Is to be activated on "installation".
hasFlexibleSchedule()
Can the schedule be configured?
getValidScheduleTypes()
Returns a collection of all valid schedule types for a specific job.
Cron management.
static getCronJobData($a_id=null, $a_include_inactive=true)
Get cron job configuration/execution data.
static sendNotification(ilCronJob $a_job, $a_message)
Send notification to admin about job event(s)
static updateFromXML($a_component, $a_id, $a_class, $a_path=null)
Process data from module.xml/service.xml.
static createDefaultEntry(ilCronJob $a_job, $a_component, $a_class, $a_path)
static isJobInactive($a_job_id)
Check if given job is currently inactive.
static getJobInstanceById($a_job_id)
Get job instance (by job id)
static runJobManual($a_job_id)
Run single job manually.
static activateJob(ilCronJob $a_job, $a_manual=false)
Activate cron job.
__construct(\ilSetting $settings, \ilLogger $logger)
ilCronManager constructor.
static getJobInstance($a_id, $a_component, $a_class, $a_path=null)
Get job instance (by job data)
static isJobActive($a_job_id)
Check if given job is currently active.
static deactivateJob(ilCronJob $a_job, $a_manual=false)
Deactivate cron job.
static updateJobResult(ilCronJob $a_job, ilCronJobResult $a_result, $a_manual=false)
Save job result.
static clearFromXML($a_component, array $a_xml_job_ids)
Clear job data.
static resetJob(ilCronJob $a_job)
Reset job.
runActiveJobs()
Run all active jobs.
static ping($a_job_id)
Keep cron job alive.
static updateJobSchedule(ilCronJob $a_job, $a_schedule_type, $a_schedule_value)
Update job schedule.
static runJob(ilCronJob $a_job, array $a_job_data=null, $a_manual=false)
Run single cron job (internal)
static getMicrotime()
Get current microtime.
static getPluginJobs($a_only_active=false)
static formatDate(ilDateTime $date, $a_skip_day=false, $a_include_wd=false, $include_seconds=false)
Format a date @access public.
static setUseRelativeDates($a_status)
set use relative dates
static useRelativeDates()
check if relative dates are used
@classDescription Date and time handling
Component logger with individual log levels by component id.
ILIAS Setting Class.
static _lookupValue($a_module, $a_keyword)
static _getHttpPath()
Class ilStrictCliCronManager.
global $ilSetting
Definition: privfeed.php:17
$_SERVER['HTTP_HOST']
Definition: raiseError.php:10
foreach($_POST as $key=> $value) $res
settings()
Definition: settings.php:2
global $ilDB
$ilUser
Definition: imgupload.php:18
$DIC
Definition: xapitoken.php:46