You are here

class AccessChecker in Permissions by Term 8.2

Same name and namespace in other branches
  1. 8 modules/permissions_by_entity/src/Service/AccessChecker.php \Drupal\permissions_by_entity\Service\AccessChecker

Class AccessChecker.

@package Drupal\permissions_by_entity\Service

Hierarchy

Expanded class hierarchy of AccessChecker

1 string reference to 'AccessChecker'
permissions_by_entity.services.yml in modules/permissions_by_entity/permissions_by_entity.services.yml
modules/permissions_by_entity/permissions_by_entity.services.yml
1 service uses AccessChecker
permissions_by_entity.access_checker in modules/permissions_by_entity/permissions_by_entity.services.yml
Drupal\permissions_by_entity\Service\AccessChecker

File

modules/permissions_by_entity/src/Service/AccessChecker.php, line 19

Namespace

Drupal\permissions_by_entity\Service
View source
class AccessChecker extends AccessCheck implements AccessCheckerInterface {

  /**
   * The event dispatcher.
   *
   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
   */
  private $eventDispatcher;

  /**
   * The cache for checked entities.
   *
   * @var \Drupal\permissions_by_entity\Service\CheckedEntityCache
   */
  private $checkedEntityCache;

  /**
   * The entity field value access denied event.
   *
   * @var \Drupal\permissions_by_entity\Event\EntityFieldValueAccessDeniedEvent
   */
  private $event;
  public function __construct(EventDispatcherInterface $event_dispatcher, CheckedEntityCache $checked_entity_cache, EntityFieldManagerInterface $entityFieldManager, Connection $database) {
    parent::__construct($database, $event_dispatcher, $entityFieldManager);
    $this->eventDispatcher = $event_dispatcher;
    $this->checkedEntityCache = $checked_entity_cache;
    $this->event = new EntityFieldValueAccessDeniedEvent();
  }

  /**
   * {@inheritdoc}
   */
  public function isAccessAllowed(FieldableEntityInterface $entity, $uid = FALSE) : bool {

    // Iterate over the fields the entity contains.
    foreach ($entity
      ->getFields() as $field) {

      // We only need to check for entity reference fields
      // which references to a taxonomy term.
      if ($field
        ->getFieldDefinition()
        ->getType() == 'entity_reference' && $field
        ->getFieldDefinition()
        ->getSetting('target_type') == 'taxonomy_term') {

        // Iterate over each referenced taxonomy term.

        /** @var \Drupal\Core\Field\FieldItemInterface $item */
        foreach ($field
          ->getValue() as $item) {

          // Let "Permissions By Term" do the actual check.
          if (!empty($item['target_id']) && !$this
            ->isAccessAllowedByDatabase($item['target_id'], $uid, $entity
            ->language()
            ->getId())) {

            // Return that the user is not allowed to access this entity.
            return FALSE;
          }
        }
      }

      // Check if the field contains another fieldable entity,
      // that we need to check.
      if ($field->entity && $field->entity instanceof FieldableEntityInterface) {

        // We need to iterate over the entities.
        $num_values = $field
          ->count();
        if ($num_values > 0) {

          // Iterate over the field values.
          for ($i = 0; $i < $num_values; $i++) {

            // Get the value of the current field index.
            $field_value = $field
              ->get($i);

            // If the value is null or empty we continue with the next index of
            // the loop.
            if (!$field_value) {
              continue;
            }

            // Get the field entity.
            $field_entity = $field_value->entity;

            // If the field entity is null we also continue with the next index
            // of the loop.
            if (!$field_entity) {
              continue;
            }

            // It is possible, that the referenced field entity creates a
            // circular dependency to the current entity. This will cause
            // memory limit exhausted errors because there is no way out for
            // the script. To avoid this, we need to be able to imagine if we
            // already checked this field entity before. If so, we ignore this
            // field entity, if not we can securely do a recursive call.
            //
            // Using own method to avoid "max nesting level error" trying to
            // check if the field entity is stored in the entitiesChecked array.
            if ($this->checkedEntityCache
              ->isChecked($field_entity)) {
              continue;
            }
            else {

              // Add the current entity to the list of checked entities.
              $this->checkedEntityCache
                ->add($field_entity);
            }

            // Do a recursive call to check if the user is allowed to access
            // this entity.
            if (!$this
              ->isAccessAllowed($field_entity, $uid)) {

              // Dispatch an event to allow subscribers
              // to do something in this case.
              $this->event
                ->setIndex($i);
              $this->event
                ->setField($field);
              $this->event
                ->setEntity($field_entity);
              $this->event
                ->setUid($uid);
              $this->eventDispatcher
                ->dispatch(PermissionsByEntityEvents::ENTITY_FIELD_VALUE_ACCESS_DENIED_EVENT, $this->event);
              $i = $this->event
                ->getIndex();
            }
          }
        }
      }
    }
    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function isAccessControlled(FieldableEntityInterface $entity, bool $clearCache = TRUE) : bool {
    if ($clearCache) {
      $this->checkedEntityCache
        ->clear();
    }

    // Iterate over the fields the entity contains.
    foreach ($entity
      ->getFields() as $field) {

      // We only need to check for entity reference fields
      // which references to a taxonomy term.
      if ($field
        ->getFieldDefinition()
        ->getType() == 'entity_reference' && $field
        ->getFieldDefinition()
        ->getSetting('target_type') == 'taxonomy_term') {

        // Iterate over each referenced taxonomy term.

        /** @var \Drupal\Core\Field\FieldItemInterface $item */
        foreach ($field
          ->getValue() as $item) {
          if (!empty($item['target_id']) && $this
            ->isAnyPermissionSetForTerm($item['target_id'], $entity
            ->language()
            ->getId())) {
            return TRUE;
          }
        }
      }

      // Check if the field contains another fieldable entity,
      // that we need to check.
      if ($field->entity && $field->entity instanceof FieldableEntityInterface) {

        // We need to iterate over the entities.
        $num_values = $field
          ->count();
        if ($num_values > 0) {

          // Iterate over the field values.
          for ($i = 0; $i < $num_values; $i++) {

            // Get the value of the current field index.
            $field_value = $field
              ->get($i);

            // If the value is null or empty we continue with the next index of
            // the loop.
            if (!$field_value) {
              continue;
            }

            // Get the field entity.
            $field_entity = $field_value->entity;

            // If the field entity is null we also continue with the next index
            // of the loop.
            if (!$field_entity) {
              continue;
            }

            // It is possible, that the referenced field entity creates a
            // circular dependency to the current entity. This will cause
            // memory limit exhausted errors because there is no way out for
            // the script. To avoid this, we need to be able to imagine if we
            // already checked this field entity before. If so, we ignore this
            // field entity, if not we can securely do a recursive call.
            //
            // Using own method to avoid "max nesting level error" trying to
            // check if the field entity is stored in the entitiesChecked array.
            if ($this->checkedEntityCache
              ->isChecked($field_entity)) {
              continue;
            }
            else {

              // Add the current entity to the list of checked entities.
              $this->checkedEntityCache
                ->add($field_entity);
            }

            // Do a recursive call to check if the user is allowed to access
            // this entity.
            if ($this
              ->isAccessControlled($field_entity, FALSE)) {
              return TRUE;
            }
          }
        }
      }
    }
    return FALSE;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
AccessCheck::$database protected property The database connection.
AccessCheck::$entityFieldManager private property
AccessCheck::canUserAccessByNode public function
AccessCheck::dispatchDeniedEvent private function
AccessCheck::dispatchDeniedEventOnRestricedAccess public function
AccessCheck::handleNode public function
AccessCheck::isAccessAllowedByDatabase public function
AccessCheck::isAnyPermissionSetForTerm public function
AccessCheck::isAnyTaxonomyTermFieldDefinedInNodeType public function
AccessCheck::isTermAllowedByUserId private function
AccessCheck::isTermAllowedByUserRole public function
AccessChecker::$checkedEntityCache private property The cache for checked entities.
AccessChecker::$event private property The entity field value access denied event.
AccessChecker::$eventDispatcher private property The event dispatcher. Overrides AccessCheck::$eventDispatcher
AccessChecker::isAccessAllowed public function Checks if a user is allowed to access a fieldable entity. Overrides AccessCheckerInterface::isAccessAllowed
AccessChecker::isAccessControlled public function Checks if the given entity uses terms with access restrictions. Overrides AccessCheckerInterface::isAccessControlled
AccessChecker::__construct public function Constructs AccessCheck object. Overrides AccessCheck::__construct