ILIAS  release_5-3 Revision v5.3.23-19-g915713cf615
class.ilCronManager.php
Go to the documentation of this file.
1 <?php
2 
3 /* Copyright (c) 1998-2010 ILIAS open source, Extended GPL, see docs/LICENSE */
4 
12 {
16  public static function runActiveJobs()
17  {
18  global $ilLog, $ilSetting;
19 
20  // separate log for cron
21  // $this->log->setFilename($_COOKIE["ilClientId"]."_cron.txt");
22 
23  $ilLog->write("CRON - batch start");
24 
25  $ts = time();
26  $ilSetting->set("last_cronjob_start_ts", $ts);
27 
28  $useRelativeDates = ilDatePresentation::useRelativeDates();
30  $ilLog->info(sprintf('Set last datetime to: %s', ilDatePresentation::formatDate(new ilDateTime($ts, IL_CAL_UNIX))));
31  $ilLog->info(sprintf(
32  'Verification of last run datetime (read from database): %s',
34  new ilDateTime(ilSetting::_lookupValue('common', 'last_cronjob_start_ts'), IL_CAL_UNIX)
35  )
36  ));
37  ilDatePresentation::setUseRelativeDates((bool) $useRelativeDates);
38 
39  // ilLink::_getStaticLink() should work in crons
40  if (!defined("ILIAS_HTTP_PATH")) {
41  define("ILIAS_HTTP_PATH", ilUtil::_getHttpPath());
42  }
43 
44  // system
45  foreach (self::getCronJobData(null, false) as $row) {
46  $job = self::getJobInstanceById($row["job_id"]);
47  if ($job) {
48  // #18411 - we are NOT using the initial job data as it might be outdated at this point
49  self::runJob($job);
50  }
51  }
52 
53  // plugins
54  foreach (self::getPluginJobs(true) as $item) {
55  self::runJob($item[0], $item[1]);
56  }
57 
58  $ilLog->write("CRON - batch end");
59  }
60 
67  public static function runJobManual($a_job_id)
68  {
69  global $ilLog;
70 
71  $result = false;
72 
73  $ilLog->write("CRON - manual start (" . $a_job_id . ")");
74 
75  $job = self::getJobInstanceById($a_job_id);
76  if ($job) {
77  if ($job->isManuallyExecutable()) {
78  $result = self::runJob($job, null, true);
79  } else {
80  $ilLog->write("CRON - job " . $a_job_id . " is not intended to be executed manually");
81  }
82  } else {
83  $ilLog->write("CRON - job " . $a_job_id . " seems invalid or is inactive");
84  }
85 
86  $ilLog->write("CRON - manual end (" . $a_job_id . ")");
87 
88  return $result;
89  }
90 
99  protected static function runJob(ilCronJob $a_job, array $a_job_data = null, $a_manual = false)
100  {
101  global $ilLog, $ilDB;
102 
103  $did_run = false;
104 
105  include_once "Services/Cron/classes/class.ilCronJobResult.php";
106 
107  if ($a_job_data === null) {
108  // aquire "fresh" job (status) data
109  $a_job_data = array_pop(self::getCronJobData($a_job->getId()));
110  }
111 
112  // already running?
113  if ($a_job_data["alive_ts"]) {
114  $ilLog->write("CRON - job " . $a_job_data["job_id"] . " still running");
115 
116  $cut = 60*60*3; // 3h
117 
118  // is running (and has not pinged) for 3 hours straight, we assume it crashed
119  if (time()-$a_job_data["alive_ts"] > $cut) {
120  $ilDB->manipulate("UPDATE cron_job SET" .
121  " running_ts = " . $ilDB->quote(0, "integer") .
122  " , alive_ts = " . $ilDB->quote(0, "integer") .
123  " WHERE job_id = " . $ilDB->quote($a_job_data["job_id"], "text"));
124 
125  self::deactivateJob($a_job); // #13082
126 
127  $result = new ilCronJobResult();
130  $result->setMessage("Cron job deactivated because it has been inactive for 3 hours");
131 
132  if (!$a_manual) {
133  self::sendNotification($a_job, $result);
134  }
135 
136  self::updateJobResult($a_job, $result, $a_manual);
137 
138  $ilLog->write("CRON - job " . $a_job_data["job_id"] . " deactivated (assumed crash)");
139  }
140  }
141  // initiate run?
142  elseif ($a_job->isActive(
143  $a_job_data["job_result_ts"],
144  $a_job_data["schedule_type"],
145  $a_job_data["schedule_value"],
146  $a_manual
147  )) {
148  $ilLog->write("CRON - job " . $a_job_data["job_id"] . " started");
149 
150  $ilDB->manipulate("UPDATE cron_job SET" .
151  " running_ts = " . $ilDB->quote(time(), "integer") .
152  " , alive_ts = " . $ilDB->quote(time(), "integer") .
153  " WHERE job_id = " . $ilDB->quote($a_job_data["job_id"], "text"));
154 
155  $ts_in = self::getMicrotime();
156  try {
157  $result = $a_job->run();
158  } catch (\Exception $e) {
159  $result = new \ilCronJobResult();
161  $result->setMessage(sprintf("Exception: %s", $e->getMessage()));
162 
163  $ilLog->error($e->getMessage());
164  $ilLog->error($e->getTraceAsString());
165  } catch (\Throwable $e) { // Could be appended to the catch block with a | in PHP 7.1
166  $result = new \ilCronJobResult();
168  $result->setMessage(sprintf("Exception: %s", $e->getMessage()));
169 
170  $ilLog->error($e->getMessage());
171  $ilLog->error($e->getTraceAsString());
172  }
173  $ts_dur = self::getMicrotime()-$ts_in;
174 
175  // no proper result
176  if (!$result instanceof ilCronJobResult) {
177  $result = new ilCronJobResult();
180  $result->setMessage("Cron job did not return a proper result");
181 
182  if (!$a_manual) {
183  self::sendNotification($a_job, $result);
184  }
185 
186  $ilLog->write("CRON - job " . $a_job_data["job_id"] . " no result");
187  }
188  // no valid configuration, job won't work
189  elseif ($result->getStatus() == ilCronJobResult::STATUS_INVALID_CONFIGURATION) {
190  self::deactivateJob($a_job);
191 
192  if (!$a_manual) {
193  self::sendNotification($a_job, $result);
194  }
195 
196  $ilLog->write("CRON - job " . $a_job_data["job_id"] . " invalid configuration");
197  }
198  // success!
199  else {
200  $did_run = true;
201  }
202 
203  $result->setDuration($ts_dur);
204 
205  self::updateJobResult($a_job, $result, $a_manual);
206 
207  $ilDB->manipulate("UPDATE cron_job SET" .
208  " running_ts = " . $ilDB->quote(0, "integer") .
209  " , alive_ts = " . $ilDB->quote(0, "integer") .
210  " WHERE job_id = " . $ilDB->quote($a_job_data["job_id"], "text"));
211 
212  $ilLog->write("CRON - job " . $a_job_data["job_id"] . " finished");
213  } else {
214  $ilLog->write("CRON - job " . $a_job_data["job_id"] . " returned status inactive");
215  }
216 
217  return $did_run;
218  }
219 
226  public static function getJobInstanceById($a_job_id)
227  {
228  global $ilLog, $ilPluginAdmin;
229 
230  // plugin
231  if (substr($a_job_id, 0, 4) == "pl__") {
232  $parts = explode("__", $a_job_id);
233  $pl_name = $parts[1];
234  $job_id = $parts[2];
235  if ($ilPluginAdmin->isActive(IL_COMP_SERVICE, "Cron", "crnhk", $pl_name)) {
236  $plugin_obj = $ilPluginAdmin->getPluginObject(
238  "Cron",
239  "crnhk",
240  $pl_name
241  );
242  $job = $plugin_obj->getCronJobInstance($job_id);
243  if ($job instanceof ilCronJob) {
244  // should never happen but who knows...
245  if (!sizeof(ilCronManager::getCronJobData($job_id))) {
246  // as job is not "imported" from xml
248  }
249  return $job;
250  }
251  }
252 
253  return null;
254  }
255  // system
256  else {
257  $job_data = array_pop(self::getCronJobData($a_job_id));
258  if ($job_data["job_id"] == $a_job_id) {
259  return self::getJobInstance(
260  $job_data["job_id"],
261  $job_data["component"],
262  $job_data["class"],
263  $job_data["path"]
264  );
265  }
266  }
267 
268  $ilLog->write("CRON - job " . $a_job_id . " seems invalid or is inactive");
269  }
270 
279  public static function getJobInstance($a_id, $a_component, $a_class, $a_path = null)
280  {
281  global $ilLog;
282 
283  if (!$a_path) {
284  $a_path = $a_component . "/classes/";
285  }
286  $class_file = $a_path . "class." . $a_class . ".php";
287  if (file_exists($class_file)) {
288  include_once $class_file;
289  if (class_exists($a_class)) {
290  $job = new $a_class();
291  if ($job instanceof ilCronJob) {
292  if ($job->getId() == $a_id) {
293  return $job;
294  } else {
295  $mess .= " - job id mismatch";
296  }
297  } else {
298  $mess .= " - does not extend ilCronJob";
299  }
300  } else {
301  $mess = "- class not found in file";
302  }
303  } else {
304  $mess = " - class file not found";
305  }
306 
307  $ilLog->write("Cron XML - Job " . $a_id . " in class " . $a_class . " (" .
308  $class_file . ") is invalid." . $mess);
309  }
310 
317  protected static function sendNotification(ilCronJob $a_job, $a_message)
318  {
319  // :TODO:
320  }
321 
322  public static function createDefaultEntry(ilCronJob $a_job, $a_component, $a_class, $a_path)
323  {
324  global $ilDB, $ilLog, $ilSetting;
325 
326  // already exists?
327  $sql = "SELECT job_id, schedule_type FROM cron_job" .
328  " WHERE component = " . $ilDB->quote($a_component, "text") .
329  " AND job_id = " . $ilDB->quote($a_job->getId(), "text");
330  $set = $ilDB->query($sql);
331  $row = $ilDB->fetchAssoc($set);
332  $job_exists = ($row["job_id"] == $a_job->getId());
333  $schedule_type = $row["schedule_type"];
334 
335  // new job
336  if (!$job_exists) {
337  $sql = "INSERT INTO cron_job (job_id, component, class, path)" .
338  " VALUES (" . $ilDB->quote($a_job->getId(), "text") . ", " .
339  $ilDB->quote($a_component, "text") . ", " .
340  $ilDB->quote($a_class, "text") . ", " .
341  $ilDB->quote($a_path, "text") . ")";
342  $ilDB->manipulate($sql);
343 
344  $ilLog->write("Cron XML - Job " . $a_job->getId() . " in class " . $a_class .
345  " added.");
346 
347  // only if flexible
348  self::updateJobSchedule(
349  $a_job,
350  $a_job->getDefaultScheduleType(),
351  $a_job->getDefaultScheduleValue()
352  );
353 
354  // #12221
355  if (!is_object($ilSetting)) {
356  include_once "Services/Administration/classes/class.ilSetting.php";
357  $ilSetting = new ilSetting();
358  }
359 
360  if ($a_job->hasAutoActivation()) {
361  self::activateJob($a_job);
362  } else {
363  // to overwrite dependent settings
364  $a_job->activationWasToggled(false);
365  }
366  }
367  // existing job - but schedule is flexible now
368  elseif ($a_job->hasFlexibleSchedule() && !$schedule_type) {
369  self::updateJobSchedule(
370  $a_job,
371  $a_job->getDefaultScheduleType(),
372  $a_job->getDefaultScheduleValue()
373  );
374  }
375  // existing job - but schedule is static now
376  elseif (!$a_job->hasFlexibleSchedule() && $schedule_type) {
377  self::updateJobSchedule($a_job, null, null);
378  }
379  }
380 
389  public static function updateFromXML($a_component, $a_id, $a_class, $a_path = null)
390  {
391  global $ilDB;
392 
393  if (!$ilDB->tableExists("cron_job")) {
394  return;
395  }
396 
397  // only if job seems valid
398  $job = self::getJobInstance($a_id, $a_component, $a_class, $a_path);
399  if ($job) {
400  self::createDefaultEntry($job, $a_component, $a_class, $a_path);
401  }
402  }
403 
410  public static function clearFromXML($a_component, array $a_xml_job_ids)
411  {
412  global $ilDB, $ilLog;
413 
414  if (!$ilDB->tableExists("cron_job")) {
415  return;
416  }
417 
418  // gather existing jobs
419  $all_jobs = array();
420  $sql = "SELECT job_id FROM cron_job" .
421  " WHERE component = " . $ilDB->quote($a_component, "text");
422  $set = $ilDB->query($sql);
423  while ($row = $ilDB->fetchAssoc($set)) {
424  $all_jobs[] = $row["job_id"];
425  }
426 
427  if (sizeof($all_jobs)) {
428  if (sizeof($a_xml_job_ids)) {
429  // delete obsolete job data
430  foreach ($all_jobs as $job_id) {
431  if (!in_array($job_id, $a_xml_job_ids)) {
432  $ilDB->manipulate("DELETE FROM cron_job" .
433  " WHERE component = " . $ilDB->quote($a_component, "text") .
434  " AND job_id = " . $ilDB->quote($job_id, "text"));
435 
436  $ilLog->write("Cron XML - Job " . $job_id . " in class " . $a_component .
437  " deleted.");
438  }
439  }
440  } else {
441  $ilDB->manipulate("DELETE FROM cron_job" .
442  " WHERE component = " . $ilDB->quote($a_component, "text"));
443 
444  $ilLog->write("Cron XML - All jobs deleted for " . $a_component . " as component is inactive.");
445  }
446  }
447  }
448 
449  public static function getPluginJobs($a_only_active = false)
450  {
451  global $ilPluginAdmin;
452 
453  $res = array();
454 
455  foreach ($ilPluginAdmin->getActivePluginsForSlot(IL_COMP_SERVICE, "Cron", "crnhk") as $pl_name) {
456  $plugin_obj = $ilPluginAdmin->getPluginObject(IL_COMP_SERVICE, "Cron", "crnhk", $pl_name);
457 
458  foreach ((array) $plugin_obj->getCronJobInstances() as $job) {
459  $item = array_pop(ilCronManager::getCronJobData($job->getId()));
460  if (!is_array($item) || 0 === count($item)) {
461  // as job is not "imported" from xml
463  }
464 
465  $item = array_pop(ilCronManager::getCronJobData($job->getId()));
466 
467  // #17941
468  if (!$a_only_active ||
469  $item["job_status"] == 1) {
470  $res[$job->getId()] = array($job, $item);
471  }
472  }
473  }
474 
475  return $res;
476  }
477 
485  public static function getCronJobData($a_id = null, $a_include_inactive = true)
486  {
487  global $ilDB;
488 
489  $res = array();
490 
491  if ($a_id && !is_array($a_id)) {
492  $a_id = array($a_id);
493  }
494 
495  $sql = "SELECT * FROM cron_job";
496 
497  $where = array();
498  if ($a_id) {
499  $where[] = $ilDB->in("job_id", $a_id, "", "text");
500  } else {
501  $where[] = "class <> " . $ilDB->quote(IL_COMP_PLUGIN, "text");
502  }
503  if (!$a_include_inactive) {
504  $where[] = "job_status = " . $ilDB->quote(1, "integer");
505  }
506  if (sizeof($where)) {
507  $sql .= " WHERE " . implode(" AND ", $where);
508  }
509 
510  // :TODO: discuss job execution order
511  $sql .= " ORDER BY job_id";
512 
513  $set = $ilDB->query($sql);
514  while ($row = $ilDB->fetchAssoc($set)) {
515  $res[] = $row;
516  }
517 
518  return $res;
519  }
520 
526  public static function resetJob(ilCronJob $a_job)
527  {
528  global $ilDB;
529 
530  include_once "Services/Cron/classes/class.ilCronJobResult.php";
531  $result = new ilCronJobResult();
534  $result->setMessage("Cron job re-activated by admin");
535  self::updateJobResult($a_job, $result, true);
536 
537  $ilDB->manipulate("UPDATE cron_job" .
538  " SET running_ts = " . $ilDB->quote(0, "integer") .
539  " , alive_ts = " . $ilDB->quote(0, "integer") .
540  " , job_result_ts = " . $ilDB->quote(0, "integer") .
541  " WHERE job_id = " . $ilDB->quote($a_job->getId(), "text"));
542 
543  self::activateJob($a_job, true);
544  }
545 
552  public static function activateJob(ilCronJob $a_job, $a_manual = false)
553  {
554  global $ilDB, $ilUser;
555 
556  $user_id = $a_manual ? $ilUser->getId() : 0;
557 
558  $sql = "UPDATE cron_job SET " .
559  " job_status = " . $ilDB->quote(1, "integer") .
560  " , job_status_user_id = " . $ilDB->quote($user_id, "integer") .
561  " , job_status_type = " . $ilDB->quote($a_manual, "integer") .
562  " , job_status_ts = " . $ilDB->quote(time(), "integer") .
563  " WHERE job_id = " . $ilDB->quote($a_job->getId(), "text");
564  $ilDB->manipulate($sql);
565 
566  $a_job->activationWasToggled(true);
567  }
568 
575  public static function deactivateJob(ilCronJob $a_job, $a_manual = false)
576  {
577  global $ilDB, $ilUser;
578 
579  $user_id = $a_manual ? $ilUser->getId() : 0;
580 
581  $sql = "UPDATE cron_job SET " .
582  " job_status = " . $ilDB->quote(0, "integer") .
583  " , job_status_user_id = " . $ilDB->quote($user_id, "integer") .
584  " , job_status_type = " . $ilDB->quote($a_manual, "integer") .
585  " , job_status_ts = " . $ilDB->quote(time(), "integer") .
586  " WHERE job_id = " . $ilDB->quote($a_job->getId(), "text");
587  $ilDB->manipulate($sql);
588 
589  $a_job->activationWasToggled(false);
590  }
591 
598  public static function isJobActive($a_job_id)
599  {
600  $job = self::getCronJobData($a_job_id);
601  if ((bool) $job[0]["job_status"]) {
602  return true;
603  }
604  return false;
605  }
606 
613  public static function isJobInactive($a_job_id)
614  {
615  $job = self::getCronJobData($a_job_id);
616  if (!(bool) $job[0]["job_status"]) {
617  return true;
618  }
619  return false;
620  }
621 
629  protected static function updateJobResult(ilCronJob $a_job, ilCronJobResult $a_result, $a_manual = false)
630  {
631  global $ilDB, $ilUser;
632 
633  $user_id = $a_manual ? $ilUser->getId() : 0;
634 
635  $sql = "UPDATE cron_job SET " .
636  " job_result_status = " . $ilDB->quote($a_result->getStatus(), "integer") .
637  " , job_result_user_id = " . $ilDB->quote($user_id, "integer") .
638  " , job_result_code = " . $ilDB->quote($a_result->getCode(), "text") .
639  " , job_result_message = " . $ilDB->quote($a_result->getMessage(), "text") .
640  " , job_result_type = " . $ilDB->quote($a_manual, "integer") .
641  " , job_result_ts = " . $ilDB->quote(time(), "integer") .
642  " , job_result_dur = " . $ilDB->quote($a_result->getDuration()*1000, "integer") .
643  " WHERE job_id = " . $ilDB->quote($a_job->getId(), "text");
644  $ilDB->manipulate($sql);
645  }
646 
654  public static function updateJobSchedule(ilCronJob $a_job, $a_schedule_type, $a_schedule_value)
655  {
656  global $ilDB;
657 
658  if ($a_schedule_type === null ||
659  ($a_job->hasFlexibleSchedule() &&
660  in_array($a_schedule_type, $a_job->getValidScheduleTypes()))) {
661  $sql = "UPDATE cron_job SET " .
662  " schedule_type = " . $ilDB->quote($a_schedule_type, "integer") .
663  " , schedule_value = " . $ilDB->quote($a_schedule_value, "integer") .
664  " WHERE job_id = " . $ilDB->quote($a_job->getId(), "text");
665  $ilDB->manipulate($sql);
666  }
667  }
668 
674  protected static function getMicrotime()
675  {
676  list($usec, $sec) = explode(" ", microtime());
677  return ((float) $usec + (float) $sec);
678  }
679 
685  public static function ping($a_job_id)
686  {
687  global $ilDB;
688 
689  $ilDB->manipulate("UPDATE cron_job SET " .
690  " alive_ts = " . $ilDB->quote(time(), "integer") .
691  " WHERE job_id = " . $ilDB->quote($a_job_id, "text"));
692  }
693 }
static getJobInstance($a_id, $a_component, $a_class, $a_path=null)
Get job instance (by job data)
static runActiveJobs()
Run all active jobs.
run()
Run job.
static updateFromXML($a_component, $a_id, $a_class, $a_path=null)
Process data from module.xml/service.xml.
static sendNotification(ilCronJob $a_job, $a_message)
Send notification to admin about job event(s)
getValidScheduleTypes()
Get all available schedule types.
$result
getId()
Get id.
Cron job application base class.
static deactivateJob(ilCronJob $a_job, $a_manual=false)
Deactivate cron job.
static formatDate(ilDateTime $date, $a_skip_day=false, $a_include_wd=false)
Format a date public.
static runJob(ilCronJob $a_job, array $a_job_data=null, $a_manual=false)
Run single cron job (internal)
const IL_COMP_PLUGIN
static activateJob(ilCronJob $a_job, $a_manual=false)
Activate cron job.
static isJobInactive($a_job_id)
Check if given job is currently inactive.
static setUseRelativeDates($a_status)
set use relative dates
static runJobManual($a_job_id)
Run single job manually.
activationWasToggled($a_currently_active)
Cron job status was changed.
const IL_CAL_UNIX
static useRelativeDates()
check if relative dates are used
static resetJob(ilCronJob $a_job)
Reset job.
static _lookupValue($a_module, $a_keyword)
Cron management.
static getCronJobData($a_id=null, $a_include_inactive=true)
Get cron job configuration/execution data.
foreach($_POST as $key=> $value) $res
static getJobInstanceById($a_job_id)
Get job instance (by job id)
hasFlexibleSchedule()
Can the schedule be configured?
static createDefaultEntry(ilCronJob $a_job, $a_component, $a_class, $a_path)
isActive($a_ts_last_run, $a_schedule_type, $a_schedule_value, $a_manual=false)
Is job currently active?
Date and time handling
$ilUser
Definition: imgupload.php:18
Create styles array
The data for the language used.
static updateJobResult(ilCronJob $a_job, ilCronJobResult $a_result, $a_manual=false)
Save job result.
getDefaultScheduleType()
Get schedule type.
static _getHttpPath()
static ping($a_job_id)
Keep cron job alive.
global $ilSetting
Definition: privfeed.php:17
static getPluginJobs($a_only_active=false)
static getMicrotime()
Get current microtime.
static clearFromXML($a_component, array $a_xml_job_ids)
Clear job data.
global $ilDB
getDefaultScheduleValue()
Get schedule value.
static updateJobSchedule(ilCronJob $a_job, $a_schedule_type, $a_schedule_value)
Update job schedule.
Add data(end) time
Method that wraps PHPs time in order to allow simulations with the workflow.
Cron job result data container.
defined( 'APPLICATION_ENV')||define( 'APPLICATION_ENV'
Definition: bootstrap.php:27
static isJobActive($a_job_id)
Check if given job is currently active.
hasAutoActivation()
Is to be activated on "installation".
const IL_COMP_SERVICE