ILIAS  release_9 Revision v9.13-25-g2c18ec4c24f
class.ilCronJobRepositoryImpl.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
22 
24 {
25  private const TYPE_PLUGINS = 'Plugins';
26 
27  public function __construct(
28  private readonly ilDBInterface $db,
29  private readonly ilSetting $setting,
30  private readonly ilLogger $logger,
31  private readonly ilComponentRepository $componentRepository,
32  private readonly ilComponentFactory $componentFactory
33  ) {
34  }
35 
36  public function getJobInstanceById(string $id): ?ilCronJob
37  {
38  // plugin
39  if (str_starts_with($id, 'pl__')) {
40  $parts = explode('__', $id);
41  $pl_name = $parts[1];
42  $job_id = $parts[2];
43 
44  foreach ($this->componentRepository->getPlugins() as $pl) {
45  if ($pl->getName() !== $pl_name || !$pl->isActive()) {
46  continue;
47  }
48 
49  $plugin = $this->componentFactory->getPlugin($pl->getId());
50  if (!$plugin instanceof ilCronJobProvider) {
51  continue;
52  }
53 
54  try {
55  $job = $plugin->getCronJobInstance($job_id);
56 
57  // should never happen but who knows...
58  $jobs_data = $this->getCronJobData($job_id);
59  if ($jobs_data === []) {
60  // as job is not 'imported' from xml
61  $this->createDefaultEntry($job, $pl_name, self::TYPE_PLUGINS, '');
62  }
63 
64  return $job;
65  } catch (OutOfBoundsException) {
66  // Maybe a job was removed from plugin, renamed etc.
67  }
68  break;
69  }
70  } else {
71  $jobs_data = $this->getCronJobData($id);
72  if ($jobs_data !== [] && $jobs_data[0]['job_id'] === $id) {
73  return $this->getJobInstance(
74  $jobs_data[0]['job_id'],
75  $jobs_data[0]['component'],
76  $jobs_data[0]['class']
77  );
78  }
79  }
80 
81  $this->logger->info('CRON - job ' . $id . ' seems invalid or is inactive');
82 
83  return null;
84  }
85 
86  public function getJobInstance(
87  string $a_id,
88  string $a_component,
89  string $a_class,
90  bool $isCreationContext = false
91  ): ?ilCronJob {
92  if (class_exists($a_class)) {
93  if ($isCreationContext) {
94  $refl = new ReflectionClass($a_class);
95  $job = $refl->newInstanceWithoutConstructor();
96  } else {
97  $job = new $a_class();
98  }
99 
100  if ($job instanceof ilCronJob && $job->getId() === $a_id) {
101  return $job;
102  }
103  }
104 
105  return null;
106  }
107 
108  public function getCronJobData($id = null, bool $withInactiveJobsIncluded = true): array
109  {
110  $jobData = [];
111 
112  if ($id && !is_array($id)) {
113  $id = [$id];
114  }
115 
116  $query = "SELECT * FROM cron_job";
117  $where = [];
118  if ($id) {
119  $where[] = $this->db->in('job_id', $id, false, ilDBConstants::T_TEXT);
120  } else {
121  $where[] = 'class != ' . $this->db->quote(self::TYPE_PLUGINS, ilDBConstants::T_TEXT);
122  }
123  if (!$withInactiveJobsIncluded) {
124  $where[] = 'job_status = ' . $this->db->quote(1, ilDBConstants::T_INTEGER);
125  }
126  if ($where !== []) {
127  $query .= ' WHERE ' . implode(' AND ', $where);
128  }
129  // :TODO: discuss job execution order
130  $query .= ' ORDER BY job_id';
131 
132  $res = $this->db->query($query);
133  while ($row = $this->db->fetchAssoc($res)) {
134  $jobData[] = $row;
135  }
136 
137  return $jobData;
138  }
139 
140  public function registerJob(
141  string $a_component,
142  string $a_id,
143  string $a_class,
144  ?string $a_path
145  ): void {
146  if (!$this->db->tableExists('cron_job')) {
147  return;
148  }
149 
150  $job = $this->getJobInstance($a_id, $a_component, $a_class, true);
151  if ($job) {
152  $this->createDefaultEntry($job, $a_component, $a_class, $a_path);
153  }
154  }
155 
156  public function unregisterJob(string $a_component, array $a_xml_job_ids): void
157  {
158  if (!$this->db->tableExists('cron_job')) {
159  return;
160  }
161 
162  $jobs = [];
163  $query = 'SELECT job_id FROM cron_job WHERE component = ' . $this->db->quote($a_component, 'text');
164  $res = $this->db->query($query);
165  while ($row = $this->db->fetchAssoc($res)) {
166  $jobs[] = $row['job_id'];
167  }
168 
169  if ($jobs !== []) {
170  if ($a_xml_job_ids !== []) {
171  foreach ($jobs as $job_id) {
172  if (!in_array($job_id, $a_xml_job_ids, true)) {
173  $this->db->manipulate(
174  'DELETE FROM cron_job' .
175  ' WHERE component = ' . $this->db->quote($a_component, 'text') .
176  ' AND job_id = ' . $this->db->quote($job_id, 'text')
177  );
178  }
179  }
180  } else {
181  $this->db->manipulate(
182  'DELETE FROM cron_job WHERE component = ' . $this->db->quote($a_component, 'text')
183  );
184  }
185  }
186  }
187 
188  public function createDefaultEntry(
189  ilCronJob $job,
190  string $component,
191  string $class,
192  ?string $path
193  ): void {
194  $query = "SELECT job_id, schedule_type, component, class, path FROM cron_job" .
195  " WHERE job_id = " . $this->db->quote($job->getId(), "text");
196  $res = $this->db->query($query);
197  $row = $this->db->fetchAssoc($res);
198  $job_id = $row['job_id'] ?? null;
199  $job_exists = ($job_id === $job->getId());
200  $schedule_type_value = $row['schedule_type'] ?? null;
201  $schedule_type = is_numeric($schedule_type_value) ? CronJobScheduleType::tryFrom((int) $schedule_type_value) : null;
202 
203  if (
204  $job_exists && (
205  $row['component'] !== $component ||
206  $row['class'] !== $class ||
207  $row['path'] !== $path
208  )
209  ) {
210  $this->db->manipulateF(
211  'UPDATE cron_job SET component = %s, class = %s, path = %s WHERE job_id = %s',
212  ['text', 'text', 'text', 'text'],
213  [$component, $class, $path, $job->getId()]
214  );
215  }
216 
217  // new job
218  if (!$job_exists) {
219  $query = 'INSERT INTO cron_job (job_id, component, class, path)' .
220  ' VALUES (' . $this->db->quote($job->getId(), 'text') . ', ' .
221  $this->db->quote($component, 'text') . ', ' .
222  $this->db->quote($class, 'text') . ', ' .
223  $this->db->quote($path, 'text') . ')';
224  $this->db->manipulate($query);
225 
226  $this->logger->info('Cron XML - Job ' . $job->getId() . ' in class ' . $class . ' added.');
227 
228  // only if flexible
229  $this->updateJobSchedule(
230  $job,
231  $job->getDefaultScheduleType(),
233  );
234 
235  if ($job->hasAutoActivation()) {
236  $this->activateJob($job, new DateTimeImmutable('@' . time()));
237  $job->activationWasToggled($this->db, $this->setting, true);
238  } else {
239  // to overwrite dependent settings
240  $job->activationWasToggled($this->db, $this->setting, false);
241  }
242  } elseif ($schedule_type === null && $job->hasFlexibleSchedule()) {
243  // existing job - but schedule is flexible now
244  $this->updateJobSchedule(
245  $job,
246  $job->getDefaultScheduleType(),
248  );
249  } elseif ($schedule_type !== null && !$job->hasFlexibleSchedule()) {
250  // existing job - but schedule is not flexible anymore
251  $this->updateJobSchedule($job, null, null);
252  }
253  }
254 
258  public function getPluginJobs(bool $withOnlyActive = false): array
259  {
260  $res = [];
261  foreach ($this->componentRepository->getPlugins() as $pl) {
262  if (!$pl->isActive()) {
263  continue;
264  }
265 
266  $plugin = $this->componentFactory->getPlugin($pl->getId());
267 
268  if (!$plugin instanceof ilCronJobProvider) {
269  continue;
270  }
271 
272  foreach ($plugin->getCronJobInstances() as $job) {
273  $jobs_data = $this->getCronJobData($job->getId());
274  $job_data = $jobs_data[0] ?? null;
275  if (!is_array($job_data) || $job_data === []) {
276  // as job is not "imported" from xml
277  $this->createDefaultEntry($job, $plugin->getPluginName(), self::TYPE_PLUGINS, '');
278  }
279 
280  $jobs_data = $this->getCronJobData($job->getId());
281  $job_data = $jobs_data[0];
282 
283  // #17941
284  if (!$withOnlyActive || (int) $job_data['job_status'] === 1) {
285  $res[$job->getId()] = [$job, $job_data];
286  }
287  }
288  }
289 
290  return $res;
291  }
292 
293  public function resetJob(ilCronJob $job): void
294  {
295  $this->db->manipulate(
296  'UPDATE cron_job' .
297  ' SET running_ts = ' . $this->db->quote(0, 'integer') .
298  ' , alive_ts = ' . $this->db->quote(0, 'integer') .
299  ' , job_result_ts = ' . $this->db->quote(0, 'integer') .
300  ' WHERE job_id = ' . $this->db->quote($job->getId(), 'text')
301  );
302  }
303 
304  public function updateJobResult(
305  ilCronJob $job,
306  DateTimeImmutable $when,
307  ilObjUser $actor,
308  ilCronJobResult $result,
309  bool $wasManualExecution = false
310  ): void {
311  $user_id = $wasManualExecution ? $actor->getId() : 0;
312 
313  $query = 'UPDATE cron_job SET ' .
314  ' job_result_status = ' . $this->db->quote($result->getStatus(), 'integer') .
315  ' , job_result_user_id = ' . $this->db->quote($user_id, 'integer') .
316  ' , job_result_code = ' . $this->db->quote($result->getCode(), 'text') .
317  ' , job_result_message = ' . $this->db->quote($result->getMessage(), 'text') .
318  ' , job_result_type = ' . $this->db->quote((int) $wasManualExecution, 'integer') .
319  ' , job_result_ts = ' . $this->db->quote($when->getTimestamp(), 'integer') .
320  ' , job_result_dur = ' . $this->db->quote($result->getDuration() * 1000, 'integer') .
321  ' WHERE job_id = ' . $this->db->quote($job->getId(), 'text');
322  $this->db->manipulate($query);
323  }
324 
325  public function updateRunInformation(string $jobId, int $runningTimestamp, int $aliveTimestamp): void
326  {
327  $this->db->manipulate(
328  'UPDATE cron_job SET' .
329  ' running_ts = ' . $this->db->quote($runningTimestamp, 'integer') .
330  ' , alive_ts = ' . $this->db->quote($aliveTimestamp, 'integer') .
331  ' WHERE job_id = ' . $this->db->quote($jobId, 'text')
332  );
333  }
334 
335  public function updateJobSchedule(ilCronJob $job, ?CronJobScheduleType $scheduleType, ?int $scheduleValue): void
336  {
337  if (
338  $scheduleType === null ||
339  ($job->hasFlexibleSchedule() && in_array($scheduleType, $job->getValidScheduleTypes(), true))
340  ) {
341  $query = 'UPDATE cron_job SET ' .
342  ' schedule_type = ' . $this->db->quote($scheduleType?->value, 'integer') .
343  ' , schedule_value = ' . $this->db->quote($scheduleValue, 'integer') .
344  ' WHERE job_id = ' . $this->db->quote($job->getId(), 'text');
345  $this->db->manipulate($query);
346  }
347  }
348 
349  public function activateJob(
350  ilCronJob $job,
351  DateTimeImmutable $when,
352  ?ilObjUser $actor = null,
353  bool $wasManuallyExecuted = false
354  ): void {
355  $usrId = 0;
356  if ($wasManuallyExecuted && $actor instanceof ilObjUser) {
357  $usrId = $actor->getId();
358  }
359 
360  $query = 'UPDATE cron_job SET ' .
361  ' job_status = ' . $this->db->quote(1, 'integer') .
362  ' , job_status_user_id = ' . $this->db->quote($usrId, 'integer') .
363  ' , job_status_type = ' . $this->db->quote($wasManuallyExecuted, 'integer') .
364  ' , job_status_ts = ' . $this->db->quote($when->getTimestamp(), 'integer') .
365  ' WHERE job_id = ' . $this->db->quote($job->getId(), 'text');
366  $this->db->manipulate($query);
367  }
368 
369  public function deactivateJob(
370  ilCronJob $job,
371  DateTimeImmutable $when,
372  ilObjUser $actor,
373  bool $wasManuallyExecuted = false
374  ): void {
375  $usrId = $wasManuallyExecuted ? $actor->getId() : 0;
376 
377  $query = 'UPDATE cron_job SET ' .
378  ' job_status = ' . $this->db->quote(0, 'integer') .
379  ' , job_result_status = ' . $this->db->quote(null, 'text') .
380  ' , job_result_message = ' . $this->db->quote(null, 'text') .
381  ' , job_result_type = ' . $this->db->quote(null, 'text') .
382  ' , job_result_code = ' . $this->db->quote(null, 'text') .
383  ' , job_status_user_id = ' . $this->db->quote($usrId, 'integer') .
384  ' , job_status_type = ' . $this->db->quote($wasManuallyExecuted, 'integer') .
385  ' , job_status_ts = ' . $this->db->quote($when->getTimestamp(), 'integer') .
386  ' WHERE job_id = ' . $this->db->quote($job->getId(), 'text');
387  $this->db->manipulate($query);
388  }
389 
390  public function findAll(): ilCronJobCollection
391  {
392  $collection = new ilCronJobEntities();
393 
394  foreach ($this->getCronJobData() as $item) {
395  $job = $this->getJobInstance(
396  $item['job_id'],
397  $item['component'],
398  $item['class']
399  );
400  if ($job) {
401  $collection->add(new ilCronJobEntity($job, $item));
402  }
403  }
404 
405  foreach ($this->getPluginJobs() as $item) {
406  $collection->add(new ilCronJobEntity($item[0], $item[1], true));
407  }
408 
409  return $collection;
410  }
411 }
$res
Definition: ltiservices.php:69
Readable part of repository interface to ilComponentDataDB.
activationWasToggled(ilDBInterface $db, ilSetting $setting, bool $a_currently_active)
Important: This method is (also) called from the setup process, where the constructor of an ilCronJob...
getValidScheduleTypes()
Returns a collection of all valid schedule types for a specific job.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
updateJobSchedule(ilCronJob $job, ?CronJobScheduleType $scheduleType, ?int $scheduleValue)
updateJobResult(ilCronJob $job, DateTimeImmutable $when, ilObjUser $actor, ilCronJobResult $result, bool $wasManualExecution=false)
if($clientAssertionType !='urn:ietf:params:oauth:client-assertion-type:jwt-bearer'|| $grantType !='client_credentials') $parts
Definition: ltitoken.php:64
unregisterJob(string $a_component, array $a_xml_job_ids)
getPluginJobs(bool $withOnlyActive=false)
getCronJobData($id=null, bool $withInactiveJobsIncluded=true)
Get cron job configuration/execution data.
$path
Definition: ltiservices.php:32
activateJob(ilCronJob $job, DateTimeImmutable $when, ?ilObjUser $actor=null, bool $wasManuallyExecuted=false)
hasFlexibleSchedule()
registerJob(string $a_component, string $a_id, string $a_class, ?string $a_path)
__construct(private readonly ilDBInterface $db, private readonly ilSetting $setting, private readonly ilLogger $logger, private readonly ilComponentRepository $componentRepository, private readonly ilComponentFactory $componentFactory)
getDefaultScheduleType()
deactivateJob(ilCronJob $job, DateTimeImmutable $when, ilObjUser $actor, bool $wasManuallyExecuted=false)
updateRunInformation(string $jobId, int $runningTimestamp, int $aliveTimestamp)
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
getDefaultScheduleValue()
getJobInstance(string $a_id, string $a_component, string $a_class, bool $isCreationContext=false)
hasAutoActivation()
Is to be activated on "installation", does only work for ILIAS core cron jobs.
createDefaultEntry(ilCronJob $job, string $component, string $class, ?string $path)