simple_account_policy-1.0.0/simple_account_policy.module
simple_account_policy.module
<?php
/**
* @file
* Module File.
*
* @todo add extra email settings to the site.settings mail form (and store the input in our own config)
*/
use Drupal\Component\Render\PlainTextOutput;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Form\FormState;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
use Drupal\user\Entity\User;
use Drupal\user\UserInterface;
/**
* Implements hook_help().
*/
function simple_account_policy_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
// Main module help for the simple_account_policy module.
case 'help.page.simple_account_policy':
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('This module implements a simple account policy with the following configurable rules:') . '</p>';
$output .= '<ul>' . t('<li>Username email and username must match (enforces an email as username)</li>') . '</ul>';
$output .= '<ul>' . t('<li>Username allowed patterns (usernames must follow this pattern to be valid)</li>') . '</ul>';
$output .= '<ul>' . t('<li>Username ignore patterns (do not apply policy for usernames matching this pattern)</li>') . '</ul>';
$output .= '<ul>' . t('<li>Email allowed patterns (email must follow this pattern to be valid)</li>') . '</ul>';
$output .= '<ul>' . t('<li>Cron check interval (interval at which module will check user policy on all users)</li>') . '</ul>';
$output .= '<ul>' . t('<li>The inactive period. When users do not login for this period of time, they will be blocked.</li>') . '</ul>';
$output .= '<ul>' . t('<li>Inactive warning period. If users are about to be blocked, send out a warning mail.</li>') . '</ul>';
$output .= '<ul>' . t('<li>The warning mail message and subject.</li>') . '</ul>';
return $output;
default:
}
}
/**
* Implements hook_cron().
*
* Block users after a period of time (default 3 months).
*/
function simple_account_policy_cron() {
/** @var \Drupal\simple_account_policy\AccountPolicyInterface $accountPolicy */
$accountPolicy = \Drupal::service('simple_account_policy');
$requestTime = \Drupal::time()->getRequestTime();
$last_run = \Drupal::state()
->get('simple_account_policy.last_cron_run', FALSE);
$inactive_interval = $accountPolicy->inactiveInterval();
if (empty($inactive_interval) || ($last_run && ($requestTime - $last_run) < $inactive_interval)) {
return FALSE;
}
// @todo we should do this with a queue to prevent
// issues on sites with lots of user.
$users = User::loadMultiple();
foreach ($users as $user) {
if ($accountPolicy->applyPolicy($user) && $accountPolicy->shouldBeDeleted($user)) {
$accountPolicy->delete($user);
continue;
}
/** @var \Drupal\user\Entity\User $user */
if ($user->isBlocked()) {
continue;
}
if ($accountPolicy->applyPolicy($user)) {
if ($accountPolicy->shouldIssueWarning($user)) {
$accountPolicy->issueWarning($user);
}
else {
if ($accountPolicy->isInactive($user)) {
$accountPolicy->block($user);
}
}
}
}
\Drupal::state()->set('simple_account_policy.last_cron_run', time());
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function simple_account_policy_form_user_form_alter(&$form, FormStateInterface $form_state, $form_id) {
$build_info = $form_state->getBuildInfo();
/** @var \Drupal\user\ProfileForm $profileForm */
$profileForm = $build_info['callback_object'];
/** @var \Drupal\user\Entity\User $currentUser */
$currentUser = $profileForm->getEntity();
/** @var \Drupal\simple_account_policy\AccountPolicyInterface $accountPolicy */
$accountPolicy = \Drupal::service('simple_account_policy');
$username_prevent_changes = ($accountPolicy->getConfiguration()->get('username_prevent_changes') ?? FALSE);
if (!$currentUser->isNew() && $accountPolicy->applyPolicy($currentUser) && $username_prevent_changes) {
$form['account']['name']['#disabled'] = TRUE;
}
// We need user input here, since fields are not in values (yet).
$input = $form_state->getUserInput();
$data = [
'mail' => $input['mail'] ?? '',
'name' => $input['name'] ?? '',
];
$errors = empty($pass) ? [] : $accountPolicy->validate($currentUser, $data);
$policy = $accountPolicy->policy($currentUser, $errors);
if (!empty($policy['mail'])) {
$form['account']['mail']['#description'] = $policy['mail'];
}
if (!empty($policy['name'])) {
$form['account']['name']['#description'] = $policy['name'];
}
$form['#validate'][] = 'simple_account_policy_form_user_validate';
}
/**
* Validation function for User form.
*/
function simple_account_policy_form_user_validate($form, FormState $form_state) {
$build_info = $form_state->getBuildInfo();
$form = $build_info['callback_object'];
/** @var \Drupal\user\Entity\User $currentUser */
$currentUser = $form->getEntity();
/** @var \Drupal\simple_account_policy\AccountPolicyInterface $accountPolicy */
$accountPolicy = \Drupal::service('simple_account_policy');
if ($accountPolicy->applyPolicy($currentUser)) {
$values = [
'mail' => $form_state->getValue('mail'),
'name' => $form_state->getValue('name'),
];
$validation = $accountPolicy->validate($currentUser, $values);
if (!empty($validation['mail'])) {
$form_state->setErrorByName('mail', t("The email does not satisfy the account policy rules."));
}
if (!empty($validation['name'])) {
$form_state->setErrorByName('name', t("The user name does not satisfy the account policy rules."));
}
}
}
/**
* Set an extra operations to activate users.
*
* @param array $operations
* Operations.
* @param \Drupal\Core\Entity\EntityInterface $entity
* Entity.
*/
function simple_account_policy_entity_operation_alter(array &$operations, EntityInterface $entity) {
$currentUser = \Drupal::currentUser();
if ($currentUser->hasPermission('account policy activate users') && $entity->getEntityTypeId() == 'user') {
if ($entity instanceof UserInterface && $entity->isBlocked()) {
$operations['activate'] = [
'title' => t('Activate'),
'weight' => 999,
'url' => Url::fromRoute('simple_account_policy.activate', ['user' => $entity->id()]),
];
}
}
if ($currentUser->hasPermission('account policy block users') && $entity->getEntityTypeId() == 'user') {
if ($entity instanceof UserInterface && $entity->isActive()) {
$operations['block'] = [
'title' => t('Block'),
'weight' => 999,
'url' => Url::fromRoute('simple_account_policy.block', ['user' => $entity->id()]),
];
}
}
}
/**
* Implements hook_mail().
*/
function simple_account_policy_mail($key, &$message, $params) {
$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 = \Drupal::config('simple_account_policy.settings');
$token_options = ['langcode' => $langcode, 'clear' => TRUE];
$message['subject'] .= PlainTextOutput::renderFromHtml($token_service->replace($config->get($key . '.subject'), $variables, $token_options));
$message['body'][] = $token_service->replace($config->get($key . '.body'), $variables, $token_options);
$language_manager->setConfigOverrideLanguage($original_language);
}
/**
* Helper to print out a readable time period in the past or future.
*
* In 5 days
* 5 days ago
* ...
*
* @param string|int $ts
* The timestamp to calculate against the current time.
* @param string $future_txt
* Untranslated text to display if timestamp is in the future.
* Use @output to position the resulting period.
* @param string $past_txt
* Untranslated text to display if timestamp is in the past.
* Use @output to position the resulting period.
* @param string $langcode
* The langcode to translate the result in, if none given this
* is the current language.
*
* @return string
* String
*/
function simple_account_policy_time_ago($ts, $future_txt = "@output", $past_txt = "@output", $langcode = NULL): string {
$langcode = $langcode ?? \Drupal::languageManager()
->getCurrentLanguage()
->getId();
if (is_string($ts) && !ctype_digit($ts)) {
$ts = strtotime($ts);
}
$diff = time() - $ts;
if ($diff == 0) {
$output = t('now', [], ['langcode' => $langcode]);
// phpcs:disable Drupal.Semantics.FunctionT.NotLiteralString
return (string) t($future_txt, ['@output' => $output], ['langcode' => $langcode]);
}
elseif ($diff > 0) {
$day_diff = floor($diff / 86400);
switch (TRUE) {
case ($day_diff == 0 && $diff < 60):
$output = t('just now', [], ['langcode' => $langcode]);
break;
case ($day_diff == 0 && $diff < 120):
$output = t('1 minute ago', [], ['langcode' => $langcode]);
break;
case ($day_diff == 0 && $diff < 3600):
$output = t('@time minutes ago', ['@time' => floor($diff / 60)], ['langcode' => $langcode]);
break;
case ($day_diff == 0 && $diff < 7200):
$output = t('1 hour ago', [], ['langcode' => $langcode]);
break;
case ($day_diff == 0 && $diff < 86400):
$output = t('@time hours ago', ['@time' => floor($diff / 3600)], ['langcode' => $langcode]);
break;
case ($day_diff == 1):
$output = t('yesterday', [], ['langcode' => $langcode]);
break;
case ($day_diff < 7):
$output = t('@time days ago', ['@time' => $day_diff], ['langcode' => $langcode]);
break;
case ($day_diff < 31):
$output = t('@time weeks ago', ['@time' => ceil($day_diff / 7)], ['langcode' => $langcode]);
break;
case ($day_diff < 60):
$output = t('last month', [], ['langcode' => $langcode]);
break;
default:
/** @var \Drupal\Core\Datetime\DateFormatterInterface $date_formatter */
$date_formatter = \Drupal::service('date.formatter');
$output = $date_formatter->format($ts, 'long');
}
// phpcs:disable Drupal.Semantics.FunctionT.NotLiteralString
return (string) t($past_txt, ['@output' => $output], ['langcode' => $langcode]);
}
else {
$diff = abs($diff);
$day_diff = floor($diff / 86400);
switch (TRUE) {
case ($day_diff == 0 && $diff < 120):
$output = t('in a minute', [], ['langcode' => $langcode]);
break;
case ($day_diff == 0 && $diff < 3600):
$output = t('in @time minutes', ['@time' => floor($diff / 60)], ['langcode' => $langcode]);
break;
case ($day_diff == 0 && $diff < 7200):
$output = t('in an hour', [], ['langcode' => $langcode]);
break;
case ($day_diff == 0 && $diff < 86400):
$output = t('in @time hours', ['@time' => floor($diff / 3600)], ['langcode' => $langcode]);
break;
case ($day_diff == 1):
$output = t('tomorrow', [], ['langcode' => $langcode]);
break;
case ($day_diff < 4):
/** @var \Drupal\Core\Datetime\DateFormatterInterface $date_formatter */
$date_formatter = \Drupal::service('date.formatter');
$output = $date_formatter->format($ts, 'custom', 'l');
break;
case ($day_diff < 7 + (7 - intval(date('w')))):
$output = t('next week', [], ['langcode' => $langcode]);
break;
case (ceil($day_diff / 7) < 4):
$output = t('in @time weeks', ['@time' => ceil($day_diff / 7)], ['langcode' => $langcode]);
break;
case (date('n', $ts) == intval(date('n')) + 1):
$output = t('next month', [], ['langcode' => $langcode]);
break;
default:
/** @var \Drupal\Core\Datetime\DateFormatterInterface $date_formatter */
$date_formatter = \Drupal::service('date.formatter');
$output = $date_formatter->format($ts, 'long');
}
// phpcs:disable Drupal.Semantics.FunctionT.NotLiteralString
return (string) t($future_txt, ['@output' => $output], ['langcode' => $langcode]);
}
}
