Code Coverage |
||||||||||||||||
Lines |
Branches |
Paths |
Functions and Methods |
Classes and Traits |
||||||||||||
| Total | |
0.00% |
0 / 54 |
|
0.00% |
0 / 40 |
|
0.00% |
0 / 29 |
|
0.00% |
0 / 6 |
CRAP | |
0.00% |
0 / 1 |
| AccessControlHandler | |
0.00% |
0 / 54 |
|
0.00% |
0 / 40 |
|
0.00% |
0 / 29 |
|
0.00% |
0 / 6 |
506 | |
0.00% |
0 / 1 |
| __construct | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| createInstance | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| loadCurrentPageLayout | |
0.00% |
0 / 7 |
|
0.00% |
0 / 5 |
|
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 | |||
| checkAccess | |
0.00% |
0 / 21 |
|
0.00% |
0 / 17 |
|
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
90 | |||
| getAccessResult | |
0.00% |
0 / 11 |
|
0.00% |
0 / 11 |
|
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
30 | |||
| mergeCacheabilityFromConditions | |
0.00% |
0 / 5 |
|
0.00% |
0 / 5 |
|
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace Drupal\display_builder_page_layout; |
| 6 | |
| 7 | use Drupal\Component\Plugin\Exception\ContextException; |
| 8 | use Drupal\Component\Plugin\Exception\MissingValueContextException; |
| 9 | use Drupal\Core\Access\AccessResult; |
| 10 | use Drupal\Core\Access\AccessResultInterface; |
| 11 | use Drupal\Core\Cache\Cache; |
| 12 | use Drupal\Core\Cache\CacheableDependencyInterface; |
| 13 | use Drupal\Core\Condition\ConditionAccessResolverTrait; |
| 14 | use Drupal\Core\Entity\EntityAccessControlHandler; |
| 15 | use Drupal\Core\Entity\EntityHandlerInterface; |
| 16 | use Drupal\Core\Entity\EntityInterface; |
| 17 | use Drupal\Core\Entity\EntityTypeInterface; |
| 18 | use Drupal\Core\Entity\EntityTypeManagerInterface; |
| 19 | use Drupal\Core\Plugin\Context\ContextHandlerInterface; |
| 20 | use Drupal\Core\Plugin\Context\ContextRepositoryInterface; |
| 21 | use Drupal\Core\Plugin\ContextAwarePluginInterface; |
| 22 | use Drupal\Core\Session\AccountInterface; |
| 23 | use 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 | */ |
| 30 | class 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 | // Require permission to administer page layouts. |
| 116 | if (!$account->hasPermission('administer page_layout')) { |
| 117 | return AccessResult::forbidden()->cachePerPermissions(); |
| 118 | } |
| 119 | |
| 120 | // Don't grant access to disabled page layouts. |
| 121 | if (!$entity->status() || empty($entity->getSources())) { |
| 122 | return AccessResult::forbidden()->addCacheableDependency($entity); |
| 123 | } |
| 124 | |
| 125 | $conditions = []; |
| 126 | $missing_context = FALSE; |
| 127 | $missing_value = FALSE; |
| 128 | |
| 129 | foreach ($entity->getConditions() as $condition_id => $condition) { |
| 130 | if ($condition instanceof ContextAwarePluginInterface) { |
| 131 | try { |
| 132 | $contexts = $this->contextRepository->getRuntimeContexts(\array_values($condition->getContextMapping())); |
| 133 | $this->contextHandler->applyContextMapping($condition, $contexts); |
| 134 | } |
| 135 | catch (MissingValueContextException) { |
| 136 | $missing_value = TRUE; |
| 137 | } |
| 138 | catch (ContextException) { |
| 139 | $missing_context = TRUE; |
| 140 | } |
| 141 | } |
| 142 | $conditions[$condition_id] = $condition; |
| 143 | } |
| 144 | $access = $this->getAccessResult($missing_value, $missing_context, $conditions); |
| 145 | $this->mergeCacheabilityFromConditions($access, $conditions); |
| 146 | |
| 147 | // Ensure that access is evaluated again when the entity changes. |
| 148 | return $access->addCacheableDependency($entity); |
| 149 | } |
| 150 | |
| 151 | /** |
| 152 | * Get access result. |
| 153 | * |
| 154 | * @param bool $missing_value |
| 155 | * Whether any of the contexts are missing a value. |
| 156 | * @param bool $missing_context |
| 157 | * Whether any of the contexts are missing. |
| 158 | * @param \Drupal\Core\Condition\ConditionInterface[] $conditions |
| 159 | * List of visibility conditions. |
| 160 | * |
| 161 | * @return \Drupal\Core\Access\AccessResult |
| 162 | * The access result. |
| 163 | */ |
| 164 | protected function getAccessResult(bool $missing_value, bool $missing_context, array $conditions): AccessResult { |
| 165 | if ($missing_context) { |
| 166 | // If any context is missing then we might be missing cacheable |
| 167 | // metadata, and don't know based on what conditions the layout is |
| 168 | // accessible or not. Make sure the result cannot be cached. |
| 169 | $access = AccessResult::forbidden()->setCacheMaxAge(0); |
| 170 | } |
| 171 | elseif ($missing_value) { |
| 172 | // The contexts exist but have no value. Deny access without |
| 173 | // disabling caching. For example the node type condition will have a |
| 174 | // missing context on any non-node route like the frontpage. |
| 175 | $access = AccessResult::forbidden(); |
| 176 | } |
| 177 | elseif ($this->resolveConditions($conditions, 'and') !== FALSE) { |
| 178 | $access = AccessResult::allowed(); |
| 179 | } |
| 180 | else { |
| 181 | $reason = \count($conditions) > 1 |
| 182 | ? "One of the conditions ('%s') denied access." |
| 183 | : "The condition '%s' denied access."; |
| 184 | $access = AccessResult::forbidden(\sprintf($reason, \implode("', '", \array_keys($conditions)))); |
| 185 | } |
| 186 | |
| 187 | return $access; |
| 188 | } |
| 189 | |
| 190 | /** |
| 191 | * Merges cacheable metadata from conditions onto the access result object. |
| 192 | * |
| 193 | * @param \Drupal\Core\Access\AccessResult $access |
| 194 | * The access result object. |
| 195 | * @param \Drupal\Core\Condition\ConditionInterface[] $conditions |
| 196 | * List of visibility conditions. |
| 197 | */ |
| 198 | protected function mergeCacheabilityFromConditions(AccessResult $access, array $conditions): void { |
| 199 | foreach ($conditions as $condition) { |
| 200 | if ($condition instanceof CacheableDependencyInterface) { |
| 201 | $access->addCacheTags($condition->getCacheTags()); |
| 202 | $access->addCacheContexts($condition->getCacheContexts()); |
| 203 | $access->setCacheMaxAge(Cache::mergeMaxAges($access->getCacheMaxAge(), $condition->getCacheMaxAge())); |
| 204 | } |
| 205 | } |
| 206 | } |
| 207 | |
| 208 | } |
Below are the source code lines that represent each code branch as identified by Xdebug. Please note a branch is not
necessarily coterminous with a line, a line may contain multiple branches and therefore show up more than once.
Please also be aware that some branches may be implicit rather than explicit, e.g. an if statement
always has an else as part of its logical flow even if you didn't write one.
| 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 | } |
| 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); |
| 116 | if (!$account->hasPermission('administer page_layout')) { |
| 117 | return AccessResult::forbidden()->cachePerPermissions(); |
| 121 | if (!$entity->status() || empty($entity->getSources())) { |
| 121 | if (!$entity->status() || empty($entity->getSources())) { |
| 122 | return AccessResult::forbidden()->addCacheableDependency($entity); |
| 125 | $conditions = []; |
| 126 | $missing_context = FALSE; |
| 127 | $missing_value = FALSE; |
| 128 | |
| 129 | foreach ($entity->getConditions() as $condition_id => $condition) { |
| 129 | foreach ($entity->getConditions() as $condition_id => $condition) { |
| 129 | foreach ($entity->getConditions() as $condition_id => $condition) { |
| 130 | if ($condition instanceof ContextAwarePluginInterface) { |
| 132 | $contexts = $this->contextRepository->getRuntimeContexts(\array_values($condition->getContextMapping())); |
| 133 | $this->contextHandler->applyContextMapping($condition, $contexts); |
| 135 | catch (MissingValueContextException) { |
| 136 | $missing_value = TRUE; |
| 138 | catch (ContextException) { |
| 139 | $missing_context = TRUE; |
| 140 | } |
| 141 | } |
| 142 | $conditions[$condition_id] = $condition; |
| 129 | foreach ($entity->getConditions() as $condition_id => $condition) { |
| 130 | if ($condition instanceof ContextAwarePluginInterface) { |
| 131 | try { |
| 132 | $contexts = $this->contextRepository->getRuntimeContexts(\array_values($condition->getContextMapping())); |
| 133 | $this->contextHandler->applyContextMapping($condition, $contexts); |
| 134 | } |
| 135 | catch (MissingValueContextException) { |
| 136 | $missing_value = TRUE; |
| 137 | } |
| 138 | catch (ContextException) { |
| 139 | $missing_context = TRUE; |
| 140 | } |
| 141 | } |
| 142 | $conditions[$condition_id] = $condition; |
| 129 | foreach ($entity->getConditions() as $condition_id => $condition) { |
| 130 | if ($condition instanceof ContextAwarePluginInterface) { |
| 131 | try { |
| 132 | $contexts = $this->contextRepository->getRuntimeContexts(\array_values($condition->getContextMapping())); |
| 133 | $this->contextHandler->applyContextMapping($condition, $contexts); |
| 134 | } |
| 135 | catch (MissingValueContextException) { |
| 136 | $missing_value = TRUE; |
| 137 | } |
| 138 | catch (ContextException) { |
| 139 | $missing_context = TRUE; |
| 140 | } |
| 141 | } |
| 142 | $conditions[$condition_id] = $condition; |
| 143 | } |
| 144 | $access = $this->getAccessResult($missing_value, $missing_context, $conditions); |
| 145 | $this->mergeCacheabilityFromConditions($access, $conditions); |
| 146 | |
| 147 | // Ensure that access is evaluated again when the entity changes. |
| 148 | return $access->addCacheableDependency($entity); |
| 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'), |
| 164 | protected function getAccessResult(bool $missing_value, bool $missing_context, array $conditions): AccessResult { |
| 165 | if ($missing_context) { |
| 165 | if ($missing_context) { |
| 166 | // If any context is missing then we might be missing cacheable |
| 167 | // metadata, and don't know based on what conditions the layout is |
| 168 | // accessible or not. Make sure the result cannot be cached. |
| 169 | $access = AccessResult::forbidden()->setCacheMaxAge(0); |
| 171 | elseif ($missing_value) { |
| 171 | elseif ($missing_value) { |
| 172 | // The contexts exist but have no value. Deny access without |
| 173 | // disabling caching. For example the node type condition will have a |
| 174 | // missing context on any non-node route like the frontpage. |
| 175 | $access = AccessResult::forbidden(); |
| 177 | elseif ($this->resolveConditions($conditions, 'and') !== FALSE) { |
| 177 | elseif ($this->resolveConditions($conditions, 'and') !== FALSE) { |
| 178 | $access = AccessResult::allowed(); |
| 181 | $reason = \count($conditions) > 1 |
| 182 | ? "One of the conditions ('%s') denied access." |
| 181 | $reason = \count($conditions) > 1 |
| 182 | ? "One of the conditions ('%s') denied access." |
| 183 | : "The condition '%s' denied access."; |
| 181 | $reason = \count($conditions) > 1 |
| 182 | ? "One of the conditions ('%s') denied access." |
| 183 | : "The condition '%s' denied access."; |
| 184 | $access = AccessResult::forbidden(\sprintf($reason, \implode("', '", \array_keys($conditions)))); |
| 185 | } |
| 186 | |
| 187 | return $access; |
| 187 | return $access; |
| 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) { |
| 95 | foreach ($page_layouts as $page_layout) { |
| 96 | if ($this->access($page_layout, 'view')) { |
| 97 | return $page_layout; |
| 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; |
| 198 | protected function mergeCacheabilityFromConditions(AccessResult $access, array $conditions): void { |
| 199 | foreach ($conditions as $condition) { |
| 199 | foreach ($conditions as $condition) { |
| 200 | if ($condition instanceof CacheableDependencyInterface) { |
| 199 | foreach ($conditions as $condition) { |
| 200 | if ($condition instanceof CacheableDependencyInterface) { |
| 201 | $access->addCacheTags($condition->getCacheTags()); |
| 199 | foreach ($conditions as $condition) { |
| 200 | if ($condition instanceof CacheableDependencyInterface) { |
| 201 | $access->addCacheTags($condition->getCacheTags()); |
| 202 | $access->addCacheContexts($condition->getCacheContexts()); |
| 203 | $access->setCacheMaxAge(Cache::mergeMaxAges($access->getCacheMaxAge(), $condition->getCacheMaxAge())); |
| 204 | } |
| 205 | } |
| 206 | } |