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