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

role_expire.module
<?php

/**
 * @file
 * Role Expire module.
 *
 * Enables user roles to expire on given time.
 */

use Drupal\Component\Render\FormattableMarkup;
use Drupal\Component\Utility\Html;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\role_expire\Event\RoleExpiresEvent;
use Drupal\user\Entity\Role;
use Drupal\user\Entity\User;
use Drupal\user\RoleInterface;
use Drupal\user\UserInterface;

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Add role expiration fields to user register/edit forms.
 */
function role_expire_form_user_form_alter(&$form, FormStateInterface $form_state): void {
  $account = \Drupal::routeMatch()->getParameter('user');

  $form = array_merge_recursive($form, role_expire_add_expiration_input($account));
  $form['#validate'][] = 'role_expire_user_form_submit_validate';
  $form['actions']['submit']['#submit'][] = 'role_expire_user_form_submit';
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Add role default duration field to role edit form.
 */
function role_expire_form_user_role_form_alter(&$form, FormStateInterface $form_state): void {

  if (\Drupal::currentUser()->hasPermission('edit role expire default duration') || \Drupal::currentUser()->hasPermission('administer users')) {
    $formatted_link = new FormattableMarkup(
      '<a href="@link" target="_blank">strtotime</a>',
      [
        '@link' => 'http://php.net/manual/en/function.strtotime.php',
      ]
    );

    $rid = $form['id']['#default_value'] ?? '';
    $form['role_expire'] = [
      '#title' => t("Default duration for the role"),
      '#type' => 'textfield',
      '#size' => 30,
      '#default_value' => \Drupal::service('role_expire.api')
        ->getDefaultDuration($rid),
      '#maxlength' => 32,
      '#attributes' => ['class' => ['role-expire-role-expiry']],
      '#description' => t('Enter the time span you want to set as the default duration for this role. Examples: 12 hours, 1 day, 3 days, 4 weeks, 3 months, 1 year. Leave blank for no default duration. (If you speak php, this value may be any @link-compatible relative form.)',
        ['@link' => $formatted_link]
      ),
    ];
    $form['#validate'][] = 'role_expire_user_admin_role_validate';
    $form['actions']['submit']['#submit'][] = 'role_expire_user_admin_role_submit';
    $form['actions']['delete']['#submit'][] = 'role_expire_user_admin_role_submit_delete';
  }
}

/**
 * Form validation handler called by role_expire_form_user_admin_role_alter.
 *
 * Ensure that the specified duration is a valid, relative, positive strtotime-
 * compatible string.
 */
function role_expire_user_admin_role_validate($form, FormStateInterface &$form_state): void {
  $values = $form_state->getValues();

  if (!empty($values['role_expire'])) {
    $duration_string = Html::escape($values['role_expire']);
    /*
     * Make sure it's a *relative* duration string. That is, it will result in a
     * different strtotime when a different 'now' value is used.
     */
    $now = time();
    $timestamp = strtotime($duration_string, $now);
    $timestamp2 = strtotime($duration_string, $now - 100);

    if ($timestamp === FALSE || $timestamp < 0) {
      // Invalid format.
      $form_state->setErrorByName('role_expire', 'Role expiry default duration must be a strtotime-compatible string.');
    }
    elseif ($timestamp < $now) {
      // In the past.
      $form_state->setErrorByName('role_expire', 'Role expiry default duration must be a <strong>future</strong> strtotime-compatible string.');
    }
    elseif ($timestamp == $timestamp2) {
      /*
       * This is an absolute (or special) timestamp. That's not allowed
       * (not relative).
       */
      $form_state->setErrorByName('role_expire', 'Role expiry default duration must be a <strong>relative</strong> strtotime-compatible string.');
    }
  }
}

/**
 * Form submit handler called by role_expire_form_user_admin_role_alter.
 *
 * Updates default duration in database.
 */
function role_expire_user_admin_role_submit($form, FormStateInterface &$form_state): void {
  $values = $form_state->getValues();

  /*
   * If the form doesn't specify a default duration, then delete default
   * duration. Otherwise, set the default duration to what's specified.
   */
  if (!empty($values['role_expire'])) {
    $duration_string = Html::escape($values['role_expire']);
    \Drupal::service('role_expire.api')->setDefaultDuration($values['id'], $duration_string);
    \Drupal::service('messenger')->addMessage('New default role expiration set.');
  }
  else {
    \Drupal::service('role_expire.api')->deleteDefaultDuration($values['id']);
  }
}

/**
 * Form delete handler called by role_expire_form_user_admin_role_alter.
 *
 * Removes default duration in database.
 */
function role_expire_user_admin_role_submit_delete($form, FormStateInterface &$form_state): void {
  $values = $form_state->getValues();

  \Drupal::service('role_expire.api')->deleteDefaultDuration($values['id']);
}

/**
 * Form validation handler called by user_register_form & user_form alter hooks.
 *
 * Allows to get and save the current roles of the user before the new user data
 * is actually saved. By doing this, in the submit method we can ensure role
 * expire data consistency.
 *
 * https://drupal.stackexchange.com/questions/200620/insert-a-value-to-form-state
 */
function role_expire_user_form_submit_validate($form, FormStateInterface &$form_state): void {
  $account = $form_state->getFormObject()->getEntity();
  $original_roles = $account->getRoles();
  $form_state->set('original_roles', $original_roles);
}

/**
 * Form submit handler called by user_register_form and user_form alter hooks.
 *
 * @todo This method needs debugging.
 *
 * On D7 version, this code was inside hook_user_update. Updates default
 * duration in database.
 */
function role_expire_user_form_submit($form, FormStateInterface &$form_state): void {
  $values = $form_state->getValues();

  /*
   * Only rely on Role Delegation data if the user hasn't accessed to the normal
   * roles field.
   */
  if (!\Drupal::currentUser()->hasPermission('administer permissions')) {
    // If Role Delegation module is used.
    if (isset($values['role_change'])) {
      $values['roles'] = [];
      foreach ($values['role_change'] as $rid) {
        if (isset($rid['target_id'])) {
          $values['roles'][] = $rid['target_id'];
        }
        else {
          $values['roles'][] = $rid;
        }
      }
    }
  }

  $account = $form_state->getFormObject()->getEntity();
  $original_roles = $form_state->get('original_roles') ?? [];

  if ((\Drupal::currentUser()->hasPermission('edit users role expire') || \Drupal::currentUser()->hasPermission('administer users'))) {

    // Add roles expiry information for the user role.
    foreach ($values as $key => $value) {
      if (strpos($key, 'role_expire_') === 0) {
        $rid = substr($key, strlen('role_expire_'));
        if ($value != '' && in_array($rid, $values['roles'])) {
          $expiry_timestamp = strtotime($value);
          \Drupal::service('role_expire.api')->writeRecord($account->id(), $rid, $expiry_timestamp);
        }
        else {
          $roleExpirationCanBeDeleted = \Drupal::service('role_expire.api')->roleExpirationCanBeDeletedOnUserEditSave($rid);
          if ($roleExpirationCanBeDeleted) {
            \Drupal::service('role_expire.api')
              ->deleteRecord($account->id(), $rid, FALSE);
          }
        }
      }
    }

    if (isset($values['roles'])) {

      /*
       * Add default expiration to any new roles that have been given to the
       * user.
       */
      $new_roles = array_diff($values['roles'], $original_roles);
      if (!empty($new_roles)) {
        /*
         * We have the new roles, loop over them and see whether we need to
         * assign expiry to them.
         */
        foreach ($new_roles as $role_id) {
          \Drupal::service('role_expire.api')->processDefaultRoleDurationForUser($role_id, $account->id());
        }
      }

      // Remove expiration for roles that have been removed from the user.
      $del_roles = array_diff($original_roles, $values['roles']);
      if (!empty($del_roles)) {
        /*
         * We have the deleted roles, loop over them and remove their expiry
         * info.
         */
        foreach ($del_roles as $role_id) {
          $roleExpirationCanBeDeleted = \Drupal::service('role_expire.api')->roleExpirationCanBeDeletedOnUserEditSave($role_id);
          if ($roleExpirationCanBeDeleted) {
            \Drupal::service('role_expire.api')
              ->deleteRecord($account->id(), $role_id);
          }
        }
      }

    } // if values[roles]

  } // if permissions

}

/**
 * Implements hook_user_insert().
 */
function role_expire_user_insert(UserInterface $account): void {

  $role_expire_api = \Drupal::service('role_expire.api');
  $enabled_roles = $role_expire_api->getEnabledExpirationRoles();

  if (!empty($enabled_roles)) {

    // This adds default expiration to any new
    // roles that have been given to the user.
    $roles = $account->getRoles();

    // We have the new roles, loop over them and
    // see whether we need to assign expiry to them.
    foreach ($roles as $role_id) {
      if (in_array($role_id, $enabled_roles)) {
        $role_expire_api->processDefaultRoleDurationForUser($role_id, $account->id());
      }
    }
  }
}

/**
 * Implements hook_user_cancel().
 */
function role_expire_user_cancel($edit, UserInterface $account, $method): void {
  // Delete user records.
  \Drupal::service('role_expire.api')->deleteUserRecords($account->id());
}

/**
 * Implements hook_user_delete().
 */
function role_expire_user_delete(UserInterface $account): void {
  // Delete user records.
  \Drupal::service('role_expire.api')->deleteUserRecords($account->id());
}

/**
 * Implements hook_user_load().
 */
function role_expire_user_load($users): void {
  /*
   * We don't load the information to the user object. Other modules can use
   * our API to query the information.
   *
   * Load the starter roles into a static cache so it is easy to see what has
   * changed later on.
   */
  foreach ($users as $account) {
    _role_static_user_roles($account->id(), $account->getRoles());
  }
}

/**
 * Implements hook_ENTITY_TYPE_view() for user entities.
 */
function role_expire_user_view(&$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode): void {
  $account = $build['#user'];
  $currentUser = \Drupal::currentUser();

  if ($display->getComponent('role_expire')) {

    // Only show the role expire field to role administrators or the user.
    if ($currentUser->hasPermission('administer role expire')
        || $currentUser->hasPermission('edit users role expire')
        || $currentUser->hasPermission('administer users')
        || $currentUser->id() == $account->id()
    ) {

      // 1. Gather all role expiration information.
      $roles = [];
      $expiry_roles = \Drupal::service('role_expire.api')->getAllUserRecords($account->id());
      $role_names = array_map(
        fn(RoleInterface $role) => $role->label(),
        Role::loadMultiple());
      foreach (($account->getRoles()) as $rid) {
        if (array_key_exists($rid, $expiry_roles)) {
          $roles[] = t("%role role expiration date: %datetime", [
            '%role' => $role_names[$rid],
            '%datetime' => \Drupal::service('date.formatter')->format(
              $expiry_roles[$rid]
            ),
          ]
          );
        }
        else {
          $roles[] = t("%role role (no expiration set)", [
            '%role' => $role_names[$rid],
          ]
          );
        }
      }

      // 2. Build role expiration information.
      if ($roles) {
        $build['role_expire'] = [
          '#theme' => 'item_list',
          '#items' => $roles,
          '#title' => t('Roles'),
          '#attributes' => ['class' => ['role-expiry-roles']],
          '#weight' => 1000,
        ];
      }
    }
  }
}

/**
 * Implements hook_entity_extra_field_info().
 */
function role_expire_entity_extra_field_info(): array {
  // Add pseudo field.
  $fields['user']['user']['display']['role_expire'] = [
    'label' => t('Role expiration'),
    'weight' => 1000,
  ];

  return $fields;
}

/**
 * Implements hook_cron().
 *
 * @todo This method needs intensive debugging.
 */
function role_expire_cron(): void {
  $expires = \Drupal::service('role_expire.api')->getExpired();

  if ($expires) {
    foreach ($expires as $expire) {

      // Remove the role expiration record from the role_expires table.
      \Drupal::service('role_expire.api')->deleteRecord($expire->uid, $expire->rid);

      // Remove the role from the user.
      $account = User::load($expire->uid);

      // If the account *does* exist, update it.
      if (!empty($account)) {

        // Assign a new role after expiration if requested given configuration.
        $new_roles = \Drupal::service('role_expire.api')->getRolesAfterExpiration();
        if (!empty($new_roles) && !empty($new_roles[$expire->rid])) {
          $new_rid = $new_roles[$expire->rid];
          $account->addRole($new_rid);
          \Drupal::service('role_expire.api')->processDefaultRoleDurationForUser($new_rid, $account->id());
          \Drupal::logger('role_expire')->notice(t('Added role @role to user @account.',
            [
              '@role' => $new_rid,
              '@account' => $account->id(),
            ]
          )
          );
        }

        $account->removeRole($expire->rid);
        $account->save();

        /*
         * Rules integration.
         * #3193800: RoleExpiresEvent should not depend on Rules module
         */
        $event = new RoleExpiresEvent($account, $expire->rid);
        $event_dispatcher = \Drupal::service('event_dispatcher');
        $event_dispatcher->dispatch($event, RoleExpiresEvent::EVENT_NAME);

        \Drupal::logger('role_expire')->notice(t('Removed role @role from user @account.',
          [
            '@role' => $expire->rid,
            '@account' => $account->id(),
          ]
        )
        );
      }
      else {

        // The account doesn't exist. Throw a warning message.
        \Drupal::logger('role_expire')->notice(t('Data integrity warning: Role_expire table updated, but no user with uid @uid.', ['@uid' => $expire->uid]));
      }
    }
  }
}

/**
 * Add form element that accepts the role expiration time.
 *
 * @param \Drupal\user\Entity\UserInterface $account
 *   Edited user or null.
 *
 * @return array
 *   Form element.
 */
function role_expire_add_expiration_input(?UserInterface $account): array {
  $form = [];

  if (\Drupal::currentUser()->hasPermission('edit users role expire') || \Drupal::currentUser()->hasPermission('administer users')) {

    $form['#attached']['library'][] = 'role_expire/role_expire';
    $form['roles']['#attributes'] = ['class' => ['role-expire-roles']];

    foreach (_role_expire_get_role() as $rid => $role) {
      if (!is_null($account)) {
        $expiry_timestamp = \Drupal::service('role_expire.api')->getUserRoleExpiryTime($account->id(), $rid);
      }
      else {
        $expiry_timestamp = '';
      }

      // Describe in which ways the role expire field can be filled in. If there
      // is a default duration configured for the role in question, this will be
      // noted in the description.
      $default_duration = \Drupal::service('role_expire.api')->getDefaultDuration($rid);
      $options_list = [
        '#theme' => 'item_list',
        '#items' => [],
      ];
      if ($default_duration) {
        $options_list['#items']['blank'] = t('Leave the field blank. In this case, the role will expire according to the default setting, which is %default.', [
          '%default' => $default_duration,
        ]);
      }
      else {
        $options_list['#items']['blank'] = t('Leave the field blank. In this case, the role will never expire (since no default duration is configured for this role).');
      }
      $options_list['#items']['date-time'] = t('Enter a date and time in the format YYYY-MM-DD HH:MM:SS. The role will expire after this time.');
      $options_list['#items']['relative-time'] = t("Use a relative time, for example: '1 day', '2 months', '1 year' or '3 years'.");

      $description = t('You have the following options: @options', [
        '@options' => \Drupal::service('renderer')->renderRoot($options_list),
      ]);
      $description .= t('Note that role expiration depends on a cron job to run, so it might not expire at the exact time that is configured here.');

      $expirationExpanded = \Drupal::service('role_expire.api')->expirationExpanded();
      $defaultExpiration = !empty($expiry_timestamp) ? date("Y-m-d H:i:s", $expiry_timestamp) : '';
      $defaultExpirationTitle = !empty($defaultExpiration) ? $defaultExpiration : t('No expiration set');

      $form['role_expire_data_' . $rid] = [
        '#type' => 'details',
        '#title' => t('Role expiration (@current)', ['@current' => $defaultExpirationTitle]),
        '#open' => $expirationExpanded ?? FALSE,
        '#attributes' => ['class' => ['role-expire-details']],
      ];

      // Display a field in which the role expiration time can be configured for
      // the user that is being edited.
      $form['role_expire_data_' . $rid]['role_expire_' . $rid] = [
        '#title' => t("%role role expiration date/time", ['%role' => $role]),
        '#type' => 'textfield',
        '#default_value' => $defaultExpiration,
        '#attributes' => ['class' => ['role-expire-role-expiry']],
        '#description' => $description,
      ];
    }

    $form['#validate'][] = '_role_expire_validate_role_expires';
  }

  return $form;
}

/**
 * Store user roles for this page request.
 *
 * Helper function.
 *
 * @return array|false|mixed
 *   Array of roles
 * phpcs:disable.
 */
function _role_static_user_roles($id, $roles = ''): mixed {
  // phpcs:enable.
  static $user_roles = [];

  if (!isset($user_roles[$id]) && is_array($roles)) {
    $user_roles[$id] = $roles;
  }
  if (!isset($user_roles[$id])) {
    return FALSE;
  }
  else {
    return $user_roles[$id];
  }

}

/**
 * Get valid roles.
 *
 * Helper function.
 *
 * @return array
 *   Array of roles.
 * phpcs:disable.
 */
function _role_expire_get_role(): array {
  // phpcs:enable.
  $roles_out = [];
  $roles = Role::loadMultiple();
  unset($roles[RoleInterface::ANONYMOUS_ID]);
  unset($roles[RoleInterface::AUTHENTICATED_ID]);

  $enabled_roles = \Drupal::service('role_expire.api')->getEnabledExpirationRoles();

  // Return in the same format as in D7 version to simplify D8 upgrade.
  foreach ($roles as $role) {
    if (in_array($role->id(), $enabled_roles)) {
      $roles_out[$role->id()] = $role->label();
    }
  }
  return $roles_out;
}

/**
 * Form validation handler for the role expiration on the user_profile_form().
 *
 * Helper function.
 *
 * @see user_profile_form()
 * phpcs:disable.
 */
function _role_expire_validate_role_expires(&$form, FormStateInterface &$form_state): void {
  // phpcs:enable.

  $values = $form_state->getValues();
  date_default_timezone_set(date_default_timezone_get());
  $time = \Drupal::time()->getRequestTime();

  foreach ($values as $name => $value) {
    if (strpos($name, 'role_expire_') === 0 && trim($value) != '') {
      $expiry_time = strtotime($value);
      if (!$expiry_time) {
        $form_state->setErrorByName($name, t("Role expiry is not in correct format."));
      }
      if ($expiry_time <= $time) {
        $form_state->setErrorByName($name, t("Role expiry must be in the future."));
      }
    }
  }
}

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

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