user_expire-8.x-1.x-dev/user_expire.module

user_expire.module
<?php

/**
 * @file
 * Main module file for User expire module.
 */

use Drupal\Core\Database\Query\Condition;
use Drupal\Core\Database\StatementInterface;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
use Drupal\user\Entity\User;
use Drupal\user\RoleInterface;
use Drupal\Component\Render\PlainTextOutput;
use Drupal\Component\Utility\DeprecationHelper;

/**
 * Implements hook_help().
 */
function user_expire_help($route_name, RouteMatchInterface $route_match) {
  $output = '';
  switch ($route_name) {
    // Main module help for the user_expire module.
    case 'help.page.user_expire':
      $output = '<h3>' . t('About') . '</h3>';
      $output .= '<dt>' . t('This module allows an administrator to define a date on which to expire a specific user account or to define a period at a role level where inactive accounts will be locked.') . '</dt>';
      $output .= '<h3>' . t('Uses') . '</h3>';
      $output .= '<dt>' . t('User expire settings.') . '</dt>';
      $output .= '<dd>' . t('This module has a configuration page, see the <a href=":user_expire">User Expire settings</a>.', [
        ':user_expire' => Url::fromRoute('user_expire.admin')->toString(),
      ]) . '</dd>';
      break;
  }
  return $output;
}

/**
 * Implements hook_user_load().
 */
function user_expire_user_load($users): void {
  foreach ($users as $uid => $user) {
    $query = \Drupal::database()->select('user_expire', 'ue');

    $expiration = $query->condition('ue.uid', $uid)
      ->fields('ue', ['expiration'])
      ->execute()
      ->fetchField();
    if (!empty($expiration)) {
      $user->expiration = $expiration;
    }
  }
}

/**
 * Implements hook_user_login().
 */
function user_expire_user_login($account): void {
  user_expire_notify_user();
}

/**
 * Implements hook_user_cancel().
 */
function user_expire_user_cancel($edit, $account, $method): void {
  user_expire_set_expiration($account);
}

/**
 * Implements hook_user_delete().
 */
function user_expire_user_delete($account): void {
  user_expire_set_expiration($account);
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Add the user expire form to an individual user's account page.
 *
 * @see \Drupal\user\ProfileForm::form()
 */
function user_expire_form_user_form_alter(&$form, FormStateInterface $form_state): void {
  if (\Drupal::currentUser()->hasPermission('set user expiration')) {
    $entity = $form_state->getFormObject()->getEntity();

    $form['user_expire'] = [
      '#type' => 'details',
      '#title' => t('User expiration'),
      '#open' => TRUE,
      '#weight' => 5,
    ];

    $form['user_expire']['user_expiration'] = [
      '#title' => t('Set expiration for this user'),
      '#type' => 'checkbox',
      '#default_value' => !empty($entity->expiration),
    ];

    $form['user_expire']['container'] = [
      '#type' => 'container',
      '#states' => [
        'invisible' => [
          ':input[name="user_expiration"]' => ['checked' => FALSE],
        ],
      ],
    ];

    $form['user_expire']['container']['user_expiration_date'] = [
      '#title' => t('Expiration date'),
      '#type' => 'datetime',
      '#description' => t('The date on which this account will be disabled.'),
      '#date_date_format' => 'Y-m-d',
      '#date_time_element' => 'none',
      '#default_value' => $entity->expiration ? DrupalDateTime::createFromTimestamp($entity->expiration) : NULL,
      '#required' => !empty($form_state->getValue('user_expiration')),
    ];
  }

  $form['actions']['submit']['#submit'][] = 'user_expire_user_profile_form_submit';
}

/**
 * Submit callback for the user profile form to save the contact page setting.
 */
function user_expire_user_profile_form_submit($form, FormStateInterface $form_state): void {
  $account = $form_state->getFormObject()->getEntity();
  if ($account->id() && $form_state->hasValue('user_expiration_date')) {
    $account->user_expiration_date = $form_state->getValue('user_expiration_date');
    $account->user_expiration = $form_state->getValue('user_expiration');
    $account->expiration = $form_state->getValue('user_expiration_date');

    _user_expire_save($account);
  }
}

/**
 * Implements hook_user_insert().
 */
function user_expire_user_insert(EntityInterface $entity): void {
  _user_expire_save($entity);
}

/**
 * Save expiration date from user edit form.
 *
 * @param object $account
 *   A user object to modify.
 */
function _user_expire_save(object $account): void {
  if (isset($account->user_expiration) && $account->user_expiration) {
    if (is_array($account->user_expiration_date) && isset($account->user_expiration_date['month'])) {
      $time_for_datetime = $account->user_expiration_date['year'] . '-' . $account->user_expiration_date['month'] . '-' . $account->user_expiration_date['day'];
    }
    else {
      $time_for_datetime = $account->user_expiration_date;
    }

    $new_date = new DateTime($time_for_datetime, new DateTimeZone(date_default_timezone_get()));
    $new_date->setTime(0, 0, 0);

    $timestamp = $new_date->getTimestamp();
    user_expire_set_expiration($account, $timestamp);
  }
  else {
    user_expire_set_expiration($account);
  }
}

/**
 * Implements hook_cron().
 */
function user_expire_cron(): void {
  // Start with per-role warnings (if enabled).
  $config = \Drupal::configFactory()->get('user_expire.settings');
  $send_expiration_warnings = $config->get('send_expiration_warnings') ?? TRUE;
  if ($send_expiration_warnings) {
    user_expire_expire_by_role_warning();
  }

  // Then do per-user blocking.
  user_expire_process_per_user_expiration();

  // Then per-role inactivity blocking.
  user_expire_expire_by_role();
}

/**
 * Expires users who have an expiration that has passed.
 */
function user_expire_process_per_user_expiration(): void {
  // Retrieve list of all users to be disabled.
  $query = \Drupal::database()->select('user_expire', 'ue');

  $expired_users = $query->condition('ue.expiration', \Drupal::time()->getRequestTime(), '<=')
    ->fields('ue', ['uid'])
    ->execute()
    ->fetchCol();

  $accounts = [];
  foreach ($expired_users as $uid) {
    $accounts[] = \Drupal::entityTypeManager()->getStorage('user')->load($uid);
  }
  user_expire_expire_users($accounts);
}

/**
 * Set a specific user's expiration time.
 *
 * @param object $account
 *   A user object to modify.
 * @param int $expiration
 *   (Optional) An expiration time to set for the user. If this value is
 *   omitted, it will be used to reset a user's expiration time.
 */
function user_expire_set_expiration(object $account, ?int $expiration = NULL): void {
  if (!empty($expiration)) {
    // If there's an expiration, save it.
    \Drupal::database()->merge('user_expire')
      ->key('uid', $account->id())
      ->fields([
        'uid' => $account->id(),
        'expiration' => $expiration,
      ])
      ->execute();

    $account->expiration = $expiration;
    user_expire_notify_user($account);
  }
  else {
    // If the expiration is not set, delete any value that might be set.
    if (!$account->isNew()) {
      // New accounts can't have a record to delete.
      // Existing records (!is_new) might.
      // Remove user expiration times for this user.
      $deleted = \Drupal::database()->delete('user_expire')
        ->condition('uid', $account->id())
        ->execute();

      // Notify user that expiration time has been deleted.
      if ($deleted) {
        \Drupal::messenger()->addMessage(t("%name's expiration date has been reset.", ['%name' => $account->getAccountName()]));
      }
    }
  }
}

/**
 * Expire a group of users.
 *
 * @param array $accounts
 *   A set of user objects to expire.
 */
function user_expire_expire_users(array $accounts): void {
  foreach ($accounts as $account) {
    if ($account) {
      // Block user's account.
      $account->block();
      \Drupal::entityTypeManager()->getStorage('user')->save($account);
      // Remove current expiration time.
      user_expire_set_expiration($account);
      // Log notification to watchdog.
      \Drupal::logger('user_expire')->info('User %name has expired.', ['%name' => $account->getAccountName()]);
    }
  }
}

/**
 * Expire a single user.
 *
 * @param object $account
 *   A single user object to expire.
 */
function user_expire_expire_user(object $account): void {
  user_expire_expire_users([$account]);
}

/**
 * Displays a message to users with expiring accounts.
 *
 * @param object $account
 *   (Optional) A user object on which to report.
 */
function user_expire_notify_user(?object $account = NULL): void {
  $user = \Drupal::currentUser();

  if (is_null($account)) {
    $account = $user;
  }

  // Only display a message on accounts with a current expiration date.
  if (empty($account->expiration)) {
    return;
  }

  if ($user->id() == $account->id()) {
    // Notify current user that expiration time is in effect.
    \Drupal::messenger()->addMessage(t("Your account's expiration date is set to @date.", ['@date' => \Drupal::service('date.formatter')->format($account->expiration)]));
  }
  else {
    // Notify user that expiration time is in effect for this user.
    \Drupal::messenger()->addMessage(t("%name's expiration date is set to @date.", [
      '%name' => $account->getAccountName(),
      '@date' => \Drupal::service('date.formatter')->format($account->expiration),
    ]));
  }
}

/**
 * Warns users with an upcoming expiration by roles.
 */
function user_expire_expire_by_role_warning(): void {
  $config = \Drupal::configFactory()->getEditable('user_expire.settings');
  $logger = \Drupal::logger('user_expire');
  $last_run = \Drupal::state()->get('user_expire_last_run', 0);
  $warning_frequency = $config->get('frequency');

  // Warn people every 2 days.
  if ($last_run && $last_run > (\Drupal::time()->getRequestTime() - $warning_frequency)) {
    \Drupal::logger('user_expire')->debug('Skipping warning as it was run within the last @hours hours',
      ['@hours' => ($warning_frequency / (60 * 60))]);
    return;
  }
  // Find people to warn.
  $rules = user_expire_get_role_rules();
  $rules = array_filter($rules);

  $warning_offset = $config->get('offset');

  foreach ($rules as $rid => $inactivity_period) {
    $uids_to_warn = user_expire_find_users_to_expire_by_role($rid, $inactivity_period - $warning_offset);
    if ($uids_to_warn) {
      foreach ($uids_to_warn as $uid) {
        $account = \Drupal::entityTypeManager()->getStorage('user')->load($uid->uid);
        if (!$account) {
          $logger->debug('Skipping warning @uid as it failed to load a valid user', [
            '@uid' => $uid->uid,
          ]);
        }
        else {
          // Send a notification email.
          $logger->info('Sending warning about expiring account @name by role', ['@name' => $account->getAccountName()]);
          \Drupal::service('plugin.manager.mail')->mail('user_expire', 'expiration_warning', $account->getEmail(), $account->getPreferredLangcode(),
            [
              'account' => $account,
            ]
          );
        }
      }
    }
  }
  \Drupal::state()->set('user_expire_last_run', \Drupal::time()->getRequestTime());
}

/**
 * Expires user by roles according to rules in the database.
 */
function user_expire_expire_by_role(): void {
  $rules = user_expire_get_role_rules();
  $logger = \Drupal::logger('user_expire');

  foreach ($rules as $rid => $inactivity_period) {
    $uids_to_expire = user_expire_find_users_to_expire_by_role($rid, $inactivity_period);
    if ($uids_to_expire) {
      foreach ($uids_to_expire as $uid) {
        $account = \Drupal::entityTypeManager()->getStorage('user')->load($uid->uid);
        if (!$account) {
          $logger->warning('Skipping @uid as it failed to load a valid user', [
            '@uid' => $uid->uid,
          ]);
        }
        else {
          $logger->info('Expiring account @name by role', ['@name' => $account->getAccountName()]);
          user_expire_expire_user($account);
        }
      }
    }
  }
}

/**
 * Finds users to expire by role and expiration period.
 *
 * @param string $role_id
 *   The role ID to search for.
 * @param int $seconds_since_login
 *   Seconds since login. To find users *about* to expire, use a smaller number.
 *
 * @return \Drupal\Core\Database\StatementInterface|null
 *   Returns an iterator for use in a loop.
 */
function user_expire_find_users_to_expire_by_role(string $role_id, int $seconds_since_login): ?StatementInterface {
  // An inactivity period of zero means the rule is disabled for the role.
  if (empty($seconds_since_login)) {
    return NULL;
  }

  // Find all the of users that need to be expired.
  $query = \Drupal::database()->select('users_field_data', 'u');

  $query->fields('u', ['uid'])
    ->condition('status', 1, '=')
    ->condition('u.uid', 0, '<>');

  // Conditional fragment for checking on access.
  $db_and_access = new Condition('AND');
  $db_and_access->condition('u.access', \Drupal::time()->getRequestTime() - $seconds_since_login, '<=')
    ->condition('u.access', 0, '>');

  // Conditional fragment for checking on created.
  $db_and_created = new Condition('AND');
  $db_and_created->condition('u.created', \Drupal::time()->getRequestTime() - $seconds_since_login, '<=')
    ->condition('u.access', 0, '=');

  // Now OR the access and created fragments together.
  $access_or_created = new Condition('OR');
  $access_or_created->condition($db_and_access)
    ->condition($db_and_created);

  // And finally, AND them together with the status and uid checks.
  $query->condition($access_or_created);

  // If this role is not the authenticated role, add a condition on the role.
  // The Authenticated "role" is not in this table as it affects all users.
  if (RoleInterface::AUTHENTICATED_ID != $role_id) {
    $query->join('user__roles', 'ur', 'u.uid = ur.entity_id');
    $query->condition('ur.roles_target_id', $role_id, '=');
  }
  return $query->execute();
}

/**
 * Gets the role inactivity rules.
 *
 * @return mixed
 *   An array of objects keyed by rid of rid and inactivity_period or FALSE.
 */
function user_expire_get_role_rules(): mixed {
  $config_factory = \Drupal::configFactory();
  $config = $config_factory->get('user_expire.settings');
  return array_filter($config->get('user_expire_roles')) ?: [];
}

/**
 * Implements hook_mail().
 */
function user_expire_mail($key, &$message, $params): void {
  if ($key == 'expiration_warning') {
    $token_service = \Drupal::token();
    $language_manager = \Drupal::languageManager();
    $langcode = $message['langcode'];
    $variables = ['user' => $params['account']];

    $language = $language_manager->getLanguage($langcode);
    $original_language = $language_manager->getConfigOverrideLanguage();
    $language_manager->setConfigOverrideLanguage($language);

    $config_factory = \Drupal::configFactory();
    $config = $config_factory->get('user_expire.settings');

    $token_options = ['langcode' => $langcode, 'callback' => 'user_mail_tokens', 'clear' => TRUE];
    $message['subject'] .= PlainTextOutput::renderFromHtml($token_service->replace($config->get('expiration_warning_mail.subject'), $variables, $token_options));
    $message['body'][] = $token_service->replace($config->get('expiration_warning_mail.body'), $variables, $token_options);

    $language_manager->setConfigOverrideLanguage($original_language);
  }
}

/**
 * Implements hook_ENTITY_TYPE_presave() for user entities.
 *
 * If the account was blocked but is now active, update the expiry so it is
 * not re-blocked by the next cron run.
 */
function user_expire_user_presave(User $account) {
  $original = DeprecationHelper::backwardsCompatibleCall(
    \Drupal::VERSION,
    '11.2.0',
    fn() => $account->getOriginal(),
    fn() => $account->original,
  );

  if (!empty($original) && $original->isBlocked() && $account->isActive()) {
    $account->setLastAccessTime(\Drupal::time()->getRequestTime());
  }
}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc