You are here

Anonymizer.php in General Data Protection Regulation 3.0.x

Namespace

Drupal\gdpr_tasks

File

modules/gdpr_tasks/src/Anonymizer.php
View source
<?php

namespace Drupal\gdpr_tasks;

use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\File\FileSystemInterface;
use Drupal\Core\Link;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Url;
use Drupal\gdpr_fields\EntityTraversalFactory;
use Drupal\gdpr_tasks\Entity\TaskInterface;
use Drupal\gdpr_tasks\Form\RemovalSettingsForm;
use function date;
use function file_put_contents;
use function json_encode;

/**
 * Anonymizes or removes field values for GDPR.
 */
class Anonymizer {
  use StringTranslationTrait;

  /**
   * Database instance for the request.
   *
   * @var \Drupal\Core\Database\Connection
   */
  private $database;

  /**
   * Entity Type manager used to retrieve field storage info.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  private $entityTypeManager;

  /**
   * The current user.
   *
   * @var \Drupal\Core\Session\AccountProxyInterface
   */
  private $currentUser;

  /**
   * Config factory.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  private $configFactory;

  /**
   * Traverses the entity hierarchy finding GDPR fields.
   *
   * @var \Drupal\gdpr_tasks\Traversal\RightToBeForgottenEntityTraversal
   */
  private $traversalFactory;

  /**
   * Filesystem.
   *
   * @var \Drupal\Core\File\FileSystemInterface
   */
  private $fileSystem;

  /**
   * Anonymizer constructor.
   *
   * @param \Drupal\Core\Database\Connection $database
   *   The database connection.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   The entity type manager.
   * @param \Drupal\Core\Session\AccountProxyInterface $currentUser
   *   The current user.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
   *   The config factory.
   * @param \Drupal\gdpr_fields\EntityTraversalFactory $traversalFactory
   *   Instantiates a traverser class.
   * @param \Drupal\Core\File\FileSystemInterface $fileSystem
   *   Filesystem.
   */
  public function __construct(Connection $database, EntityTypeManagerInterface $entityTypeManager, AccountProxyInterface $currentUser, ConfigFactoryInterface $configFactory, EntityTraversalFactory $traversalFactory, FileSystemInterface $fileSystem) {
    $this->database = $database;
    $this->entityTypeManager = $entityTypeManager;
    $this->currentUser = $currentUser;
    $this->configFactory = $configFactory;
    $this->traversalFactory = $traversalFactory;
    $this->fileSystem = $fileSystem;
  }

  /**
   * Runs anonymization routines against a user.
   *
   * @param \Drupal\gdpr_tasks\Entity\TaskInterface $task
   *   The current task being executed.
   *
   * @return array
   *   Returns array containing any error messages.
   *
   * @throws \Drupal\Core\TypedData\Exception\ReadOnlyException
   */
  public function run(TaskInterface $task) {

    // Make sure we load a fresh copy of the entity (bypassing the cache)
    // so we don't end up affecting any other references to the entity.
    $user = $task
      ->getOwner();
    $errors = [];
    if (!$this
      ->checkExportDirectoryExists()) {
      $errors[] = $this
        ->t('An export directory has not been set. Please set this %link.', [
        '%link' => Link::fromTextAndUrl('here', Url::fromRoute('gdpr_tasks.remove_settings'))
          ->toString(),
      ]);
      return $errors;
    }

    // Traverser does the actual anonymizing.
    $traverser = $this->traversalFactory
      ->getTraversal($user);
    $result = $traverser
      ->getResults();
    $log = $result['log'];
    $errors = $result['errors'];
    $successes = $result['successes'];
    $failures = $result['failures'];
    $deletions = $result['to_delete'];
    $task
      ->get('removal_log')
      ->setValue($log);
    if (\count($failures) === 0) {
      $transaction = $this->database
        ->startTransaction();
      try {

        /** @var \Drupal\Core\Entity\EntityInterface $entity */
        foreach ($successes as $entity) {
          $entity
            ->save();
        }
        foreach ($deletions as $entity) {
          $entity
            ->delete();
        }

        // Re-fetch the user so we see any changes that were made.
        $user = $this
          ->refetchUser($task
          ->getOwnerId());
        $user
          ->block();
        $user
          ->save();
        $this
          ->writeLogToFile($task, $log);
      } catch (\Exception $e) {
        $transaction
          ->rollBack();
        $errors[] = $e
          ->getMessage();
      }
    }
    return $errors;
  }

  /**
   * Re-fetches the user bypassing the cache.
   *
   * @param string $user_id
   *   The ID of the user to fetch.
   *
   * @return \Drupal\user\UserInterface
   *   The user that was fetched.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  private function refetchUser($user_id) {
    return $this->entityTypeManager
      ->getStorage('user')
      ->loadUnchanged($user_id);
  }

  /**
   * Checks that the export directory has been set.
   *
   * @return bool
   *   Indicates whether the export directory has been configured and exists.
   */
  private function checkExportDirectoryExists() {
    $directory = $this->configFactory
      ->get(RemovalSettingsForm::CONFIG_KEY)
      ->get(RemovalSettingsForm::EXPORT_DIRECTORY);
    return !empty($directory) && $this->fileSystem
      ->prepareDirectory($directory);
  }

  /**
   * Stores the task log to the configured directory as JSON.
   *
   * @param \Drupal\gdpr_tasks\Entity\TaskInterface $task
   *   The task in progress.
   * @param array $log
   *   Log of processed fields.
   */
  private function writeLogToFile(TaskInterface $task, array $log) {
    $filename = 'GDPR_RTF_' . date('Y-m-d H-i-s') . '_' . $task
      ->uuid() . '.json';
    $dir = $this->configFactory
      ->get(RemovalSettingsForm::CONFIG_KEY)
      ->get(RemovalSettingsForm::EXPORT_DIRECTORY);
    $filename = $dir . '/' . $filename;

    // Don't serialize the whole entity as we don't need all fields.
    $output = [
      'task_id' => $task
        ->id(),
      'task_uuid' => $task
        ->uuid(),
      'owner_id' => $task
        ->getOwnerId(),
      'created' => $task
        ->getCreatedTime(),
      'processed_by' => $this->currentUser
        ->id(),
      'log' => $log,
    ];
    file_put_contents($filename, json_encode($output));
  }

}

Classes

Namesort descending Description
Anonymizer Anonymizes or removes field values for GDPR.