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