Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 52
n/a
0 / 0
n/a
0 / 0
0.00% covered (danger)
0.00%
0 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
AccessControlHandler
0.00% covered (danger)
0.00%
0 / 52
n/a
0 / 0
n/a
0 / 0
0.00% covered (danger)
0.00%
0 / 6
462
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 4
n/a
0 / 0
n/a
0 / 0
0.00% covered (danger)
0.00%
0 / 1
2
 createInstance
0.00% covered (danger)
0.00%
0 / 6
n/a
0 / 0
n/a
0 / 0
0.00% covered (danger)
0.00%
0 / 1
2
 loadCurrentPageLayout
0.00% covered (danger)
0.00%
0 / 7
n/a
0 / 0
n/a
0 / 0
0.00% covered (danger)
0.00%
0 / 1
12
 checkAccess
0.00% covered (danger)
0.00%
0 / 19
n/a
0 / 0
n/a
0 / 0
0.00% covered (danger)
0.00%
0 / 1
72
 getAccessResult
0.00% covered (danger)
0.00%
0 / 11
n/a
0 / 0
n/a
0 / 0
0.00% covered (danger)
0.00%
0 / 1
30
 mergeCacheabilityFromConditions
0.00% covered (danger)
0.00%
0 / 5
n/a
0 / 0
n/a
0 / 0
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3declare(strict_types=1);
4
5namespace Drupal\display_builder_page_layout;
6
7use Drupal\Component\Plugin\Exception\ContextException;
8use Drupal\Component\Plugin\Exception\MissingValueContextException;
9use Drupal\Core\Access\AccessResult;
10use Drupal\Core\Access\AccessResultInterface;
11use Drupal\Core\Cache\Cache;
12use Drupal\Core\Cache\CacheableDependencyInterface;
13use Drupal\Core\Condition\ConditionAccessResolverTrait;
14use Drupal\Core\Entity\EntityAccessControlHandler;
15use Drupal\Core\Entity\EntityHandlerInterface;
16use Drupal\Core\Entity\EntityInterface;
17use Drupal\Core\Entity\EntityTypeInterface;
18use Drupal\Core\Entity\EntityTypeManagerInterface;
19use Drupal\Core\Plugin\Context\ContextHandlerInterface;
20use Drupal\Core\Plugin\Context\ContextRepositoryInterface;
21use Drupal\Core\Plugin\ContextAwarePluginInterface;
22use Drupal\Core\Session\AccountInterface;
23use Symfony\Component\DependencyInjection\ContainerInterface;
24
25/**
26 * Defines the access control handler for the page layout entity type.
27 *
28 * @see \Drupal\display_builder_page_layout\Entity\PageLayout
29 */
30class AccessControlHandler extends EntityAccessControlHandler implements EntityHandlerInterface {
31
32  use ConditionAccessResolverTrait;
33
34  /**
35   * The plugin context handler.
36   *
37   * @var \Drupal\Core\Plugin\Context\ContextHandlerInterface
38   */
39  protected $contextHandler;
40
41  /**
42   * The context manager service.
43   *
44   * @var \Drupal\Core\Plugin\Context\ContextRepositoryInterface
45   */
46  protected $contextRepository;
47
48  /**
49   * The entity type manager.
50   */
51  protected EntityTypeManagerInterface $entityTypeManager;
52
53  /**
54   * {@inheritdoc}
55   */
56  public function __construct(
57    EntityTypeInterface $entity_type,
58    ContextHandlerInterface $context_handler,
59    ContextRepositoryInterface $context_repository,
60    EntityTypeManagerInterface $entity_type_manager,
61  ) {
62    parent::__construct($entity_type);
63    $this->contextHandler = $context_handler;
64    $this->contextRepository = $context_repository;
65    $this->entityTypeManager = $entity_type_manager;
66  }
67
68  /**
69   * {@inheritdoc}
70   */
71  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type): AccessControlHandler {
72    return new static(
73      $entity_type,
74      $container->get('context.handler'),
75      $container->get('context.repository'),
76      $container->get('entity_type.manager'),
77    );
78  }
79
80  /**
81   * Load the first suitable Page Layout entity.
82   *
83   * A 'suitable' Page Layout is an entity which is enabled, meeting the right
84   * conditions, and with non-empty sources.
85   *
86   * @return ?PageLayoutInterface
87   *   A Page Layout entity.
88   */
89  public function loadCurrentPageLayout(): ?PageLayoutInterface {
90    $storage = $this->entityTypeManager->getStorage('page_layout');
91    $entity_ids = $storage->getQuery()->accessCheck(TRUE)->sort('weight', 'ASC')->execute();
92    /** @var \Drupal\display_builder_page_layout\PageLayoutInterface[] $page_layouts */
93    $page_layouts = $storage->loadMultiple($entity_ids);
94
95    foreach ($page_layouts as $page_layout) {
96      if ($this->access($page_layout, 'view')) {
97        return $page_layout;
98      }
99    }
100
101    // If no suitable Page Layout found, PageVariantSubscriber will load the
102    // usual Block Layout with theme's page.html.twig.
103    return NULL;
104  }
105
106  /**
107   * {@inheritdoc}
108   */
109  protected function checkAccess(EntityInterface $entity, $operation, AccountInterface $account): AccessResultInterface {
110    /** @var \Drupal\display_builder_page_layout\PageLayoutInterface $entity */
111    if ($operation !== 'view') {
112      return parent::checkAccess($entity, $operation, $account);
113    }
114
115    // Don't grant access to disabled page layouts.
116    if (!$entity->status() || empty($entity->getSources())) {
117      return AccessResult::forbidden()->addCacheableDependency($entity);
118    }
119
120    $conditions = [];
121    $missing_context = FALSE;
122    $missing_value = FALSE;
123
124    foreach ($entity->getConditions() as $condition_id => $condition) {
125      if ($condition instanceof ContextAwarePluginInterface) {
126        try {
127          $contexts = $this->contextRepository->getRuntimeContexts(\array_values($condition->getContextMapping()));
128          $this->contextHandler->applyContextMapping($condition, $contexts);
129        }
130        catch (MissingValueContextException) {
131          $missing_value = TRUE;
132        }
133        catch (ContextException) {
134          $missing_context = TRUE;
135        }
136      }
137      $conditions[$condition_id] = $condition;
138    }
139    $access = $this->getAccessResult($missing_value, $missing_context, $conditions);
140    $this->mergeCacheabilityFromConditions($access, $conditions);
141
142    // Ensure that access is evaluated again when the entity changes.
143    return $access->addCacheableDependency($entity);
144  }
145
146  /**
147   * Get access result.
148   *
149   * @param bool $missing_value
150   *   Whether any of the contexts are missing a value.
151   * @param bool $missing_context
152   *   Whether any of the contexts are missing.
153   * @param \Drupal\Core\Condition\ConditionInterface[] $conditions
154   *   List of visibility conditions.
155   *
156   * @return \Drupal\Core\Access\AccessResult
157   *   The access result.
158   */
159  protected function getAccessResult(bool $missing_value, bool $missing_context, array $conditions): AccessResult {
160    if ($missing_context) {
161      // If any context is missing then we might be missing cacheable
162      // metadata, and don't know based on what conditions the layout is
163      // accessible or not. Make sure the result cannot be cached.
164      $access = AccessResult::forbidden()->setCacheMaxAge(0);
165    }
166    elseif ($missing_value) {
167      // The contexts exist but have no value. Deny access without
168      // disabling caching. For example the node type condition will have a
169      // missing context on any non-node route like the frontpage.
170      $access = AccessResult::forbidden();
171    }
172    elseif ($this->resolveConditions($conditions, 'and') !== FALSE) {
173      $access = AccessResult::allowed();
174    }
175    else {
176      $reason = \count($conditions) > 1
177      ? "One of the conditions ('%s') denied access."
178      : "The  condition '%s' denied access.";
179      $access = AccessResult::forbidden(\sprintf($reason, \implode("', '", \array_keys($conditions))));
180    }
181
182    return $access;
183  }
184
185  /**
186   * Merges cacheable metadata from conditions onto the access result object.
187   *
188   * @param \Drupal\Core\Access\AccessResult $access
189   *   The access result object.
190   * @param \Drupal\Core\Condition\ConditionInterface[] $conditions
191   *   List of visibility conditions.
192   */
193  protected function mergeCacheabilityFromConditions(AccessResult $access, array $conditions): void {
194    foreach ($conditions as $condition) {
195      if ($condition instanceof CacheableDependencyInterface) {
196        $access->addCacheTags($condition->getCacheTags());
197        $access->addCacheContexts($condition->getCacheContexts());
198        $access->setCacheMaxAge(Cache::mergeMaxAges($access->getCacheMaxAge(), $condition->getCacheMaxAge()));
199      }
200    }
201  }
202
203}

Paths

Below are the source code lines that represent each code path as identified by Xdebug. Please note a path is not necessarily coterminous with a line, a line may contain multiple paths and therefore show up more than once. Please also be aware that some paths may include implicit rather than explicit branches, e.g. an if statement always has an else as part of its logical flow even if you didn't write one.