You are here

AgreementHandler.php in Agreement 3.0.x

Same filename and directory in other branches
  1. 8.2 src/AgreementHandler.php

Namespace

Drupal\agreement

File

src/AgreementHandler.php
View source
<?php

namespace Drupal\agreement;

use Drupal\agreement\Entity\Agreement;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Database\DatabaseExceptionWrapper;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Path\PathMatcherInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\user\RoleInterface;
use Drupal\user\UserInterface;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\RequestStack;

/**
 * Agreement handler provides methods for looking up agreements.
 */
class AgreementHandler implements AgreementHandlerInterface {

  /**
   * Prefix to use for cookie names for anonymous agreements.
   */
  const ANON_AGREEMENT_COOKIE_PREFIX = 'agreement_anon_';

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

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

  /**
   * Path matcher.
   *
   * @var \Drupal\Core\Path\PathMatcherInterface
   */
  protected $pathMatcher;

  /**
   * The datetime.time service.
   *
   * @var \Drupal\Component\Datetime\TimeInterface
   */
  protected $time;

  /**
   * The request stack.
   *
   * @var \Symfony\Component\HttpFoundation\RequestStack
   */
  protected $requestStack;

  /**
   * Initialize method.
   *
   * @param \Drupal\Core\Database\Connection $connection
   *   The database connection.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   The entity type manager.
   * @param \Drupal\Core\Path\PathMatcherInterface $pathMatcher
   *   The path matcher service.
   * @param \Drupal\Component\Datetime\TimeInterface $time
   *   The datetime.time service.
   * @param \Symfony\Component\HttpFoundation\RequestStack $requestStack
   *   The request stack.
   */
  public function __construct(Connection $connection, EntityTypeManagerInterface $entityTypeManager, PathMatcherInterface $pathMatcher, TimeInterface $time, RequestStack $requestStack) {
    $this->connection = $connection;
    $this->entityTypeManager = $entityTypeManager;
    $this->pathMatcher = $pathMatcher;
    $this->time = $time;
    $this->requestStack = $requestStack;
  }

  /**
   * {@inheritdoc}
   */
  public function agree(Agreement $agreement, AccountProxyInterface $account, $agreed = 1) {
    if ($this
      ->isAnonymousAgreement($agreement, $account)) {
      return $this
        ->agreeAnonymously($account, $agreement, $agreed);
    }
    return $this
      ->agreeWhileLoggedIn($account, $agreement, $agreed);
  }

  /**
   * {@inheritdoc}
   */
  public function hasAgreed(Agreement $agreement, AccountProxyInterface $account) {
    if ($this
      ->isAnonymousAgreement($agreement, $account)) {
      return $this
        ->hasAnonymousUserAgreed($agreement);
    }
    return $this
      ->hasAuthenticatedUserAgreed($agreement, $account);
  }

  /**
   * {@inheritdoc}
   */
  public function lastAgreed(Agreement $agreement, UserInterface $account) {
    $query = $this->connection
      ->select('agreement');
    $query
      ->fields('agreement', [
      'agreed_date',
    ])
      ->condition('uid', $account
      ->id())
      ->condition('type', $agreement
      ->id())
      ->range(0, 1);
    $agreed_date = $query
      ->execute()
      ->fetchField();
    return $agreed_date === FALSE || $agreed_date === NULL ? -1 : $agreed_date;
  }

  /**
   * {@inheritdoc}
   */
  public function canAgree(Agreement $agreement, AccountProxyInterface $account) {
    return !$account
      ->hasPermission('bypass agreement') && $agreement
      ->accountHasAgreementRole($account);
  }

  /**
   * {@inheritdoc}
   */
  public function isAnonymousAgreement(Agreement $agreement, AccountProxyInterface $account) {
    if ($account
      ->isAnonymous() && in_array(RoleInterface::ANONYMOUS_ID, $agreement
      ->getSettings()['roles'])) {
      return TRUE;
    }
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function getAgreementByUserAndPath(AccountProxyInterface $account, $path) {
    $agreement_types = $this->entityTypeManager
      ->getStorage('agreement')
      ->loadMultiple();
    $default_exceptions = [
      '/user/password',
      '/user/register',
      '/user/reset/*',
      '/user/login',
      '/user/logout',
      '/admin/config/people/agreement',
      '/admin/config/people/agreement/*',
      '/admin/config/people/agreement/manage/*',
    ];

    // Get a list of pages to never display agreements on.
    $exceptions = array_reduce($agreement_types, function (&$result, Agreement $item) {
      $result[] = $item
        ->get('path');
      return $result;
    }, $default_exceptions);
    $exception_string = implode("\n", $exceptions);
    if ($this->pathMatcher
      ->matchPath($path, $exception_string)) {
      return FALSE;
    }

    // Reduce the agreement types based on the user role.
    $agreements_with_roles = array_reduce($agreement_types, function (&$result, Agreement $item) use ($account) {
      if ($item
        ->accountHasAgreementRole($account)) {
        $result[] = $item;
      }
      return $result;
    }, []);

    // Try to find an agreement type that matches the path.
    $pathMatcher = $this->pathMatcher;
    $self = $this;
    $info = array_reduce($agreements_with_roles, function (&$result, Agreement $item) use ($account, $path, $pathMatcher, $self) {
      if ($result) {

        // Always returns the first matched agreement.
        return $result;
      }
      $pattern = $item
        ->getVisibilityPages();
      $has_match = $pathMatcher
        ->matchPath($path, $pattern);
      $has_agreed = $self
        ->hasAgreed($item, $account);
      $visibility = (int) $item
        ->getVisibilitySetting();
      if (0 === $visibility && FALSE === $has_match && !$has_agreed) {

        // An agreement exists that matches any page.
        $result = $item;
      }
      elseif (1 === $visibility && $has_match && !$has_agreed) {

        // An agreement exists that matches the current path.
        $result = $item;
      }
      return $result;
    }, FALSE);
    return $info;
  }

  /**
   * {@inheritdoc}
   */
  public static function prefixPath($value) {
    return $value ? '/' . $value : $value;
  }

  /**
   * Accept agreement for an anonymous user.
   *
   * @param \Drupal\Core\Session\AccountProxyInterface $account
   *   The user account to agree.
   * @param \Drupal\agreement\Entity\Agreement $agreement
   *   The agreement that the user is agreeing to.
   * @param int $agreed
   *   An optional integer to set the agreement status to. Defaults to 1.
   *
   * @return \Symfony\Component\HttpFoundation\Cookie
   *   A cookie to retain the user's acceptance of the agreement.
   */
  protected function agreeAnonymously(AccountProxyInterface $account, Agreement $agreement, $agreed) {
    $agreementType = $agreement
      ->id();
    $cookieName = static::ANON_AGREEMENT_COOKIE_PREFIX . $agreementType;
    $expire = 0;
    if ($agreement
      ->getSettings()['frequency'] == 365) {
      $expire = new \DateTime('+1 year');
    }
    elseif ($agreement
      ->agreeOnce()) {
      $expire = new \DateTime('+10 years');
    }
    return new Cookie($cookieName, $agreed, $expire, '/', NULL, NULL, 'lax');
  }

  /**
   * Accept agreement for an authenticated user.
   *
   * @param \Drupal\Core\Session\AccountProxyInterface $account
   *   The user account that is agreeing.
   * @param \Drupal\agreement\Entity\Agreement $agreement
   *   The agreement that the user is agreeing to.
   * @param int $agreed
   *   An optional integer to set the agreement status to. Defaults to 1.
   *
   * @return bool
   *   TRUE if the operation was successful. Otherwise FALSE.
   */
  protected function agreeWhileLoggedIn(AccountProxyInterface $account, Agreement $agreement, $agreed) {
    try {
      $transaction = $this->connection
        ->startTransaction();
      $this->connection
        ->delete('agreement')
        ->condition('uid', $account
        ->id())
        ->condition('type', $agreement
        ->id())
        ->execute();
      $id = $this->connection
        ->insert('agreement')
        ->fields([
        'uid' => $account
          ->id(),
        'type' => $agreement
          ->id(),
        'agreed' => $agreed,
        'sid' => session_id(),
        'agreed_date' => $this->time
          ->getRequestTime(),
      ])
        ->execute();
    } catch (DatabaseExceptionWrapper $e) {
      $transaction
        ->rollback();
      return FALSE;
    } catch (\Exception $e) {
      $transaction
        ->rollback();
      return FALSE;
    }
    return isset($id);
  }

  /**
   * Check the status of the anonymous user for a particular agreement.
   *
   * @param \Drupal\agreement\Entity\Agreement $agreement
   *   The agreement to check if a user has agreed.
   *
   * @return bool
   *   TRUE if the user account has agreed to this agreement.
   */
  protected function hasAnonymousUserAgreed(Agreement $agreement) {
    $agreementType = $agreement
      ->id();
    return $this->requestStack
      ->getCurrentRequest()->cookies
      ->has(static::ANON_AGREEMENT_COOKIE_PREFIX . $agreementType);
  }

  /**
   * Check the status of a user account for a particular agreement.
   *
   * @param \Drupal\agreement\Entity\Agreement $agreement
   *   The agreement to check if a user has agreed.
   * @param \Drupal\Core\Session\AccountProxyInterface $account
   *   The user account to check.
   *
   * @return bool
   *   TRUE if the user account has agreed to this agreement.
   */
  protected function hasAuthenticatedUserAgreed(Agreement $agreement, AccountProxyInterface $account) {
    $settings = $agreement
      ->getSettings();
    $frequency = $settings['frequency'];
    $query = $this->connection
      ->select('agreement');
    $query
      ->fields('agreement', [
      'agreed',
    ])
      ->condition('uid', $account
      ->id())
      ->condition('type', $agreement
      ->id())
      ->range(0, 1);
    if ($frequency == 0) {

      // Must agree on every login.
      $query
        ->condition('sid', session_id());
    }
    else {

      // Must agree when frequency is set greater than zero (number of days).
      $timestamp = $agreement
        ->getAgreementFrequencyTimestamp();
      if ($timestamp > 0) {
        $query
          ->condition('agreed_date', $agreement
          ->getAgreementFrequencyTimestamp(), '>=');
      }
    }
    $agreed = $query
      ->execute()
      ->fetchField();
    return $agreed !== NULL && $agreed > 0;
  }

}

Classes

Namesort descending Description
AgreementHandler Agreement handler provides methods for looking up agreements.