ILIAS  release_10 Revision v10.1-43-ga1241a92c2f
class.ilCronDeleteInactiveUserAccounts.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
23 
31 {
32  private const DEFAULT_INACTIVITY_PERIOD = 365;
33  private const DEFAULT_REMINDER_PERIOD = 0;
34 
35  private const ACTION_USER_NONE = 0;
36  private const ACTION_USER_REMINDER_MAIL_SENT = 1;
37  private const ACTION_USER_DELETED = 2;
38 
39  private int $delete_period;
40  private int $reminder_period;
42  private array $include_roles;
45  private Language $lng;
49  private \ILIAS\HTTP\GlobalHttpState $http;
50  private \ILIAS\Refinery\Factory $refinery;
52  private \ilGlobalTemplateInterface $main_tpl;
53 
54  public function __construct()
55  {
57  global $DIC;
58 
59  if (isset($DIC['ilDB'])) {
60  $this->cron_delete_reminder_mail = new ilCronDeleteInactiveUserReminderMail($DIC['ilDB']);
61  }
62 
63  if (isset($DIC['tpl'])) {
64  $this->main_tpl = $DIC['tpl'];
65  }
66  if (isset($DIC['http'])) {
67  $this->http = $DIC['http'];
68  }
69 
70  if (isset($DIC['lng'])) {
71  $this->lng = $DIC['lng'];
72  }
73 
74  if (isset($DIC['ilLog'])) {
75  $this->log = $DIC['ilLog'];
76  }
77 
78  if (isset($DIC['refinery'])) {
79  $this->refinery = $DIC['refinery'];
80  }
81 
82  if (isset($DIC['ilObjDataCache'])) {
83  $this->objectDataCache = $DIC['ilObjDataCache'];
84  }
85 
86  if (isset($DIC['rbacreview'])) {
87  $this->rbac_review = $DIC['rbacreview'];
88  }
89 
90  if (isset($DIC['cron.repository'])) {
91  $this->cronRepository = $DIC['cron.repository'];
92  }
93 
94  if (isset($DIC['ilSetting'])) {
95  $this->settings = $DIC['ilSetting'];
96  $this->loadSettings();
97  }
98  }
99 
100  private function loadSettings(): void
101  {
102  $include_roles = $this->settings->get(
103  'cron_inactive_user_delete_include_roles',
104  null
105  );
106  if ($include_roles === null) {
107  $this->include_roles = [];
108  } else {
109  $this->include_roles = array_filter(array_map('intval', explode(',', $include_roles)));
110  }
111 
112  $this->delete_period = (int) $this->settings->get(
113  'cron_inactive_user_delete_period',
114  (string) self::DEFAULT_INACTIVITY_PERIOD
115  );
116  $this->reminder_period = (int) $this->settings->get(
117  'cron_inactive_user_reminder_period',
118  (string) self::DEFAULT_REMINDER_PERIOD
119  );
120  }
121 
125  protected function isDecimal($number): bool
126  {
127  $number_as_string = (string) $number;
128 
129  return strpos($number_as_string, ',') || strpos($number_as_string, '.');
130  }
131 
132  protected function getTimeDifferenceBySchedule(CronJobScheduleType $schedule_time, int $multiplier): int
133  {
134  $time_difference = 0;
135 
136  switch ($schedule_time) {
137  case CronJobScheduleType::SCHEDULE_TYPE_DAILY:
138  $time_difference = 86400;
139  break;
140  case CronJobScheduleType::SCHEDULE_TYPE_IN_MINUTES:
141  $time_difference = 60 * $multiplier;
142  break;
143  case CronJobScheduleType::SCHEDULE_TYPE_IN_HOURS:
144  $time_difference = 3600 * $multiplier;
145  break;
146  case CronJobScheduleType::SCHEDULE_TYPE_IN_DAYS:
147  $time_difference = 86400 * $multiplier;
148  break;
149  case CronJobScheduleType::SCHEDULE_TYPE_WEEKLY:
150  $time_difference = 604800;
151  break;
152  case CronJobScheduleType::SCHEDULE_TYPE_MONTHLY:
153  $time_difference = 2629743;
154  break;
155  case CronJobScheduleType::SCHEDULE_TYPE_QUARTERLY:
156  $time_difference = 7889229;
157  break;
159  $time_difference = 31556926;
160  break;
161  }
162 
163  return $time_difference;
164  }
165 
166  public function getId(): string
167  {
168  return "user_inactive";
169  }
170 
171  public function getTitle(): string
172  {
173  return $this->lng->txt("delete_inactive_user_accounts");
174  }
175 
176  public function getDescription(): string
177  {
178  return $this->lng->txt("delete_inactive_user_accounts_desc");
179  }
180 
182  {
183  return CronJobScheduleType::SCHEDULE_TYPE_DAILY;
184  }
185 
186  public function getDefaultScheduleValue(): ?int
187  {
188  return null;
189  }
190 
191  public function hasAutoActivation(): bool
192  {
193  return false;
194  }
195 
196  public function hasFlexibleSchedule(): bool
197  {
198  return true;
199  }
200 
201  public function hasCustomSettings(): bool
202  {
203  return true;
204  }
205 
206  public function run(): ilCronJobResult
207  {
209  $check_mail = $this->delete_period - $this->reminder_period;
210  $usr_ids = ilObjUser::getUserIdsByInactivityPeriod($check_mail);
211  $counters = [
212  self::ACTION_USER_NONE => 0,
213  self::ACTION_USER_REMINDER_MAIL_SENT => 0,
214  self::ACTION_USER_DELETED => 0
215  ];
216  foreach ($usr_ids as $usr_id) {
217  if ($usr_id === ANONYMOUS_USER_ID || $usr_id === SYSTEM_USER_ID) {
218  continue;
219  }
220 
221  foreach ($this->include_roles as $role_id) {
222  if ($this->rbac_review->isAssigned($usr_id, $role_id)) {
223  $action_taken = $this->deleteUserOrSendReminderMail($usr_id);
224  $counters[$action_taken]++;
225  break;
226  }
227  }
228  }
229 
230  if ($counters[self::ACTION_USER_REMINDER_MAIL_SENT] > 0
231  || $counters[self::ACTION_USER_DELETED] > 0) {
232  $status = ilCronJobResult::STATUS_OK;
233  }
234 
235  $this->cron_delete_reminder_mail->removeEntriesFromTableIfLastLoginIsNewer();
236  $this->log->write(
237  'CRON - ilCronDeleteInactiveUserAccounts::run(), deleted '
238  . "=> {$counters[self::ACTION_USER_DELETED]} User(s), sent reminder "
239  . "mail to {$counters[self::ACTION_USER_REMINDER_MAIL_SENT]} User(s)"
240  );
241 
242  $result = new ilCronJobResult();
243  $result->setStatus($status);
244 
245  return $result;
246  }
247 
248  private function deleteUserOrSendReminderMail($usr_id): int
249  {
250  $user = ilObjectFactory::getInstanceByObjId($usr_id);
251  $timestamp_last_login = strtotime($user->getLastLogin());
252  $grace_period_over = time() - ($this->delete_period * 24 * 60 * 60);
253 
254  if ($timestamp_last_login < $grace_period_over) {
255  $user->delete();
256  return self::ACTION_USER_DELETED;
257  }
258 
259  if ($this->reminder_period > 0) {
260  $timestamp_for_deletion = $timestamp_last_login - $grace_period_over;
261  $account_will_be_deleted_on = $this->calculateDeletionData($timestamp_for_deletion);
262  if(
263  $this->cron_delete_reminder_mail->sendReminderMailIfNeeded(
264  $user,
265  $this->reminder_period,
266  $account_will_be_deleted_on
267  )
268  ) {
269  return self::ACTION_USER_REMINDER_MAIL_SENT;
270  }
271  }
272 
273  return self::ACTION_USER_NONE;
274  }
275 
276  protected function calculateDeletionData(int $date_for_deletion): int
277  {
278  $cron_timing = $this->cronRepository->getCronJobData($this->getId());
279  $time_difference = 0;
280  $multiplier = 1;
281 
282  if (!is_array($cron_timing) || !isset($cron_timing[0]) || !is_array($cron_timing[0])) {
283  return time() + $date_for_deletion + $time_difference;
284  }
285 
286  if (array_key_exists('schedule_type', $cron_timing[0])) {
287  if ($cron_timing[0]['schedule_value'] !== null) {
288  $multiplier = (int) $cron_timing[0]['schedule_value'];
289  }
290  $time_difference = $this->getTimeDifferenceBySchedule(
291  CronJobScheduleType::from((int) $cron_timing[0]['schedule_type']),
292  $multiplier
293  );
294  }
295  return time() + $date_for_deletion + $time_difference;
296  }
297 
298  public function addCustomSettingsToForm(ilPropertyFormGUI $a_form): void
299  {
300  $this->lng->loadLanguageModule("user");
301 
302  $schedule = $a_form->getItemByPostVar('type');
303  $schedule->setTitle($this->lng->txt('delete_inactive_user_accounts_frequency'));
304  $schedule->setInfo($this->lng->txt('delete_inactive_user_accounts_frequency_desc'));
305 
306  $sub_mlist = new ilMultiSelectInputGUI(
307  $this->lng->txt('delete_inactive_user_accounts_include_roles'),
308  'cron_inactive_user_delete_include_roles'
309  );
310  $sub_mlist->setInfo($this->lng->txt('delete_inactive_user_accounts_include_roles_desc'));
311  $roles = [];
312  foreach ($this->rbac_review->getGlobalRoles() as $role_id) {
313  if ($role_id !== ANONYMOUS_ROLE_ID) {
314  $roles[$role_id] = $this->objectDataCache->lookupTitle($role_id);
315  }
316  }
317  $sub_mlist->setOptions($roles);
318  $setting = $this->settings->get('cron_inactive_user_delete_include_roles', null);
319  if ($setting === null) {
320  $setting = [];
321  } else {
322  $setting = explode(',', $setting);
323  }
324  $sub_mlist->setValue($setting);
325  $sub_mlist->setWidth(300);
326  $a_form->addItem($sub_mlist);
327 
328  $default_setting = (string) self::DEFAULT_INACTIVITY_PERIOD;
329 
330  $sub_text = new ilNumberInputGUI(
331  $this->lng->txt('delete_inactive_user_accounts_period'),
332  'cron_inactive_user_delete_period'
333  );
334  $sub_text->allowDecimals(false);
335  $sub_text->setInfo($this->lng->txt('delete_inactive_user_accounts_period_desc'));
336  $sub_text->setValue($this->settings->get("cron_inactive_user_delete_period", $default_setting));
337  $sub_text->setSize(4);
338  $sub_text->setMaxLength(4);
339  $sub_text->setRequired(true);
340  $a_form->addItem($sub_text);
341 
342  $sub_period = new ilNumberInputGUI(
343  $this->lng->txt('send_mail_to_inactive_users'),
344  'cron_inactive_user_reminder_period'
345  );
346  $sub_period->allowDecimals(false);
347  $sub_period->setInfo($this->lng->txt("send_mail_to_inactive_users_desc"));
348  $sub_period->setValue($this->settings->get("cron_inactive_user_reminder_period", $default_setting));
349  $sub_period->setSuffix($this->lng->txt("send_mail_to_inactive_users_suffix"));
350  $sub_period->setSize(4);
351  $sub_period->setMaxLength(4);
352  $sub_period->setRequired(false);
353  $sub_period->setMinValue(0);
354  $a_form->addItem($sub_period);
355  }
356 
357  public function saveCustomSettings(ilPropertyFormGUI $a_form): bool
358  {
359  $this->lng->loadLanguageModule("user");
360 
361  $valid = true;
362 
363  $cron_period = CronJobScheduleType::from($this->http->wrapper()->post()->retrieve(
364  'type',
365  $this->refinery->kindlyTo()->int()
366  ));
367 
368  $cron_period_custom = 0;
369  $delete_period = 0;
370  $reminder_period = '';
371 
372  $empty_string_trafo = $this->refinery->custom()->transformation(static function ($value): string {
373  if ($value === '') {
374  return '';
375  }
376 
377  throw new Exception('The value to be transformed is not an empty string');
378  });
379 
380  if ($this->http->wrapper()->post()->has('sdyi')) {
381  $cron_period_custom = $this->http->wrapper()->post()->retrieve(
382  'sdyi',
383  $this->refinery->byTrying([
384  $this->refinery->kindlyTo()->int(),
385  $empty_string_trafo
386  ])
387  );
388  }
389 
390  if ($this->http->wrapper()->post()->has('cron_inactive_user_delete_period')) {
391  $delete_period = $this->http->wrapper()->post()->retrieve(
392  'cron_inactive_user_delete_period',
393  $this->refinery->byTrying([
394  $this->refinery->kindlyTo()->int(),
395  $this->refinery->in()->series([
396  $this->refinery->kindlyTo()->float(),
397  $this->refinery->kindlyTo()->int()
398  ])
399  ])
400  );
401  }
402 
403  if ($this->http->wrapper()->post()->has('cron_inactive_user_reminder_period')) {
404  $reminder_period = $this->http->wrapper()->post()->retrieve(
405  'cron_inactive_user_reminder_period',
406  $this->refinery->byTrying([
407  $empty_string_trafo,
408  $this->refinery->byTrying([
409  $this->refinery->kindlyTo()->int(),
410  $this->refinery->in()->series([
411  $this->refinery->kindlyTo()->float(),
412  $this->refinery->kindlyTo()->int()
413  ])
414  ])
415  ])
416  );
417  }
418 
419  if ($this->isDecimal($delete_period)) {
420  $valid = false;
421  $a_form->getItemByPostVar('cron_inactive_user_delete_period')->setAlert(
422  $this->lng->txt('send_mail_to_inactive_users_numbers_only')
423  );
424  }
425 
426  if ($this->isDecimal($reminder_period)) {
427  $valid = false;
428  $a_form->getItemByPostVar('cron_inactive_user_reminder_period')->setAlert(
429  $this->lng->txt('send_mail_to_inactive_users_numbers_only')
430  );
431  }
432 
433  if ($reminder_period >= $delete_period) {
434  $valid = false;
435  $a_form->getItemByPostVar('cron_inactive_user_reminder_period')->setAlert(
436  $this->lng->txt('send_mail_to_inactive_users_must_be_smaller_than')
437  );
438  }
439 
440  if ($cron_period->value >= CronJobScheduleType::SCHEDULE_TYPE_IN_DAYS->value &&
441  $cron_period->value <= CronJobScheduleType::SCHEDULE_TYPE_YEARLY->value && $reminder_period > 0) {
442  $logic = true;
443  $check_window_logic = $delete_period - $reminder_period;
444  if ($cron_period === CronJobScheduleType::SCHEDULE_TYPE_IN_DAYS) {
445  if ($check_window_logic < $cron_period_custom) {
446  $logic = false;
447  }
448  } elseif ($cron_period === CronJobScheduleType::SCHEDULE_TYPE_WEEKLY) {
449  if ($check_window_logic <= 7) {
450  $logic = false;
451  }
452  } elseif ($cron_period === CronJobScheduleType::SCHEDULE_TYPE_MONTHLY) {
453  if ($check_window_logic <= 31) {
454  $logic = false;
455  }
456  } elseif ($cron_period === CronJobScheduleType::SCHEDULE_TYPE_QUARTERLY) {
457  if ($check_window_logic <= 92) {
458  $logic = false;
459  }
460  } elseif ($cron_period === CronJobScheduleType::SCHEDULE_TYPE_YEARLY) {
461  if ($check_window_logic <= 366) {
462  $logic = false;
463  }
464  }
465 
466  if (!$logic) {
467  $valid = false;
468  $a_form->getItemByPostVar('cron_inactive_user_reminder_period')->setAlert(
469  $this->lng->txt('send_mail_reminder_window_too_small')
470  );
471  }
472  }
473 
474  if ($delete_period > 0) {
475  $roles = implode(',', $this->http->wrapper()->post()->retrieve(
476  'cron_inactive_user_delete_include_roles',
477  $this->refinery->byTrying([
478  $this->refinery->kindlyTo()->listOf($this->refinery->kindlyTo()->int()),
479  $this->refinery->always([])
480  ])
481  ));
482 
483  $this->settings->set('cron_inactive_user_delete_include_roles', $roles);
484  $this->settings->set('cron_inactive_user_delete_period', (string) $delete_period);
485  }
486 
487  if ($this->reminder_period > $reminder_period) {
488  $this->cron_delete_reminder_mail->flushDataTable();
489  }
490 
491  $this->settings->set('cron_inactive_user_reminder_period', (string) $reminder_period);
492 
493  if (!$valid) {
494  $this->main_tpl->setOnScreenMessage('failure', $this->lng->txt("form_input_not_valid"));
495  return false;
496  }
497 
498  return true;
499  }
500 }
const ANONYMOUS_USER_ID
Definition: constants.php:27
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static getUserIdsByInactivityPeriod(int $periodInDays)
Get ids of all users that have been inactive for at least the given period.
getItemByPostVar(string $a_post_var)
const SYSTEM_USER_ID
This file contains constants for PHPStan analyis, see: https://phpstan.org/config-reference#constants...
Definition: constants.php:26
$valid
Component logger with individual log levels by component id.
ilCronDeleteInactiveUserReminderMail $cron_delete_reminder_mail
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
static http()
Fetches the global http state from ILIAS.
final const STATUS_NO_ACTION
getTimeDifferenceBySchedule(CronJobScheduleType $schedule_time, int $multiplier)
global $DIC
Definition: shib_login.php:25
static getInstanceByObjId(?int $obj_id, bool $stop_on_error=true)
get an instance of an Ilias object by object id
const ANONYMOUS_ROLE_ID
Definition: constants.php:28
__construct(Container $dic, ilPlugin $plugin)
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...