block_inactive_users-8.x-1.4/src/InactiveUsersHandler.php
src/InactiveUsersHandler.php
<?php
namespace Drupal\block_inactive_users;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Mail\MailManagerInterface;
use Drupal\Core\State\StateInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Url;
use Drupal\Core\Utility\Token;
use Drupal\user\Entity\User;
use Drupal\user\UserInterface;
/**
* The InactiveUsersHandler Class.
*
* @category Drupal
* @package Drupal\block_inactive_users
* @author Rodrigue Tusse
* @license GPL-3.0+ https://www.gnu.org/licenses/gpl-3.0.txt
* @link https://www.drupal.org/project/block_inactive_users
*/
class InactiveUsersHandler {
use StringTranslationTrait;
const FORM_SETTINGS_CONFIG_OBJ_NAME = "block_inactive_users.settings";
const LOGGER_CHANNEL = "block_inactive_users";
/**
* A config factory service.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface
*/
protected $configFactoryService;
/**
* A logger instance.
*
* @var \Drupal\Core\Logger\LoggerChannelFactoryInterface
*/
protected $logger;
/**
* An language manager instance.
*
* @var \Drupal\Core\Language\LanguageManagerInterface
*/
protected $languageManager;
/**
* A time service.
*
* @var \Drupal\Component\Datetime\TimeInterface
*/
protected $timeService;
/**
* The user storage interface.
*
* @var \Drupal\user\UserStorageInterface
*/
protected $userStorage;
/**
* The module email configuration.
*
* @var \Drupal\Core\Config\ImmutableConfig
*/
protected $emailConfig;
/**
* The state store.
*
* @var Drupal\Core\State\StateInterface
*/
protected $state;
/**
* The token replacement instance.
*
* @var \Drupal\Core\Utility\Token
*/
protected $token;
/**
* The mail manager service.
*
* @var \Drupal\Core\Mail\MailManagerInterface
*/
protected $mailManager;
/**
* Constructor.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* The config factory service.
* @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $loggerChannelFactory
* The logger channel factory.
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
* The language manager service.
* @param \Drupal\Component\Datetime\TimeInterface $time_service
* The time service.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\State\StateInterface $state
* The state service.
* @param \Drupal\Core\Utility\Token $token
* The token service.
* @param \Drupal\Core\Mail\MailManagerInterface $mail_manager
* The mail manager service.
*/
public function __construct(
ConfigFactoryInterface $config_factory,
LoggerChannelFactoryInterface $loggerChannelFactory,
LanguageManagerInterface $language_manager,
TimeInterface $time_service,
EntityTypeManagerInterface $entity_type_manager,
StateInterface $state,
Token $token,
MailManagerInterface $mail_manager,
) {
$this->configFactoryService = $config_factory;
$this->logger = $loggerChannelFactory;
$this->languageManager = $language_manager;
$this->timeService = $time_service;
$this->userStorage = $entity_type_manager->getStorage('user');
$this->emailConfig = $this->configFactoryService->get(self::FORM_SETTINGS_CONFIG_OBJ_NAME);
$this->state = $state;
$this->token = $token;
$this->mailManager = $mail_manager;
}
/**
* Disables an inactive user account.
*
* @param \Drupal\user\Entity\User $user
* A user object.
* @param bool $sendmail
* Boolean to trigger email.
*
* @return \Drupal\user\Entity\User
* Returns a blocked user object.
*
* @throws \Drupal\Core\Entity\EntityStorageException
*/
public function disableInactiveUsersStatus(User $user, $sendmail = TRUE) {
$user->block();
$user->save();
$this->logger->get(self::LOGGER_CHANNEL)->info($user->getAccountName() . ' has been disabled.');
if ($sendmail) {
$activation_link = $this->createReactivationUrl($user);
// Sends email notification.
$this->mailUser(
$this->emailConfig->get('block_inactive_users_from_email'),
$user,
$this->emailConfig->get('block_inactive_users_email_subject'),
$this->emailConfig->get('block_inactive_users_email_content'),
$activation_link
);
}
return $user;
}
/**
* Takes 2 timestamps and returns the interval as days or months.
*
* @param int $start
* A timestamp of the start time (e.g., last access or created time).
* @param int $end
* A timestamp of the end time (e.g., current time).
* @param string $unit
* The unit for the interval: 'months' (default) or 'days'.
*
* @return int
* Returns the interval in the requested unit.
*
* @throws \Exception
*/
public function timestampdiff($start, $end, $unit = 'months') {
if ($unit === 'days') {
return floor(($end - $start) / 86400);
}
// Default: months.
$begin = new \DateTime("@$start");
$finish = new \DateTime("@$end");
$interval = $begin->diff($finish);
return ($interval->y * 12) + ($interval->m);
}
/**
* Sends a email notification.
*
* @param string $from
* From email address.
* @param \Drupal\user\Entity\User $user
* Users first name.
* @param string $message_subject
* Message subject field.
* @param string $message_content
* Email template.
* @param string $activation_link
* Url to re-activate user.
*
* @return bool
* Returns true if mail was sent successfully.
*/
public function mailUser($from, $user, $message_subject, $message_content, $activation_link) {
$days_until_blocked = $this->emailConfig->get('block_inactive_users_days_until_blocked');
$custom_tokens = [
'[days-until-blocked]' => $days_until_blocked,
'[activation-link]' => $activation_link,
];
$from = $this->token->replace($from);
$data = [];
$data['params']['headers'] = [
'Content-Type' => 'text/html',
'MIME-Version' => '1.0',
'reply-to' => $from,
'from' => $this->token->replace('[site:name]') . ' <' . $from . '>',
];
$data['to'] = $user->getEmail();
$data['langcode'] = $user->getPreferredLangcode();
$data['params']['subject'] = $this->token->replace(
$message_subject, [
'user' => $user,
]
);
$data['params']['body'] = $this->token->replace(
$message_content, [
'user' => $user,
]
);
// Replace pseudo-tokens.
array_walk(
$custom_tokens, function ($value, $token) use (&$data) {
$data['params']['subject'] = str_replace($token, $value, $data['params']['subject']);
$data['params']['body'] = str_replace($token, $value, $data['params']['body']);
}
);
$mail = $this->mailManager->mail('block_inactive_users', 'block_inactive_users_warn', $data['to'], $data['langcode'], $data['params'], NULL, TRUE);
if ($mail['result'] != TRUE) {
$message = $this->t('There was a problem sending your email notification to @email.', ['@email' => $user->getEmail()]);
$this->logger->get('mail-log')->error($message);
return FALSE;
}
$message = $this->t('An email notification has been sent to @email.', ['@email' => $user->getEmail()]);
$this->logger->get('mail-log')->notice($message);
return FALSE;
}
/**
* Generates a URL to confirm an account reactivation request.
*
* @param \Drupal\user\UserInterface $account
* The user account object.
* @param array $options
* (optional) A keyed array of settings. Supported options are:
* - langcode: A language code to be used when generating locale-sensitive
* URLs. If langcode is NULL the users preferred language is used.
*
* @return string
* A unique URL that may be used to confirm the reactivation of the user
* account.
*/
private function createReactivationUrl(UserInterface $account, array $options = []) {
$timestamp = $this->timeService->getRequestTime();
$langcode = $options['langcode'] ?? $account->getPreferredLangcode();
$url_options = [
'absolute' => TRUE,
'language' => $this->languageManager->getLanguage($langcode),
];
return Url::fromRoute(
'block_inactive_users.reactivate_confirm', [
'user' => $account->id(),
'timestamp' => $timestamp,
'hashed_pass' => user_pass_rehash($account, $timestamp),
], $url_options
)->toString();
}
/**
* Takes 3 parameters to cancel user depending on rules setup.
*
* @param string $uid
* Uid of the user account being canceled.
* @param string $block_inactive_users_cancel_email
* Block inactive users cancel email.
* @param string $disable_account_method
* Disable account method.
*
* @return void
* No return value.
*
* @throws \Exception
*/
public function cancelUser($uid, $block_inactive_users_cancel_email, $disable_account_method) {
user_cancel(
[
'user_cancel_notify' => $block_inactive_users_cancel_email,
'user_cancel_method' => $disable_account_method,
], $uid, $disable_account_method
);
}
/**
* Warn inactive users of their impending block.
*
* @return void
* No return value.
*/
public function warn() {
// Bail if option is not enabled.
if (!$this->emailConfig->get('block_inactive_users_warn_send_email')) {
return;
}
$users = $this->getUsers();
$idle_time = $this->configFactoryService
->get(self::FORM_SETTINGS_CONFIG_OBJ_NAME)
->get('block_inactive_users_idle_time');
$days_until_blocked = $this->emailConfig->get('block_inactive_users_days_until_blocked');
$include_never_accessed = $this->emailConfig->get('block_inactive_users_include_never_accessed');
$current_time = time();
$current_time_obj = new \Datetime("@$current_time");
$from = $this->emailConfig->get('block_inactive_users_warn_from_email');
$subject = $this->emailConfig->get('block_inactive_users_warn_email_subject');
$template = $this->emailConfig->get('block_inactive_users_warn_email_content');
foreach ($users as $user) {
$warned_users = $this->state->get('block_inactive_users.warn') ?: [];
// Skip users who have already been warned.
if (in_array($user->id(), $warned_users)) {
continue;
}
$last_access = $user->getLastAccessedTime();
// Add months from configuration.
$ban_alert_date = strtotime("+$idle_time months", (int) $last_access);
// Remove days from ban date.
$ban_alert_date = strtotime("-$days_until_blocked days", $ban_alert_date);
$ban_alert_date = new \Datetime("@$ban_alert_date");
$user_days_until_blocked = $ban_alert_date->diff($current_time_obj)->days;
$activation_link = $this->createReactivationUrl($user);
if ($last_access != 0
&& $user_days_until_blocked <= $days_until_blocked
) {
$this->mailUser($from, $user, $subject, $template, $activation_link);
// Add state so that user doesn't receive multiple emails.
$this->state->set('block_inactive_users.warn', [...$warned_users, $user->id()]);
}
// If option enabled to include blocking of users
// who have never logged in.
// Calculate the creation time and block the user if the idle time period
// has elapsed.
if ($include_never_accessed == 1
&& $last_access == 0
&& $user_days_until_blocked <= $days_until_blocked
) {
$this->mailUser($from, $user, $subject, $template, $activation_link);
// Add state so that user doesn't receive multiple emails.
$this->state->set('block_inactive_users.warn', [...$warned_users, $user->id()]);
}
}
}
/**
* Get list of users to be possibly banned.
*
* @return \Drupal\user\Entity\User[]
* Array of user entities.
*/
public function getUsers() {
$exclude_user_roles = $this->emailConfig->get('block_inactive_users_exclude_roles');
$query = $this->userStorage->getQuery()->accessCheck(TRUE);
// Only return active users.
$query->condition('status', 1);
// Subquery to find users with excluded roles.
if (!empty($exclude_user_roles)) {
$subquery = $this->userStorage->getQuery()
->condition('roles', $exclude_user_roles, 'IN')
->accessCheck(TRUE)
->execute();
// Assuming execute() returns an array of user IDs, adjust as necessary.
if (!empty($subquery)) {
$query->condition('uid', $subquery, 'NOT IN');
}
}
// Don't block the admin account.
$query->condition('uid', 1, '!=');
$user_ids = $query->execute();
return $this->userStorage->loadMultiple($user_ids);
}
}
