purge_users-8.x-2.0/src/Services/UserManagementService.php

src/Services/UserManagementService.php
<?php

declare(strict_types=1);

namespace Drupal\purge_users\Services;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\Sql\SqlEntityStorageInterface;
use Drupal\Core\Extension\ModuleHandlerInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\Session\AnonymousUserSession;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\user\UserInterface;

/**
 * Class that holds the purging logic.
 *
 * @package Drupal\purge_users\Services
 */
class UserManagementService implements UserManagementServiceInterface {

  use StringTranslationTrait;

  /**
   * Current user.
   *
   * @var \Drupal\Core\Session\AccountProxyInterface
   */
  protected $currentUser;

  /**
   * The config factory.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  protected $config;

  /**
   * The module handler.
   *
   * @var \Drupal\Core\Extension\ModuleHandlerInterface
   */
  protected $moduleHandler;

  /**
   * The messenger.
   *
   * @var \Drupal\Core\Messenger\MessengerInterface
   */
  protected $messenger;

  /**
   * Logger channel service.
   *
   * @var \Drupal\Core\Logger\LoggerChannelFactoryInterface
   */
  protected $loggerFactory;

  /**
   * The database connection.
   *
   * @var \Drupal\Core\Database\Connection
   */
  protected $connection;

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * UserManagementService constructor.
   *
   * @param \Drupal\Core\Session\AccountProxyInterface $current_user
   *   Current user.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   The config factory.
   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
   *   The module handler.
   * @param \Drupal\Core\Messenger\MessengerInterface $messenger
   *   The messenger.
   * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory
   *   The logger factory.
   * @param \Drupal\Core\Database\Connection $connection
   *   The db connection.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager service.
   */
  public function __construct(AccountProxyInterface $current_user, ConfigFactoryInterface $config_factory, ModuleHandlerInterface $module_handler, MessengerInterface $messenger, LoggerChannelFactoryInterface $logger_factory, Connection $connection, EntityTypeManagerInterface $entity_type_manager) {
    $this->currentUser = $current_user;
    $this->config = $config_factory;
    $this->moduleHandler = $module_handler;
    $this->messenger = $messenger;
    $this->loggerFactory = $logger_factory;
    $this->connection = $connection;
    $this->entityTypeManager = $entity_type_manager;
  }

  /**
   * {@inheritdoc}
   *
   * @see \_user_cancel()
   */
  public function purgeUser(UserInterface $user, string $method) {
    $logger = $this->loggerFactory->get('purge_users');
    $edit = [];
    // When the 'user_cancel_delete' method is used, user_delete() is called,
    // which invokes hook_ENTITY_TYPE_predelete() and hook_ENTITY_TYPE_delete()
    // for the user entity. Modules should use those hooks to respond to the
    // account deletion.
    if ($method != 'user_cancel_delete') {
      // Allow modules to add further sets to this batch.
      /* @see \user_cancel() */
      $this->moduleHandler->invokeAll('user_cancel', [$edit, $user, $method]);
    }

    switch ($method) {
      case 'user_cancel_reassign':
        // Reassign content to anonymous:
        $this->reassignContentOwnershipToAnonymous($user);
        // Notify and delete the user:
        $this->notifyUserToPurge($user);
        $user->delete();
        $this->messenger->addStatus($this->t('%name has been deleted.', ['%name' => $user->getDisplayName()]));
        $logger->notice('Deleted user: %name %email.', [
          '%name' => $user->getAccountName(),
          '%email' => '<' . $user->getEmail() . '>',
        ]);
        break;

      case 'user_cancel_delete':
        // Notify and delete the user:
        $this->notifyUserToPurge($user);
        $user->delete();
        $this->messenger->addStatus($this->t('%name has been deleted.', ['%name' => $user->getDisplayName()]));
        $logger->notice('Deleted user: %name %email.', [
          '%name' => $user->getAccountName(),
          '%email' => '<' . $user->getEmail() . '>',
        ]);
        break;

      case 'user_cancel_block':
      case 'user_cancel_block_unpublish':
        if ($user->isBlocked()) {
          // The user is already blocked. Do not block them again.
          return;
        }
        $this->notifyUserToPurge($user);
        $user->block();
        $user->save();
        $this->messenger->addStatus($this->t('%name has been disabled.', ['%name' => $user->getDisplayName()]));
        $logger->notice('Blocked user: %name %email.', [
          '%name' => $user->getAccountName(),
          '%email' => '<' . $user->getEmail() . '>',
        ]);
        break;

      default:
        $logger->notice('Unknown user cancel method "%method".', [
          '%method' => $method,
        ]);
        break;
    }

    // After cancelling account, ensure that user is logged out. We can't
    // destroy their session though, as we might have information in it, and we
    // can't regenerate it because batch API uses the session ID, we will
    // regenerate it in _user_cancel_session_regenerate().
    if ($user->id() == $this->currentUser->id()) {
      $this->currentUser->setAccount(new AnonymousUserSession());
    }
  }

  /**
   * Sends a notification to a user who is going to be purged now.
   *
   * @param \Drupal\user\UserInterface $user
   *   The user to be purged.
   */
  protected function notifyUserToPurge(UserInterface $user): void {
    // Send a notification email.
    $config = $this->config->get('purge_users.settings');
    if (!$config->get('send_email_notification')) {
      return;
    }
    if ($this->userIsNotified($user->id(), 'purge_users')) {
      // The user was already notified in the past. Don't do it again.
      // @todo Review if this is the intended behavior.
      return;
    }
    if (purge_users_send_notification_email($user)) {
      $this->flagUserAsNotified($user->id(), 'purge_users');
    }
  }

  /**
   * {@inheritdoc}
   */
  public function notifyUser(UserInterface $user): void {
    if ($this->userIsNotified($user->id(), 'notification_users')) {
      // The user was already notified in the past. Don't do it again.
      return;
    }
    $notifier = $this->loggerFactory->get('notification_users');

    if (purge_users_send_notification_email($user, 'notification_users')) {
      $this->flagUserAsNotified($user->id(), 'notification_users');
      $this->messenger->addStatus($this->t('%name has been notified.', ['%name' => $user->getDisplayName()]));
      $notifier->notice('Notified user: %name %email.', [
        '%name' => $user->getAccountName(),
        '%email' => '<' . $user->getEmail() . '>',
      ]);
    }
    else {
      $this->messenger->addStatus($this->t('User %name could not be notified. Check the logs.', ['%name' => $user->getDisplayName()]));
    }

  }

  /**
   * {@inheritdoc}
   */
  public function userIsNotified(string $user_id, string $type): bool {
    $notifier = $this->loggerFactory->get('notification_users');
    $result = $this->connection->select('purge_users_notifications', 'n')
      ->fields('n', ['id'])
      ->condition('uid', $user_id)
      ->condition('type', $type)
      ->execute()
      ->fetch();
    if (!empty($result)) {
      $notifier->notice('User id %uid notification skipped.', [
        '%uid' => $user_id,
      ]);
      return TRUE;
    }
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function flagUserAsNotified(string $user_id, string $type): void {
    $this->connection
      ->insert('purge_users_notifications')
      ->fields([
        'uid' => $user_id,
        'type' => $type,
        'timestamp' => time(),
      ])
      ->execute();
  }

  /**
   * {@inheritdoc}
   */
  public function removeNotificationFlags(string $user_id): void {
    $this->connection->delete('purge_users_notifications')
      ->condition('uid', $user_id)
      ->execute();
  }

  /**
   * Reassigns content ownership from a user to "anonymous".
   *
   * This is currently based on custom logic, scanning the database
   * tables (from ::getTablesWithUidColumn()) for the "uid" column and
   * updating the value from the user's UId to "0" (anonymous).
   *
   * This logic is a bit risky, for example in cases where the uid column
   * - is not used for the user id (= overwriting unrelated values)
   * - is not called "uid" (=missing related values)
   * or
   * but the risks are mitigated by limiting this to
   * ContentEntityTypeInterface storage tables.
   *
   * @param \Drupal\user\UserInterface $user
   *   The user to reassign the content from.
   */
  private function reassignContentOwnershipToAnonymous(UserInterface $user) {
    $tables = $this->getTablesWithUidColumn();
    if (empty($tables)) {
      return;
    }

    // We don't care about the user entity:
    unset($tables['user']);

    // Init db object:
    $database = $this->connection;
    foreach ($tables as $table) {
      if (isset($table['uid']) && !empty($table['uid'])) {
        foreach ($table['uid'] as $table_name) {
          $database->update($table_name)
            ->fields(['uid' => 0])
            ->condition('uid', $user->id(), '=')
            ->execute();
        }
      }
    }
  }

  /**
   * Retrieve all content entity tables which have a 'uid' column.
   *
   * @return array
   *   The table names.
   */
  private function getTablesWithUidColumn() {

    $entity_type_manager = $this->entityTypeManager;

    $tables = [];
    foreach ($entity_type_manager->getDefinitions() as $entity_type) {
      // Only list content entity types using SQL storage:
      if ($entity_type instanceof ContentEntityTypeInterface && in_array(SqlEntityStorageInterface::class, class_implements($entity_type->getStorageClass()))) {
        $storage = $entity_type_manager->getStorage($entity_type->id());
        $tables[$entity_type->id()]['uid'] = $storage->getTableMapping()->getAllFieldTableNames('uid');
      }
    }
    return $tables;
  }

}

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

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