Code Coverage |
||||||||||||||||
Lines |
Branches |
Paths |
Functions and Methods |
Classes and Traits |
||||||||||||
| Total | |
0.00% |
0 / 60 |
n/a |
0 / 0 |
n/a |
0 / 0 |
|
0.00% |
0 / 8 |
CRAP | |
0.00% |
0 / 1 |
||
| EntityViewOverridesController | |
0.00% |
0 / 60 |
n/a |
0 / 0 |
n/a |
0 / 0 |
|
0.00% |
0 / 8 |
306 | |
0.00% |
0 / 1 |
||
| title | |
0.00% |
0 / 7 |
n/a |
0 / 0 |
n/a |
0 / 0 |
|
0.00% |
0 / 1 |
2 | |||||
| getBuilder | |
0.00% |
0 / 7 |
n/a |
0 / 0 |
n/a |
0 / 0 |
|
0.00% |
0 / 1 |
2 | |||||
| getFirstBuilder | |
0.00% |
0 / 10 |
n/a |
0 / 0 |
n/a |
0 / 0 |
|
0.00% |
0 / 1 |
6 | |||||
| checkAccess | |
0.00% |
0 / 7 |
n/a |
0 / 0 |
n/a |
0 / 0 |
|
0.00% |
0 / 1 |
6 | |||||
| checkFirstBuilderAccess | |
0.00% |
0 / 6 |
n/a |
0 / 0 |
n/a |
0 / 0 |
|
0.00% |
0 / 1 |
6 | |||||
| getFirstOverridableViewMode | |
0.00% |
0 / 7 |
n/a |
0 / 0 |
n/a |
0 / 0 |
|
0.00% |
0 / 1 |
12 | |||||
| getEntityViewDisplay | |
0.00% |
0 / 4 |
n/a |
0 / 0 |
n/a |
0 / 0 |
|
0.00% |
0 / 1 |
2 | |||||
| overrideBuilderAccessResult | |
0.00% |
0 / 12 |
n/a |
0 / 0 |
n/a |
0 / 0 |
|
0.00% |
0 / 1 |
30 | |||||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace Drupal\display_builder_entity_view\Controller; |
| 6 | |
| 7 | use Drupal\Core\Access\AccessResult; |
| 8 | use Drupal\Core\Access\AccessResultInterface; |
| 9 | use Drupal\Core\Cache\CacheableMetadata; |
| 10 | use Drupal\Core\Entity\EntityInterface; |
| 11 | use Drupal\Core\Routing\RouteMatchInterface; |
| 12 | use Drupal\Core\Routing\TrustedRedirectResponse; |
| 13 | use Drupal\Core\Session\AccountInterface; |
| 14 | use Drupal\Core\StringTranslation\TranslatableMarkup; |
| 15 | use Drupal\Core\Url; |
| 16 | use Drupal\display_builder\Controller\IntegrationControllerBase; |
| 17 | use Drupal\display_builder\DisplayBuildableInterface; |
| 18 | use Drupal\display_builder_entity_view\Entity\DisplayBuilderOverridableInterface; |
| 19 | use Drupal\display_builder_entity_view\Entity\EntityViewDisplay; |
| 20 | use Symfony\Component\HttpFoundation\Response; |
| 21 | use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; |
| 22 | |
| 23 | /** |
| 24 | * Defines a controller to get access the Display Builder admin UI. |
| 25 | * |
| 26 | * @internal |
| 27 | * Controller classes are internal. |
| 28 | */ |
| 29 | final class EntityViewOverridesController extends IntegrationControllerBase { |
| 30 | |
| 31 | /** |
| 32 | * Provides a generic title callback for a display used in entities. |
| 33 | * |
| 34 | * @param \Drupal\Core\Routing\RouteMatchInterface $route_match |
| 35 | * The route match object. |
| 36 | * |
| 37 | * @return \Drupal\Core\StringTranslation\TranslatableMarkup |
| 38 | * The title for the display page. |
| 39 | */ |
| 40 | public function title(RouteMatchInterface $route_match): TranslatableMarkup { |
| 41 | $entity_type = $route_match->getParameter('entity_type_id'); |
| 42 | $entity = $route_match->getParameter($entity_type); |
| 43 | $param = [ |
| 44 | '@title' => $entity->label(), |
| 45 | // view_mode_name route parameter is never supposed to be NULL but Drupal |
| 46 | // trigger a warning if we omit to check this. |
| 47 | '@view_mode_name' => $route_match->getParameter('view_mode_name') ?? '', |
| 48 | ]; |
| 49 | |
| 50 | return $this->t('Display builder for @title, @view_mode_name', $param); |
| 51 | } |
| 52 | |
| 53 | /** |
| 54 | * Renders the Layout UI for override entities. |
| 55 | * |
| 56 | * @param \Drupal\Core\Routing\RouteMatchInterface $route_match |
| 57 | * The route match object. |
| 58 | * |
| 59 | * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException |
| 60 | * If the entity view display is not found or not overridable. |
| 61 | * |
| 62 | * @return array |
| 63 | * A render array containing the display builder. |
| 64 | */ |
| 65 | public function getBuilder(RouteMatchInterface $route_match): array { |
| 66 | $entity_type_id = $route_match->getParameter('entity_type_id'); |
| 67 | /** @var \Drupal\Core\Entity\FieldableEntityInterface $entity */ |
| 68 | $entity = $route_match->getParameter($entity_type_id); |
| 69 | |
| 70 | $view_mode = $route_match->getParameter('view_mode_name'); |
| 71 | |
| 72 | /** @var \Drupal\display_builder_entity_view\Entity\DisplayBuilderEntityDisplayInterface $entity_display */ |
| 73 | $entity_display = $this->getEntityViewDisplay($entity_type_id, $entity->bundle(), $view_mode); |
| 74 | \assert($entity_display instanceof DisplayBuilderOverridableInterface); |
| 75 | /** @var \Drupal\display_builder\DisplayBuildableInterface $with_display_builder */ |
| 76 | $with_display_builder = $entity->get($entity_display->getDisplayBuilderOverrideField()); |
| 77 | |
| 78 | return $this->renderBuilder($with_display_builder); |
| 79 | } |
| 80 | |
| 81 | /** |
| 82 | * Redirects to the default route of a specified component. |
| 83 | * |
| 84 | * @param \Drupal\Core\Routing\RouteMatchInterface $route_match |
| 85 | * The route match object. |
| 86 | * |
| 87 | * @return \Symfony\Component\HttpFoundation\Response |
| 88 | * A response object that redirects to the first available builder. |
| 89 | */ |
| 90 | public function getFirstBuilder(RouteMatchInterface $route_match): Response { |
| 91 | $entity_type_id = $route_match->getParameter('entity_type_id'); |
| 92 | /** @var \Drupal\Core\Entity\FieldableEntityInterface $entity */ |
| 93 | $entity = $route_match->getParameter($entity_type_id); |
| 94 | $account = $this->currentUser(); |
| 95 | $view_mode = $this->getFirstOverridableViewMode($entity, $account); |
| 96 | |
| 97 | if ($view_mode) { |
| 98 | $route = "entity.{$entity_type_id}.display_builder.{$view_mode}"; |
| 99 | $url = Url::fromRoute($route, [$entity_type_id => $entity->id()]); |
| 100 | $response = new TrustedRedirectResponse($url->toString()); |
| 101 | |
| 102 | return $response->addCacheableDependency((new CacheableMetadata())->setCacheMaxAge(0)); |
| 103 | } |
| 104 | |
| 105 | throw new AccessDeniedHttpException(); |
| 106 | } |
| 107 | |
| 108 | /** |
| 109 | * Access callback to ensure display tab belongs to current bundle. |
| 110 | * |
| 111 | * @param \Drupal\Core\Routing\RouteMatchInterface $route_match |
| 112 | * The route match object. |
| 113 | * @param \Drupal\Core\Session\AccountInterface $account |
| 114 | * The user account. |
| 115 | * |
| 116 | * @return \Drupal\Core\Access\AccessResultInterface |
| 117 | * The access result. |
| 118 | */ |
| 119 | public function checkAccess(RouteMatchInterface $route_match, AccountInterface $account): AccessResultInterface { |
| 120 | $route = $route_match->getRouteObject(); |
| 121 | |
| 122 | if (!$route) { |
| 123 | return AccessResult::forbidden()->addCacheContexts(['route']); |
| 124 | } |
| 125 | $entity_type_id = $route->getDefault('entity_type_id'); |
| 126 | $entity = $route_match->getParameter($entity_type_id); |
| 127 | $view_mode_name = $route->getDefault('view_mode_name'); |
| 128 | |
| 129 | return $this->overrideBuilderAccessResult($account, $entity, $view_mode_name); |
| 130 | } |
| 131 | |
| 132 | /** |
| 133 | * Access callback for the main display. |
| 134 | * |
| 135 | * @param \Drupal\Core\Routing\RouteMatchInterface $route_match |
| 136 | * The route match object. |
| 137 | * @param \Drupal\Core\Session\AccountInterface $account |
| 138 | * The user account. |
| 139 | * |
| 140 | * @return \Drupal\Core\Access\AccessResultInterface |
| 141 | * The access result. |
| 142 | */ |
| 143 | public function checkFirstBuilderAccess(RouteMatchInterface $route_match, AccountInterface $account): AccessResultInterface { |
| 144 | $entity_type_id = $route_match->getParameter('entity_type_id'); |
| 145 | /** @var \Drupal\Core\Entity\FieldableEntityInterface $entity */ |
| 146 | $entity = $route_match->getParameter($entity_type_id); |
| 147 | $view_mode = $this->getFirstOverridableViewMode($entity, $account); |
| 148 | |
| 149 | if ($view_mode) { |
| 150 | return AccessResult::allowed()->addCacheContexts(['route']); |
| 151 | } |
| 152 | |
| 153 | return AccessResult::forbidden()->addCacheContexts(['route']); |
| 154 | } |
| 155 | |
| 156 | /** |
| 157 | * Get the first overridable view mode of an entity. |
| 158 | * |
| 159 | * @param \Drupal\Core\Entity\EntityInterface $entity |
| 160 | * The entity to check. |
| 161 | * @param \Drupal\Core\Session\AccountInterface $account |
| 162 | * The user account. |
| 163 | * |
| 164 | * @return string|null |
| 165 | * The first overridable view mode name, or NULL if none is found. |
| 166 | */ |
| 167 | protected function getFirstOverridableViewMode(EntityInterface $entity, AccountInterface $account): ?string { |
| 168 | $display_infos = EntityViewDisplay::getDisplayInfos($this->entityTypeManager()); |
| 169 | $view_modes = $display_infos[$entity->getEntityTypeId()]['bundles'][$entity->bundle()] ?? []; |
| 170 | |
| 171 | foreach (\array_keys($view_modes) as $view_mode) { |
| 172 | $access = $this->overrideBuilderAccessResult($account, $entity, (string) $view_mode); |
| 173 | |
| 174 | if ($access->isAllowed()) { |
| 175 | return (string) $view_mode; |
| 176 | } |
| 177 | } |
| 178 | |
| 179 | return NULL; |
| 180 | } |
| 181 | |
| 182 | /** |
| 183 | * Get entity view display entity. |
| 184 | * |
| 185 | * @param string $entity_type_id |
| 186 | * Entity type ID. |
| 187 | * @param string $bundle |
| 188 | * Fieldable entity's bundle. |
| 189 | * @param string $view_mode |
| 190 | * View mode of the display. |
| 191 | * |
| 192 | * @return \Drupal\display_builder\DisplayBuildableInterface|null |
| 193 | * The corresponding entity view display. |
| 194 | */ |
| 195 | protected function getEntityViewDisplay(string $entity_type_id, string $bundle, string $view_mode): ?DisplayBuildableInterface { |
| 196 | $display_id = \sprintf('%s.%s.%s', $entity_type_id, $bundle, $view_mode); |
| 197 | |
| 198 | /** @var \Drupal\display_builder\DisplayBuildableInterface|null $display */ |
| 199 | $display = $this->entityTypeManager()->getStorage('entity_view_display') |
| 200 | ->load($display_id); |
| 201 | |
| 202 | return $display; |
| 203 | } |
| 204 | |
| 205 | /** |
| 206 | * Returns AccessResult for given entity and view mode. |
| 207 | * |
| 208 | * Any user with both the permission to edit the content and the one to use |
| 209 | * the display builder profile can override the display. |
| 210 | * The permission to edit the content is already checked with _entity_access |
| 211 | * in DisplayBuilderRoutes so we just need to check the profile permission. |
| 212 | * |
| 213 | * @param \Drupal\Core\Session\AccountInterface $account |
| 214 | * The user account. |
| 215 | * @param \Drupal\Core\Entity\EntityInterface $entity |
| 216 | * The entity to check access for. |
| 217 | * @param string|null $view_mode_name |
| 218 | * The view mode name, or NULL if not applicable. |
| 219 | * |
| 220 | * @return \Drupal\Core\Access\AccessResultInterface |
| 221 | * The access result. |
| 222 | */ |
| 223 | protected function overrideBuilderAccessResult(AccountInterface $account, EntityInterface $entity, ?string $view_mode_name): AccessResultInterface { |
| 224 | $forbidden = AccessResult::forbidden()->addCacheContexts(['route']); |
| 225 | |
| 226 | if ($view_mode_name === NULL) { |
| 227 | return $forbidden; |
| 228 | } |
| 229 | |
| 230 | $display = self::getEntityViewDisplay($entity->getEntityTypeId(), $entity->bundle(), $view_mode_name); |
| 231 | |
| 232 | if (!$display instanceof DisplayBuilderOverridableInterface) { |
| 233 | return $forbidden; |
| 234 | } |
| 235 | |
| 236 | if (!$display->isDisplayBuilderOverridable()) { |
| 237 | return $forbidden; |
| 238 | } |
| 239 | |
| 240 | $permission = $display->getDisplayBuilderOverrideProfile()->getPermissionName(); |
| 241 | |
| 242 | // This is the expected check. |
| 243 | if (!$account->hasPermission($permission)) { |
| 244 | return $forbidden; |
| 245 | } |
| 246 | |
| 247 | return AccessResult::allowed()->addCacheableDependency($display)->addCacheContexts(['route']); |
| 248 | } |
| 249 | |
| 250 | } |
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.