Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 101
0.00% covered (danger)
0.00%
0 / 41
0.00% covered (danger)
0.00%
0 / 33
0.00% covered (danger)
0.00%
0 / 17
CRAP
0.00% covered (danger)
0.00%
0 / 1
DisplayBuilderItemList
0.00% covered (danger)
0.00%
0 / 101
0.00% covered (danger)
0.00%
0 / 41
0.00% covered (danger)
0.00%
0 / 33
0.00% covered (danger)
0.00%
0 / 17
930
0.00% covered (danger)
0.00%
0 / 1
 getPrefix
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getContextRequirement
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getBuilderUrl
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 checkInstanceId
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 getUrlFromInstanceId
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getDisplayUrlFromInstanceId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getProfile
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getInstanceId
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 getSources
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 saveSources
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 checkAccess
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 initInstanceIfMissing
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 getInitialSources
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 getInitialContext
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 entityTypeManager
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getEntityViewDisplay
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
42
 getInstance
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3declare(strict_types=1);
4
5namespace Drupal\display_builder_entity_view\Field;
6
7use Drupal\Core\Access\AccessResultInterface;
8use Drupal\Core\Entity\EntityTypeManagerInterface;
9use Drupal\Core\Field\MapFieldItemList;
10use Drupal\Core\Plugin\Context\Context;
11use Drupal\Core\Plugin\Context\ContextDefinition;
12use Drupal\Core\Plugin\Context\EntityContext;
13use Drupal\Core\Session\AccountInterface;
14use Drupal\Core\Url;
15use Drupal\display_builder\DisplayBuildableInterface;
16use Drupal\display_builder\InstanceInterface;
17use Drupal\display_builder\ProfileInterface;
18use Drupal\display_builder_entity_view\Entity\DisplayBuilderEntityDisplayInterface;
19use Drupal\display_builder_entity_view\Entity\DisplayBuilderOverridableInterface;
20use Drupal\ui_patterns\Plugin\Context\RequirementsContext;
21
22/**
23 * Defines an item list class for layout section fields.
24 *
25 * @internal
26 *   Plugin classes are internal.
27 *
28 * @see \Drupal\layout_builder\Plugin\Field\FieldType\LayoutSectionItem
29 *
30 * phpcs:disable DrupalPractice.Objects.GlobalDrupal.GlobalDrupal
31 */
32final class DisplayBuilderItemList extends MapFieldItemList implements DisplayBuildableInterface {
33
34  /**
35   * The entity type manager.
36   */
37  protected ?EntityTypeManagerInterface $entityTypeManager;
38
39  /**
40   * The loaded display builder instance.
41   */
42  protected ?InstanceInterface $instance;
43
44  /**
45   * {@inheritdoc}
46   */
47  public static function getPrefix(): string {
48    return 'entity_override__';
49  }
50
51  /**
52   * {@inheritdoc}
53   */
54  public static function getContextRequirement(): string {
55    return 'content';
56  }
57
58  /**
59   * {@inheritdoc}
60   */
61  public function getBuilderUrl(): Url {
62    \assert(\is_string($this->getName()));
63    $entity = $this->getEntity();
64    $entity_view = self::getEntityViewDisplay(
65      $entity->getEntityTypeId(),
66      $entity->bundle(),
67      $this->getName(),
68    );
69    $entity_type_id = $entity_view->getTargetEntityTypeId();
70    $parameters = [
71      $entity_type_id => $entity->id(),
72      'view_mode_name' => $entity_view->getMode(),
73    ];
74
75    return Url::fromRoute("entity.{$entity_type_id}.display_builder.{$entity_view->getMode()}", $parameters);
76  }
77
78  /**
79   * {@inheritdoc}
80   */
81  public static function checkInstanceId(string $instance_id): ?array {
82    if (!\str_starts_with($instance_id, self::getPrefix())) {
83      return NULL;
84    }
85    [, $entity_type_id, $entity_id, $field_name] = \explode('__', $instance_id);
86
87    return [
88      'entity_type_id' => $entity_type_id,
89      'entity_id' => $entity_id,
90      'field_name' => $field_name,
91    ];
92  }
93
94  /**
95   * {@inheritdoc}
96   */
97  public static function getUrlFromInstanceId(string $instance_id): Url {
98    [, $entity_type_id, $entity_id, $field_name] = \explode('__', $instance_id);
99
100    $entity = \Drupal::entityTypeManager()->getStorage($entity_type_id)->load($entity_id);
101    $display = self::getEntityViewDisplay($entity_type_id, $entity->bundle(), $field_name);
102    $params = [
103      $entity_type_id => $entity_id,
104      'view_mode_name' => $display->getMode(),
105    ];
106
107    $route_name = \sprintf('entity.%s.display_builder.%s', $entity_type_id, $display->getMode());
108
109    return Url::fromRoute($route_name, $params);
110  }
111
112  /**
113   * {@inheritdoc}
114   */
115  public static function getDisplayUrlFromInstanceId(string $instance_id): Url {
116    return Url::fromRoute('<front>');
117  }
118
119  /**
120   * {@inheritdoc}
121   */
122  public function getProfile(): ?ProfileInterface {
123    \assert(\is_string($this->getName()));
124    $entity = $this->getEntity();
125
126    $entity_view = self::getEntityViewDisplay(
127      $entity->getEntityTypeId(),
128      $entity->bundle(),
129      $this->getName(),
130    );
131    \assert($entity_view instanceof DisplayBuilderOverridableInterface);
132
133    return $entity_view->getDisplayBuilderOverrideProfile();
134  }
135
136  /**
137   * {@inheritdoc}
138   */
139  public function getInstanceId(): ?string {
140    // Usually an entity is new if no ID exists for it yet.
141    if ($this->getEntity()->isNew()) {
142      return NULL;
143    }
144
145    $entity = $this->getEntity();
146
147    return \sprintf('%s%s__%s__%s',
148      self::getPrefix(),
149      $entity->getEntityTypeId(),
150      $entity->id(),
151      $this->getName()
152    );
153  }
154
155  /**
156   * {@inheritdoc}
157   */
158  public function getSources(): array {
159    $data = [];
160
161    foreach ($this->list as $offset => $item) {
162      $data[$offset] = $item->getValue();
163    }
164
165    return $data;
166  }
167
168  /**
169   * {@inheritdoc}
170   */
171  public function saveSources(): void {
172    $data = $this->getInstance()->getCurrentState();
173    $this->list = [];
174
175    foreach ($data as $offset => $item) {
176      $this->list[$offset] = $this->createItem($offset, $item);
177    }
178    $this->getEntity()->save();
179  }
180
181  /**
182   * {@inheritdoc}
183   */
184  public static function checkAccess(string $instance_id, AccountInterface $account): AccessResultInterface {
185    [, $entity_type_id, $entity_id] = \explode('__', $instance_id);
186
187    $entity = \Drupal::entityTypeManager()->getStorage($entity_type_id)->load($entity_id);
188
189    return $entity->access('update', $account, TRUE);
190  }
191
192  /**
193   * {@inheritdoc}
194   */
195  public function initInstanceIfMissing(): void {
196    /** @var \Drupal\display_builder\InstanceStorage $storage */
197    $storage = $this->entityTypeManager()->getStorage('display_builder_instance');
198
199    /** @var \Drupal\display_builder\InstanceInterface $instance */
200    $instance = $storage->load($this->getInstanceId());
201
202    if (!$instance) {
203      $instance = $storage->createFromImplementation($this);
204      $instance->save();
205    }
206  }
207
208  /**
209   * {@inheritdoc}
210   */
211  public function getInitialSources(): array {
212    $sources = $this->getSources();
213    $entity = $this->getEntity();
214
215    if (\count($sources) === 0) {
216      \assert(\is_string($this->getName()));
217      $display = self::getEntityViewDisplay($entity->getEntityTypeId(), $entity->bundle(), $this->getName());
218
219      if ($display->getProfile() !== NULL) {
220        $sources = $display->getSources();
221      }
222    }
223
224    return $sources;
225  }
226
227  /**
228   * {@inheritdoc}
229   */
230  public function getInitialContext(): array {
231    $entity = $this->getEntity();
232    $bundle = $entity->bundle();
233    \assert(\is_string($this->getName()));
234
235    $view_mode = self::getEntityViewDisplay($entity->getEntityTypeId(), $bundle, $this->getName())->getMode();
236    $contexts = [
237      'entity' => EntityContext::fromEntity($entity),
238      'bundle' => new Context(ContextDefinition::create('string'), $bundle),
239      'view_mode' => new Context(ContextDefinition::create('string'), $view_mode),
240    ];
241
242    return RequirementsContext::addToContext([self::getContextRequirement()], $contexts);
243  }
244
245  /**
246   * Get the entity type manager.
247   *
248   * @return \Drupal\Core\Entity\EntityTypeManagerInterface
249   *   The entity type manager.
250   */
251  protected function entityTypeManager(): EntityTypeManagerInterface {
252    return $this->entityTypeManager ??= \Drupal::service('entity_type.manager');
253  }
254
255  /**
256   * Get entity view display entity.
257   *
258   * @param string $entity_type_id
259   *   Entity type ID.
260   * @param string $bundle
261   *   Entity's bundle which support fields.
262   * @param string $fieldName
263   *   Field name of the display.
264   *
265   * @return \Drupal\display_builder_entity_view\Entity\DisplayBuilderEntityDisplayInterface|null
266   *   The corresponding entity view display.
267   */
268  private static function getEntityViewDisplay(string $entity_type_id, string $bundle, string $fieldName): ?DisplayBuilderEntityDisplayInterface {
269    /** @var \Drupal\display_builder_entity_view\Entity\DisplayBuilderEntityDisplayInterface[] $displays */
270    $displays = \Drupal::entityTypeManager()->getStorage('entity_view_display')->loadByProperties([
271      'targetEntityType' => $entity_type_id,
272    ]);
273
274    foreach ($displays as $display) {
275      if ($display instanceof DisplayBuilderOverridableInterface
276        && $display->getDisplayBuilderOverrideField() === $fieldName
277        && $display->getTargetEntityTypeId()
278        && $display->getTargetBundle() === $bundle
279      ) {
280        return $display;
281      }
282    }
283
284    return NULL;
285  }
286
287  /**
288   * Gets the Display Builder instance.
289   *
290   * @return \Drupal\display_builder\InstanceInterface|null
291   *   A display builder instance.
292   */
293  private function getInstance(): ?InstanceInterface {
294    if (!isset($this->instance)) {
295      /** @var \Drupal\display_builder\InstanceInterface|null $instance */
296      $instance = $this->entityTypeManager()->getStorage('display_builder_instance')->load($this->getInstanceId());
297      $this->instance = $instance;
298    }
299
300    return $this->instance;
301  }
302
303}