You are here

role_expire.module in Role Expire 2.x

Same filename and directory in other branches
  1. 8 role_expire.module
  2. 6 role_expire.module
  3. 7 role_expire.module

Role Expire module.

Enables user roles to expire on given time.

File

role_expire.module
View source
<?php

/**
 * @file
 * Role Expire module.
 *
 * Enables user roles to expire on given time.
 */
use Drupal\user\Entity\User;
use Drupal\Component\Utility\Html;
use Drupal\Core\Session\AccountInterface;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\role_expire\Event\RoleExpiresEvent;
use Drupal\Core\Form\FormStateInterface;

/**
 * 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) {
  $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) {
  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',
    ]);
    $form['role_expire'] = [
      '#title' => t("Default duration for the role %role", [
        '%role' => ucfirst($form['label']['#default_value']),
      ]),
      '#type' => 'textfield',
      '#size' => 30,
      '#default_value' => \Drupal::service('role_expire.api')
        ->getDefaultDuration($form['id']['#default_value']),
      '#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 invoked 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) {
  $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 invoked by role_expire_form_user_admin_role_alter.
 *
 * Updates default duration in database.
 */
function role_expire_user_admin_role_submit($form, FormStateInterface &$form_state) {
  $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 invoked 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) {
  $values = $form_state
    ->getValues();
  \Drupal::service('role_expire.api')
    ->deleteDefaultDuration($values['id']);
}

/**
 * Form validation handler invoked by user_register_form and 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) {
  $account = $form_state
    ->getFormObject()
    ->getEntity();
  $original_roles = $account
    ->getRoles();
  $form_state
    ->set('original_roles', $original_roles);
}

/**
 * Form submit handler invoked 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) {
  $values = $form_state
    ->getValues();

  // Only rely on Role Delegation data if the user hasn't access 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) {
        $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);
          }
        }
      }
    }
    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 (isset($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 (isset($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($account) {
  if (\Drupal::currentUser()
    ->hasPermission('edit users role expire') || \Drupal::currentUser()
    ->hasPermission('administer users')) {

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

    // 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());
    }
  }
}

/**
 * Implements hook_user_cancel().
 */
function role_expire_user_cancel($edit, $account, $method) {

  // Delete user records.
  \Drupal::service('role_expire.api')
    ->deleteUserRecords($account
    ->id());
}

/**
 * Implements hook_user_delete().
 */
function role_expire_user_delete($account) {

  // Delete user records.
  \Drupal::service('role_expire.api')
    ->deleteUserRecords($account
    ->id());
}

/**
 * Implements hook_user_load().
 */
function role_expire_user_load($users) {

  /*
   * 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, $entity, $display, $view_mode) {
  $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());
      foreach ($account
        ->getRoles() as $rid) {
        if (array_key_exists($rid, $expiry_roles)) {
          $roles[] = t("%role role expiration date: %timedate", [
            '%role' => ucfirst($rid),
            '%timedate' => \Drupal::service('date.formatter')
              ->format($expiry_roles[$rid]),
          ]);
        }
      }

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

/**
 * Implements hook_entity_extra_field_info().
 */
function role_expire_entity_extra_field_info() {

  // 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() {
  $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.
         * https://fago.gitbooks.io/rules-docs/content/extending_rules/events.html
         * #3193800: RoleExpiresEvent should not depend on Rules module
         */
        $event = new RoleExpiresEvent($account, $expire->rid);
        $event_dispatcher = \Drupal::service('event_dispatcher');
        $event_dispatcher
          ->dispatch(RoleExpiresEvent::EVENT_NAME, $event);
        \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\User $account
 *   Edited user or null.
 *
 * @return array
 *   Form element.
 */
function role_expire_add_expiration_input($account) {
  $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 = '';
      }
      $form['role_expire_' . $rid] = [
        '#title' => t("%role role expiration date/time", [
          '%role' => $role,
        ]),
        '#type' => 'textfield',
        '#default_value' => !empty($expiry_timestamp) ? date("Y-m-d H:i:s", $expiry_timestamp) : '',
        '#attributes' => [
          'class' => [
            'role-expire-role-expiry',
          ],
        ],
        '#description' => t("Leave blank for default role expiry (never, or the duration you have set for the role), enter date and time in format <em>YYYY-MM-DD HH:MM:SS</em> or use relative time i.e. 1 day, 2 months, 1 year, 3 years."),
      ];
    }
    $form['#validate'][] = '_role_expire_validate_role_expires';
  }
  return $form;
}

/**
 * Store user roles for this page request.
 *
 * Helper function.
 *
 * @return array
 *   Array of roles
 */
function _role_static_user_roles($id, $roles = '') {
  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.
 */
function _role_expire_get_role() {
  $roles_out = [];
  $roles = user_roles(TRUE);
  unset($roles[AccountInterface::AUTHENTICATED_ROLE]);
  $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()
 */
function _role_expire_validate_role_expires(&$form, FormStateInterface &$form_state) {
  $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."));
      }
    }
  }
}

Functions

Namesort descending Description
role_expire_add_expiration_input Add form element that accepts the role expiration time.
role_expire_cron Implements hook_cron().
role_expire_entity_extra_field_info Implements hook_entity_extra_field_info().
role_expire_form_user_form_alter Implements hook_form_FORM_ID_alter().
role_expire_form_user_role_form_alter Implements hook_form_FORM_ID_alter().
role_expire_user_admin_role_submit Form submit handler invoked by role_expire_form_user_admin_role_alter.
role_expire_user_admin_role_submit_delete Form delete handler invoked by role_expire_form_user_admin_role_alter.
role_expire_user_admin_role_validate Form validation handler invoked by role_expire_form_user_admin_role_alter.
role_expire_user_cancel Implements hook_user_cancel().
role_expire_user_delete Implements hook_user_delete().
role_expire_user_form_submit Form submit handler invoked by user_register_form and user_form alter hooks.
role_expire_user_form_submit_validate Form validation handler invoked by user_register_form and user_form alter hooks.
role_expire_user_insert Implements hook_user_insert().
role_expire_user_load Implements hook_user_load().
role_expire_user_view Implements hook_ENTITY_TYPE_view() for user entities.
_role_expire_get_role Get valid roles.
_role_expire_validate_role_expires Form validation handler for the role expiration on the user_profile_form().
_role_static_user_roles Store user roles for this page request.