ILIAS  trunk Revision v11.0_alpha-2638-g80c1d007f79
JobTable.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
22 
27 
28 class JobTable implements \ILIAS\UI\Component\Table\DataRetrieval
29 {
30  private readonly \ILIAS\UI\URLBuilder $url_builder;
31  private readonly \ILIAS\UI\URLBuilderToken $action_parameter_token;
32  private readonly \ILIAS\UI\URLBuilderToken $row_id_token;
33 
37  public function __construct(
38  \ilCronManagerGUI $a_parent_obj,
39  string $a_parent_cmd,
40  array $table_action_namespace,
41  string $table_action_param_name,
42  string $table_row_identifier_name,
43  private readonly \ILIAS\UI\Factory $ui_factory,
44  private readonly \Psr\Http\Message\ServerRequestInterface $request,
45  \ilCtrlInterface $ctrl,
46  private readonly \ilLanguage $lng,
47  private readonly \ILIAS\Cron\Job\JobCollection $job_collection,
48  private readonly JobRepository $job_repository,
49  private readonly bool $mayWrite = false
50  ) {
51  $form_action = (new \ILIAS\Data\Factory())->uri(
52  \ilUtil::_getHttpPath() . '/' .
53  $ctrl->getLinkTarget($a_parent_obj, $a_parent_cmd)
54  );
55 
56  [
60  ] = (new \ILIAS\UI\URLBuilder($form_action))->acquireParameters(
61  $table_action_namespace,
62  $table_action_param_name,
63  $table_row_identifier_name
64  );
65  }
66 
67  public function getRows(
68  \ILIAS\UI\Component\Table\DataRowBuilder $row_builder,
69  array $visible_column_ids,
71  \ILIAS\Data\Order $order,
72  ?array $filter_data,
73  ?array $additional_parameters
74  ): \Generator {
75  foreach ($this->getRecords($range, $order) as $item) {
76  $record = [
77  'title' => $this->formatTitle($item),
78  'component' => $this->formatComponent($item),
79  'schedule' => $this->formatSchedule($item),
80  'status' => (bool) $item->getJobStatus(),
81  'status_info' => $this->formatStatusInfo($item),
82  'result' => $this->formatResult($item),
83  'result_info' => $this->formatResultInfo($item),
84  'last_run' => $this->formatLastRun($item)
85  ];
86 
87  if ($item->getJob()->hasFlexibleSchedule()) {
88  if ($item->getScheduleType() === null) {
89  $this->job_repository->updateJobSchedule(
90  $item->getJob(),
91  $item->getEffectiveScheduleType(),
92  $item->getEffectiveScheduleValue()
93  );
94  }
95  } elseif ($item->getScheduleType() !== null) {
96  $this->job_repository->updateJobSchedule($item->getJob(), null, null);
97  }
98 
99  $actions_executable = $this->mayWrite && !$item->getRunningTimestamp();
100  $is_crashed = \ILIAS\Cron\Job\JobResult::STATUS_CRASHED === $item->getJobResultStatus();
101  $is_acivated = (bool) $item->getJobStatus();
102 
103  $may_reset = $actions_executable && $is_crashed;
104  $may_activate = $actions_executable && !$is_crashed && !$is_acivated;
105  $may_deactivate = $actions_executable && !$is_crashed && $is_acivated;
106  $may_run = $actions_executable && !$is_crashed && $is_acivated && $item->getJob()->isManuallyExecutable();
107  $may_edit = $actions_executable && (
108  $item->getJob()->hasFlexibleSchedule() || $item->getJob()->hasCustomSettings()
109  );
110 
111  yield $row_builder
112  ->buildDataRow($item->getEffectiveJobId(), $record)
113  ->withDisabledAction('run', !$may_run)
114  ->withDisabledAction('activate', !$may_activate)
115  ->withDisabledAction('deactivate', !$may_deactivate)
116  ->withDisabledAction('reset', !$may_reset)
117  ->withDisabledAction('edit', !$may_edit);
118  }
119  }
120 
121  public function getTotalRowCount(?array $filter_data, ?array $additional_parameters): ?int
122  {
123  return \count($this->job_collection);
124  }
125 
129  private function getRecords(\ILIAS\Data\Range $range, \ILIAS\Data\Order $order): array
130  {
131  [$order_field, $order_direction] = $order->join([], static function ($ret, $key, $value) {
132  return [$key, $value];
133  });
134 
135  $collection = new OrderedJobEntities(
136  $this->job_collection,
137  match ($order_field) {
140  default => function (JobEntity $left, JobEntity $right) use ($order_field): int {
141  if ($order_field === 'component') {
142  return \ilStr::strCmp($this->formatComponent($left), $this->formatComponent($right));
143  }
144 
145  if ($order_field === 'schedule') {
146  return \ilStr::strCmp($this->formatSchedule($left), $this->formatSchedule($right));
147  }
148 
149  if ($order_field === 'result') {
150  return \ilStr::strCmp($this->formatResult($left), $this->formatResult($right));
151  }
152 
153  if ($order_field === 'last_run') {
154  $left_last_run = 0;
155  if ($left->getRunningTimestamp()) {
156  $left_last_run = strtotime('+1year', $left->getRunningTimestamp());
157  } elseif ($left->getJobResultTimestamp()) {
158  $left_last_run = $left->getJobResultTimestamp();
159  }
160 
161  $right_last_run = 0;
162  if ($right->getRunningTimestamp()) {
163  $right_last_run = strtotime('+1year', $right->getRunningTimestamp());
164  } elseif ($right->getJobResultTimestamp()) {
165  $right_last_run = $right->getJobResultTimestamp();
166  }
167 
168  return $left_last_run <=> $right_last_run;
169  }
170 
171  return 0;
172  }
173  },
174  $order_direction === \ILIAS\Data\Order::DESC
175  );
176 
177  $records = \array_slice($collection->toArray(), $range->getStart(), $range->getLength());
178 
179  return $records;
180  }
181 
182  private function formatTitle(JobEntity $entity): string
183  {
184  $title = implode('', [
185  '<span class="cron-title" id="job-' . $entity->getEffectiveJobId() . '">',
186  $entity->getEffectiveTitle(),
187  '</span>'
188  ]);
189  if ($entity->getJob()->getDescription()) {
190  $title .= implode('', [
191  '<div class="il_Description_no_margin">',
192  $entity->getJob()->getDescription(),
193  '</div>'
194  ]);
195  }
196 
197  return $title;
198  }
199 
200  private function formatSchedule(JobEntity $entity): string
201  {
202  $schedule = match ($entity->getEffectiveScheduleType()) {
203  JobScheduleType::DAILY => $this->lng->txt('cron_schedule_daily'),
204  JobScheduleType::WEEKLY => $this->lng->txt('cron_schedule_weekly'),
205  JobScheduleType::MONTHLY => $this->lng->txt('cron_schedule_monthly'),
206  JobScheduleType::QUARTERLY => $this->lng->txt('cron_schedule_quarterly'),
207  JobScheduleType::YEARLY => $this->lng->txt('cron_schedule_yearly'),
208  JobScheduleType::IN_MINUTES => \sprintf(
209  $this->lng->txt('cron_schedule_in_minutes'),
210  $entity->getEffectiveScheduleValue()
211  ),
212  JobScheduleType::IN_HOURS => \sprintf(
213  $this->lng->txt('cron_schedule_in_hours'),
214  $entity->getEffectiveScheduleValue()
215  ),
216  JobScheduleType::IN_DAYS => \sprintf(
217  $this->lng->txt('cron_schedule_in_days'),
218  $entity->getEffectiveScheduleValue()
219  )
220  };
221 
222  return $schedule;
223  }
224 
225  public function formatComponent(JobEntity $entity): string
226  {
227  $component = $entity->getComponent();
228  if ($entity->isPlugin()) {
229  $component = $this->lng->txt('cmps_plugin') . '/' . $component;
230  }
231 
232  return $component;
233  }
234 
235  private function formatStatusInfo(JobEntity $entity): string
236  {
237  $status_info = [];
238  if ($entity->getJobStatusTimestamp()) {
239  $status_info[] = \ilDatePresentation::formatDate(
241  );
242  }
243 
244  if ($entity->getJobStatusType()) {
245  $status_info[] = \ilUserUtil::getNamePresentation($entity->getJobStatusUsrId());
246  } else {
247  $status_info[] = $this->lng->txt('cron_changed_by_crontab');
248  }
249 
250  return implode('<br />', $status_info);
251  }
252 
253  private function formatResult(JobEntity $entity): string
254  {
255  $result = '-';
256  if ($entity->getJobResultStatus()) {
257  switch ($entity->getJobResultStatus()) {
258  case \ILIAS\Cron\Job\JobResult::STATUS_INVALID_CONFIGURATION:
259  $result = $this->lng->txt('cron_result_status_invalid_configuration');
260  break;
261 
262  case \ILIAS\Cron\Job\JobResult::STATUS_NO_ACTION:
263  $result = $this->lng->txt('cron_result_status_no_action');
264  break;
265 
266  case \ILIAS\Cron\Job\JobResult::STATUS_OK:
267  $result = $this->lng->txt('cron_result_status_ok');
268  break;
269 
270  case \ILIAS\Cron\Job\JobResult::STATUS_CRASHED:
271  $result = $this->lng->txt('cron_result_status_crashed');
272  break;
273 
274  case \ILIAS\Cron\Job\JobResult::STATUS_RESET:
275  $result = $this->lng->txt('cron_result_status_reset');
276  break;
277 
278  case \ILIAS\Cron\Job\JobResult::STATUS_FAIL:
279  $result = $this->lng->txt('cron_result_status_fail');
280  break;
281  }
282  }
283 
284  return $result;
285  }
286 
287  private function formatResultInfo(JobEntity $entity): string
288  {
289  $result_info = [];
290  if ($entity->getJobResultDuration()) {
291  $result_info[] = ($entity->getJobResultDuration() / 1000) . ' sec';
292  }
293 
294  // #23391 / #11866
295  $resultCode = $entity->getJobResultCode();
296  if (\in_array($resultCode, \ILIAS\Cron\Job\JobResult::getCoreCodes(), true)) {
297  $result_info[] = $this->lng->txt('cro_job_rc_' . $resultCode);
298  } elseif ($entity->getJobResultMessage()) {
299  $result_info[] = $entity->getJobResultMessage();
300  }
301 
302  if (\defined('DEVMODE') && DEVMODE && $resultCode) {
303  $result_info[] = $resultCode;
304  }
305 
306  if ($entity->getJobResultType()) {
307  $result_info[] = \ilUserUtil::getNamePresentation($entity->getJobResultUsrId());
308  } else {
309  $result_info[] = $this->lng->txt('cron_changed_by_crontab');
310  }
311 
312  return implode('<br />', $result_info);
313  }
314 
315  private function formatLastRun(JobEntity $entity): string
316  {
317  $last_run = null;
318  if ($entity->getRunningTimestamp()) {
319  $last_run = strtotime('+1year', $entity->getRunningTimestamp());
320  } elseif ($entity->getJobResultTimestamp()) {
321  $last_run = $entity->getJobResultTimestamp();
322  }
323 
324  if ($last_run > time()) {
325  $last_run = $this->lng->txt('cron_running_since') . ' ' .
327 
328  // job has pinged
329  if ($entity->getAliveTimestamp() !== $entity->getRunningTimestamp()) {
330  $last_run .= '<br />(Ping: ' .
332  }
333  } elseif ($last_run) {
334  $last_run = \ilDatePresentation::formatDate(new \ilDateTime($last_run, IL_CAL_UNIX));
335  }
336 
337  return $last_run ?: '-';
338  }
339 
343  public function getColumns(): array
344  {
345  return [
346  'title' => $this->ui_factory
347  ->table()
348  ->column()
349  ->text($this->lng->txt('title') . ' / ' . $this->lng->txt('description'))
350  ->withIsSortable(true),
351  'component' => $this->ui_factory
352  ->table()
353  ->column()
354  ->text($this->lng->txt('cron_component'))
355  ->withIsSortable(true)
356  ->withIsOptional(true, false),
357  'schedule' => $this->ui_factory
358  ->table()
359  ->column()
360  ->text($this->lng->txt('cron_schedule'))
361  ->withIsSortable(true),
362  'status' => $this->ui_factory
363  ->table()
364  ->column()
365  ->boolean(
366  $this->lng->txt('cron_status'),
367  $this->ui_factory->symbol()->icon()->custom(
368  'assets/images/standard/icon_ok.svg',
369  $this->lng->txt('cron_status_active'),
370  \ILIAS\UI\Component\Symbol\Icon\Icon::SMALL
371  ),
372  $this->ui_factory->symbol()->icon()->custom(
373  'assets/images/standard/icon_not_ok.svg',
374  $this->lng->txt('cron_status_inactive'),
375  \ILIAS\UI\Component\Symbol\Icon\Icon::SMALL
376  )
377  )
378  ->withIsSortable(true),
379  'status_info' => $this->ui_factory
380  ->table()
381  ->column()
382  ->text($this->lng->txt('cron_status_info'))
383  ->withIsSortable(false),
384  'result' => $this->ui_factory
385  ->table()
386  ->column()
387  ->status($this->lng->txt('cron_result'))
388  ->withIsSortable(true),
389  'result_info' => $this->ui_factory
390  ->table()
391  ->column()
392  ->text($this->lng->txt('cron_result_info'))
393  ->withIsSortable(false),
394  'last_run' => $this->ui_factory
395  ->table()
396  ->column()
397  ->text($this->lng->txt('cron_last_run'))
398  ->withIsSortable(true)
399  ];
400  }
401 
405  public function getActions(): array
406  {
407  if (!$this->mayWrite) {
408  return [];
409  }
410 
411  return [
412  'run' => $this->ui_factory->table()->action()->single(
413  $this->lng->txt('cron_action_run'),
414  $this->url_builder->withParameter($this->action_parameter_token, 'run'),
416  ),
417  'activate' => $this->ui_factory->table()->action()->standard(
418  $this->lng->txt('cron_action_activate'),
419  $this->url_builder->withParameter($this->action_parameter_token, 'activate'),
421  ),
422  'deactivate' => $this->ui_factory->table()->action()->standard(
423  $this->lng->txt('cron_action_deactivate'),
424  $this->url_builder->withParameter($this->action_parameter_token, 'deactivate'),
426  ),
427  'reset' => $this->ui_factory->table()->action()->standard(
428  $this->lng->txt('cron_action_reset'),
429  $this->url_builder->withParameter($this->action_parameter_token, 'reset'),
431  ),
432  'edit' => $this->ui_factory->table()->action()->single(
433  $this->lng->txt('cron_action_edit'),
434  $this->url_builder->withParameter($this->action_parameter_token, 'edit'),
436  )
437  ];
438  }
439 
440  public function getComponent(): \ILIAS\UI\Component\Table\Table
441  {
442  return $this->ui_factory
443  ->table()
444  ->data($this, $this->lng->txt('cron_jobs'), $this->getColumns())
445  ->withActions($this->getActions())
446  ->withId(self::class)
447  ->withRequest($this->request)
448  ->withOrder(new \ILIAS\Data\Order('title', \ILIAS\Data\Order::ASC));
449  }
450 }
readonly ILIAS UI URLBuilderToken $row_id_token
Definition: JobTable.php:32
Interface Observer Contains several chained tasks and infos about them.
getRecords(\ILIAS\Data\Range $range, \ILIAS\Data\Order $order)
Definition: JobTable.php:129
const IL_CAL_UNIX
getLinkTarget(object $a_gui_obj, ?string $a_cmd=null, ?string $a_anchor=null, bool $is_async=false, bool $has_xml_style=false)
Returns a link target for the given information.
Both the subject and the direction need to be specified when expressing an order. ...
Definition: Order.php:28
readonly ILIAS UI URLBuilderToken $action_parameter_token
Definition: JobTable.php:31
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
static getNamePresentation( $a_user_id, bool $a_user_image=false, bool $a_profile_link=false, string $a_profile_back_link='', bool $a_force_first_lastname=false, bool $a_omit_login=false, bool $a_sortable=true, bool $a_return_data_array=false, $a_ctrl_path='ilpublicuserprofilegui')
Default behaviour is:
Builds data types.
Definition: Factory.php:35
static _getHttpPath()
__construct(\ilCronManagerGUI $a_parent_obj, string $a_parent_cmd, array $table_action_namespace, string $table_action_param_name, string $table_row_identifier_name, private readonly \ILIAS\UI\Factory $ui_factory, private readonly \Psr\Http\Message\ServerRequestInterface $request, \ilCtrlInterface $ctrl, private readonly \ilLanguage $lng, private readonly \ILIAS\Cron\Job\JobCollection $job_collection, private readonly JobRepository $job_repository, private readonly bool $mayWrite=false)
Definition: JobTable.php:37
global $lng
Definition: privfeed.php:31
static formatDate(ilDateTime $date, bool $a_skip_day=false, bool $a_include_wd=false, bool $include_seconds=false, ?ilObjUser $user=null,)
getTotalRowCount(?array $filter_data, ?array $additional_parameters)
Mainly for the purpose of pagination-support, it is important to know about the total number of recor...
Definition: JobTable.php:121
ilCronManagerGUI: ilPropertyFormGUI ilCronManagerGUI: ilAdministrationGUI
const DESC
Definition: Order.php:31
A simple class to express a naive range of whole positive numbers.
Definition: Range.php:28
getRows(\ILIAS\UI\Component\Table\DataRowBuilder $row_builder, array $visible_column_ids, \ILIAS\Data\Range $range, \ILIAS\Data\Order $order, ?array $filter_data, ?array $additional_parameters)
Definition: JobTable.php:67