entity_agree-2.0.x-dev/src/AgreementManager.php

src/AgreementManager.php
<?php

namespace Drupal\entity_agree;

use Drupal\Core\Cache\Cache;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Entity\RevisionableInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\entity_agree\Plugin\Field\FieldType\AgreementItem;
use Kerasai\DrupalCacheHelper\CacheHelperTrait;
use Symfony\Component\HttpFoundation\RequestStack;

/**
 * Manage agreements.
 */
class AgreementManager {

  use CacheHelperTrait;

  /**
   * The current request.
   *
   * @var \Symfony\Component\HttpFoundation\Request
   */
  protected $currentRequest;

  /**
   * The current user.
   *
   * @var \Drupal\Core\Session\AccountInterface
   */
  protected $currentUser;

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

  /**
   * Constructs an AgreementManager.
   *
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
   *   The entity type manager service.
   * @param \Drupal\Core\Session\AccountInterface $currentUser
   *   The current user.
   * @param \Symfony\Component\HttpFoundation\RequestStack $requestStack
   *   The request stack service.
   */
  public function __construct(EntityTypeManagerInterface $entityTypeManager, AccountInterface $currentUser, RequestStack $requestStack) {
    $this->entityTypeManager = $entityTypeManager;
    $this->currentUser = $currentUser;
    $this->currentRequest = $requestStack->getCurrentRequest();
  }

  /**
   * Agree to an agreement.
   *
   * @param \Drupal\Core\Entity\RevisionableInterface $entity
   *   The entity being agreed to.
   * @param \Drupal\Core\Session\AccountInterface|null $account
   *   The user who is agreeing. Optional, defaults to the current user.
   *
   * @return \Drupal\entity_agree\AgreementInterface
   *   The agreement.
   */
  public function agree(RevisionableInterface $entity, AccountInterface $account = NULL) {
    if (!$account) {
      $account = $this->currentUser;
    }

    $agreement = $this->entityTypeManager->getStorage('entity_agree_agreement')
      ->create([
        'uid' => $account->id(),
        'entity_type_id' => $entity->getEntityTypeId(),
        'entity_id' => $entity->id(),
        'entity_vid' => $entity->getRevisionId(),
        'ip_address' => $this->currentRequest->getClientIp(),
      ]);
    $agreement->save();

    return $agreement;
  }

  /**
   * Determines agreements that user needs to agree-to.
   *
   * Note, this does not take account applies handlers. Use
   * `::getOutstandingAgreementEntities` to load full entities accounting for
   * applies handling.
   *
   * @param \Drupal\Core\Session\AccountInterface|null $account
   *   The account being analyzed. Optional, defaults to the current user.
   *
   * @return array
   *   Multidimensional array containing agreements the user needs to agree to.
   *   Outer keys are entity type IDs, and values are arrays of current
   *   revision IDs keyed by IDs.
   */
  public function getOutstandingAgreements(AccountInterface $account = NULL) {
    if (!$account) {
      $account = $this->currentUser;
    }

    // No support for anonymous users.
    if ($account->isAnonymous()) {
      return [];
    }

    if ($account->hasPermission('bypass entity agreements')) {
      return [];
    }

    $user_agreements = $this->getUserAgreements($account);

    $data = [];

    foreach ($this->getAgreementRevisions() as $entity_type => $agreements) {
      $data[$entity_type] = array_diff($agreements, $user_agreements[$entity_type] ?? []);
    }

    return array_filter($data);
  }

  /**
   * Gets all entities that the user needs to agree-to.
   *
   * @param \Drupal\Core\Session\AccountInterface|null $account
   *   The account being analyzed. Optional, defaults to the current user.
   * @param string|null $op
   *   Entity operation to check for access. Optional, defauls to "view". To
   *   bypass access check, use NULL/FALSE.
   *
   * @return array
   *   Multidimensional array containing agreements the user needs to agree to.
   *   Outer keys are entity type IDs, and values fully-loaded entities keyed
   *   keyed by IDs.
   */
  public function getOutstandingAgreementEntities(AccountInterface $account = NULL, $op = 'view') {
    if (!$account) {
      $account = $this->currentUser;
    }

    // Load entity IDs from cache if possible. These are cached after access
    // has been checked and applies handlers have ran, so we can just load and
    // return the entities.
    $cid = __METHOD__ . ($op ? ":{$op}" : '') . ":user:{$account->id()}";
    if ($data = $this->getCacheHelper()->get($cid, 'entity_agree')) {
      return $this->loadEntities($data->data);
    }

    $data = [];
    $cache = new CacheableMetadata();

    foreach ($this->loadEntities($this->getOutstandingAgreements()) as $entity_type => $agreements) {
      // Grab caching info from agreements.
      foreach ($agreements as $agreement) {
        $cache->addCacheableDependency($agreement);
      }

      // Ensure agreements are applicable.
      $data[$entity_type] = array_filter($agreements, [
        $this,
        'agreementApplies',
      ]);

      // Check access to the entities for the specified operation, unless $op
      // is NULL/FALSE.
      if ($op) {
        $data[$entity_type] = array_filter($data[$entity_type], function ($entity) use ($op, $account) {
          return $entity->access($op, $account);
        });
      }
    }

    // Pull the IDs from the outstanding entities and cache.
    $cache->addCacheTags(['entity_agree', "entity_agree:user:{$account->id()}"]);
    $this->getCacheHelper()->set($cid, $this->getEntityIds($data), 'entity_agree', Cache::PERMANENT, $cache->getCacheTags());

    return array_filter($data);
  }

  /**
   * Gets agreement information for a user.
   *
   * @return array
   *   The user's current agreements.
   */
  public function getUserAgreements(AccountInterface $account) {
    $cid = __METHOD__ . ":user:{$account->id()}";

    if ($data = $this->getCacheHelper()->get($cid, 'entity_agree')) {
      return $data->data;
    }

    $data = [];

    // Query out all the user's agreements, collate by entity type and entity
    // ID.
    // @todo Convert this to SQL query to allow grouping and determine latest
    //   (MAX) revision ID?
    /** @var \Drupal\entity_agree\AgreementInterface[] $agreements */
    $agreements = $this->entityTypeManager->getStorage('entity_agree_agreement')
      ->loadByProperties([
        'uid' => $account->id(),
      ]);
    foreach ($agreements as $agreement) {
      $data[$agreement->getTargetEntityTypeId()][$agreement->getTargetEntityId()][] = $agreement->getTargetEntityRevisionId();
    }

    // Determine the latest/highest revision ID for each agreement.
    foreach ($data as $entity_type => $entities) {
      foreach ($entities as $entity_id => $revisions) {
        $data[$entity_type][$entity_id] = max($data[$entity_type][$entity_id]);
      }
    }

    $tags = ['entity_agree', "entity_agree:user:{$account->id()}"];
    $this->getCacheHelper()->set($cid, $data, 'entity_agree', Cache::PERMANENT, $tags);
    return $data;
  }

  /**
   * Get the current revisions for each agreement.
   *
   * @return array
   *   And array of entity revision IDs for agreements. Multidimensional array
   *   keyed by entity type and entity ID.
   */
  public function getAgreementRevisions() {
    if ($data = $this->getCacheHelper()->get(__METHOD__)) {
      return $data->data;
    }

    $data = [];
    foreach ($this->getAgreementFields() as $entity_type => $fields) {
      $query = $this->entityTypeManager->getStorage($entity_type)
        ->getQuery('OR');
      foreach ($fields as $field) {
        $query->condition($field, AgreementItem::ENTITY_AGREE_STATUS_ACTIVE);
      }
      $data[$entity_type] = array_flip($query->accessCheck(FALSE)->execute());
    }

    $this->getCacheHelper()->set(__METHOD__, $data, 'default', Cache::PERMANENT, ['entity_agree']);

    return $data;
  }

  /**
   * Gets information about entity types with agreement fields.
   *
   * @return array
   *   An array of agreement fields per entity type. Multidimensional array
   *   keyed by entity type.
   */
  public function getAgreementFields() {
    if ($data = $this->getCacheHelper()->get(__METHOD__)) {
      return $data->data;
    }

    $data = [];

    /** @var \Drupal\field\FieldStorageConfigInterface[] $fields */
    $fields = $this->entityTypeManager->getStorage('field_storage_config')
      ->loadByProperties(['type' => 'entity_agree']);

    foreach ($fields as $field) {
      $data[$field->getTargetEntityTypeId()][] = $field->getName();
    }

    $this->getCacheHelper()->set(__METHOD__, $data, 'default', Cache::PERMANENT, ['entity_field_info']);

    return $data;
  }

  /**
   * Determines if an entity has an agreement field.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The entity.
   *
   * @return bool
   *   Indicates if an entity has an agreement field.
   */
  public static function entityHasAgreementField(EntityInterface $entity) {
    if (!$entity instanceof FieldableEntityInterface) {
      return FALSE;
    }

    foreach ($entity->getFieldDefinitions() as $field) {
      if ($field->getType() == 'entity_agree') {
        return TRUE;
      }
    }

    return FALSE;
  }

  /**
   * Determines if an entity applies as an agreement.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The entity.
   *
   * @return bool
   *   Indicates if an entity applies as an agreement.
   */
  public function agreementApplies(EntityInterface $entity) {
    $result = TRUE;

    $fields = AgreementItem::getEntityAgreementPlugins($entity);
    foreach ($fields as $plugins) {
      foreach ($plugins as $plugin) {
        if (!$plugin instanceof AgreementHandlerAppliesInterface) {
          continue;
        }
        if ($plugin->applies()) {
          return TRUE;
        }
        else {
          $result = FALSE;
        }
      }
    }

    // Either no applies plugins, or there were applies plugins but none
    // returned TRUE.
    return $result;
  }

  /**
   * Gets the user's current agreement for the specified entity.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The entity.
   * @param \Drupal\Core\Session\AccountInterface|null $account
   *   The account being analyzed. Optional, defaults to the current user.
   *
   * @return \Drupal\entity_agree\AgreementInterface|null
   *   The user's current agreement for the specified entity, or NULL if not
   *   available.
   */
  public function userCurrentAgreement(EntityInterface $entity, AccountInterface $account = NULL) {
    if (!$entity instanceof RevisionableInterface) {
      return NULL;
    }

    if (!$account) {
      $account = $this->currentUser;
    }

    $agreement_ids = $this->entityTypeManager->getStorage('entity_agree_agreement')
      ->getQuery()
      ->condition('uid', $account->id())
      ->condition('entity_type_id', $entity->getEntityTypeId())
      ->condition('entity_id', $entity->id())
      ->condition('entity_vid', $entity->getRevisionId())
      ->execute();

    if (!$agreement_id = reset($agreement_ids)) {
      return NULL;
    }

    return $this->entityTypeManager->getStorage('entity_agree_agreement')
      ->load($agreement_id);
  }

  /**
   * Determines if the user needs to agree to the specified entity.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The entity.
   * @param \Drupal\Core\Session\AccountInterface|null $account
   *   The account being analyzed. Optional, defaults to the current user.
   *
   * @return bool
   *   Indicates if the user needs to agree to the entity.
   */
  public function userNeedsAgreement(EntityInterface $entity, AccountInterface $account = NULL) {
    if (!$account) {
      $account = $this->currentUser;
    }
    return !empty($this->getOutstandingAgreements($account)[$entity->getEntityTypeId()][$entity->id()]);
  }

  /**
   * Loads entity data.
   *
   * @param array $data
   *   A multidimensional array. Outer keys are entity type IDs, values are
   *   arrays of revision IDs, keyed by entity ID.
   *
   * @return array
   *   A multidimensional array of entities. Outer keys are entity type IDs,
   *   values are arrays of entities, keyed by entity ID.
   */
  public function loadEntities(array $data) {
    $result = [];

    foreach ($data as $entity_type => $vids) {
      $result[$entity_type] = $this->entityTypeManager->getStorage($entity_type)
        ->loadMultiple(array_keys($vids));
    }

    return array_filter($result);
  }

  /**
   * Obtains entity ID and revision info from a set of entities.
   *
   * @param array $data
   *   A multidimensional array of entities. Outer keys are entity type IDs,
   *   values are arrays of entities, keyed by entity ID.
   *
   * @return array
   *   A multidimensional array. Outer keys are entity type IDs, values are
   *   arrays of revision IDs, keyed by entity ID.
   */
  public function getEntityIds(array $data) {
    $result = [];

    /** @var \Drupal\Core\Entity\EntityInterface[] $entities */
    foreach ($data as $entity_type => $entities) {
      foreach ($entities as $entity) {
        if (!$entity instanceof RevisionableInterface) {
          continue;
        }
        $result[$entity_type][$entity->id()] = $entity->getRevisionId();
      }
    }

    return $result;
  }

}

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

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