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