Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
60.48% covered (warning)
60.48%
101 / 167
56.12% covered (warning)
56.12%
55 / 98
39.74% covered (danger)
39.74%
31 / 78
56.67% covered (warning)
56.67%
17 / 30
CRAP
0.00% covered (danger)
0.00%
0 / 1
EntityViewDisplayTrait
60.48% covered (warning)
60.48%
101 / 167
56.12% covered (warning)
56.12%
55 / 98
39.74% covered (danger)
39.74%
31 / 78
56.67% covered (warning)
56.67%
17 / 30
960.13
0.00% covered (danger)
0.00%
0 / 1
 calculateDependencies
92.31% covered (success)
92.31%
12 / 13
90.00% covered (success)
90.00%
9 / 10
50.00% covered (danger)
50.00%
3 / 6
0.00% covered (danger)
0.00%
0 / 1
8.12
 isDisplayBuilderEnabled
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 onDependencyRemoval
91.67% covered (success)
91.67%
11 / 12
85.71% covered (warning)
85.71%
6 / 7
20.00% covered (danger)
20.00%
1 / 5
0.00% covered (danger)
0.00%
0 / 1
12.19
 getPrefix
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getContextRequirement
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 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
 getBuilderUrl
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 getUrlFromInstanceId
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDisplayUrlFromInstanceId
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
 getDisplayBuilderOverrideField
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDisplayBuilderOverrideProfile
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 isDisplayBuilderOverridable
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 getProfile
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 getInstanceId
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 checkAccess
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 initInstanceIfMissing
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 getInitialSources
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
3 / 3
50.00% covered (danger)
50.00%
1 / 2
100.00% covered (success)
100.00%
1 / 1
2.50
 getInitialContext
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getSources
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 saveSources
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 postSave
55.56% covered (warning)
55.56%
10 / 18
53.85% covered (warning)
53.85%
7 / 13
16.67% covered (danger)
16.67%
3 / 18
0.00% covered (danger)
0.00%
0 / 1
35.36
 delete
0.00% covered (danger)
0.00%
0 / 3
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
 buildMultiple
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
30
 isOverrideOfCurrentDisplay
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
20
 getContextsForEntity
0.00% covered (danger)
0.00%
0 / 6
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
 contextRepository
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
 getInstance
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 loadDisplayBuilder
80.00% covered (warning)
80.00%
4 / 5
66.67% covered (warning)
66.67%
2 / 3
50.00% covered (danger)
50.00%
1 / 2
0.00% covered (danger)
0.00%
0 / 1
2.50
 buildSources
0.00% covered (danger)
0.00%
0 / 9
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
 getUrlParamsFromInstanceId
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace Drupal\display_builder_entity_view\Entity;
6
7use Drupal\Core\Access\AccessResult;
8use Drupal\Core\Access\AccessResultInterface;
9use Drupal\Core\Cache\CacheableMetadata;
10use Drupal\Core\Entity\EntityStorageInterface;
11use Drupal\Core\Entity\FieldableEntityInterface;
12use Drupal\Core\Plugin\Context\Context;
13use Drupal\Core\Plugin\Context\ContextDefinition;
14use Drupal\Core\Plugin\Context\ContextRepositoryInterface;
15use Drupal\Core\Plugin\Context\EntityContext;
16use Drupal\Core\Session\AccountInterface;
17use Drupal\Core\Url;
18use Drupal\display_builder\ConfigFormBuilderInterface;
19use Drupal\display_builder\DisplayBuildableInterface;
20use Drupal\display_builder\InstanceInterface;
21use Drupal\display_builder\ProfileInterface;
22use Drupal\display_builder_entity_view\Field\DisplayBuilderItemList;
23use Drupal\ui_patterns\Plugin\Context\RequirementsContext;
24
25/**
26 * Common methods for entity view display.
27 */
28trait EntityViewDisplayTrait {
29
30  /**
31   * Calculates dependencies for the display builder.
32   *
33   * @return $this
34   *   The current instance.
35   *
36   * @see \Drupal\Core\Entity\Display\EntityViewDisplayInterface
37   */
38  public function calculateDependencies(): self {
39    parent::calculateDependencies();
40
41    if (!$this->getInstanceId()) {
42      // If there is no instance ID, we cannot calculate dependencies.
43      return $this;
44    }
45
46    /** @var \Drupal\display_builder\InstanceInterface $instance */
47    $instance = $this->entityTypeManager->getStorage('display_builder_instance')->load($this->getInstanceId());
48
49    if (!$instance) {
50      return $this;
51    }
52    $contexts = $instance->getContexts();
53
54    if (!$contexts) {
55      return $this;
56    }
57
58    foreach ($this->getSources() as $source_data) {
59      /** @var \Drupal\ui_patterns\SourceInterface $source */
60      $source = $this->sourcePluginManager->getSource('', [], $source_data, $contexts);
61      $this->addDependencies($source->calculateDependencies());
62    }
63
64    return $this;
65  }
66
67  /**
68   * Is display builder enabled?
69   *
70   * @return bool
71   *   The display builder is enabled if there is a Display Builder entity.
72   *
73   * @see \Drupal\display_builder_entity_view\DisplayBuilderEnabledInterface
74   */
75  public function isDisplayBuilderEnabled(): bool {
76    // Display Builder must not be enabled for the '_custom' view mode that is
77    // used for on-the-fly rendering of fields in isolation from the entity.
78    if ($this->getOriginalMode() === static::CUSTOM_MODE) {
79      return FALSE;
80    }
81
82    return (bool) $this->getProfile();
83  }
84
85  /**
86   * Handler for when dependencies are removed.
87   *
88   * @param array $dependencies
89   *   The dependencies that were removed.
90   *
91   * @return bool
92   *   TRUE if the display can be overridden, FALSE otherwise.
93   *
94   * @see \Drupal\Core\Entity\Display\EntityViewDisplayInterface
95   */
96  public function onDependencyRemoval(array $dependencies): bool {
97    $changed = parent::onDependencyRemoval($dependencies);
98
99    // Loop through all sources and determine if the removed dependencies are
100    // used by their plugins.
101    /** @var \Drupal\display_builder\InstanceInterface $instance */
102    $instance = $this->getInstance();
103
104    // @todo not working when content entity type is deleted.
105    if (!$instance) {
106      return TRUE;
107    }
108
109    $contexts = $instance->getContexts();
110
111    foreach ($this->getSources() as $source_data) {
112      /** @var \Drupal\ui_patterns\SourceInterface $source */
113      $source = $this->sourcePluginManager->getSource('', [], $source_data, $contexts);
114      $source_dependencies = $source->calculateDependencies();
115      $source_removed_dependencies = $this->getPluginRemovedDependencies($source_dependencies, $dependencies);
116
117      if ($source_removed_dependencies) {
118        // @todo Allow the plugins to react to their dependency removal in
119        // https://www.drupal.org/project/drupal/issues/2579743.
120        // $this->removeSource($delta);
121        $changed = TRUE;
122      }
123    }
124
125    return $changed;
126  }
127
128  /**
129   * {@inheritdoc}
130   */
131  public static function getPrefix(): string {
132    return 'entity_view__';
133  }
134
135  /**
136   * Returns the context requirement for this entity view display.
137   *
138   * This is used to ensure that the entity context is available when building
139   * the display builder.
140   *
141   * @return string
142   *   The context requirement string.
143   *
144   * @see \Drupal\display_builder\DisplayBuildableInterface
145   */
146  public static function getContextRequirement(): string {
147    return 'entity';
148  }
149
150  /**
151   * {@inheritdoc}
152   *
153   * @see \Drupal\display_builder\DisplayBuildableInterface
154   */
155  public static function checkInstanceId(string $instance_id): ?array {
156    if (!\str_starts_with($instance_id, EntityViewDisplay::getPrefix())) {
157      return NULL;
158    }
159    [, $entity, $bundle, $view_mode] = \explode('__', $instance_id);
160
161    return [
162      'entity' => $entity,
163      'bundle' => $bundle,
164      'view_mode' => $view_mode,
165    ];
166  }
167
168  /**
169   * {@inheritdoc}
170   */
171  public function getBuilderUrl(): Url {
172    $fieldable_entity_type = $this->entityTypeManager->getDefinition($this->getTargetEntityTypeId());
173    $bundle_parameter_key = $fieldable_entity_type->getBundleEntityType() ?: 'bundle';
174    $parameters = [
175      $bundle_parameter_key => $this->getTargetBundle(),
176      'view_mode_name' => $this->getMode(),
177    ];
178    $route_name = \sprintf('display_builder_entity_view.%s', $this->getTargetEntityTypeId());
179
180    return Url::fromRoute($route_name, $parameters);
181  }
182
183  /**
184   * Returns the URL for the display builder from an instance id.
185   *
186   * @param string $instance_id
187   *   The builder instance ID.
188   *
189   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
190   *
191   * @return \Drupal\Core\Url
192   *   The url of the instance.
193   *
194   * @see \Drupal\display_builder\DisplayBuildableInterface
195   */
196  public static function getUrlFromInstanceId(string $instance_id): Url {
197    $params = self::getUrlParamsFromInstanceId($instance_id);
198    $route_name = \sprintf('display_builder_entity_view.%s', $params['entity']);
199
200    return Url::fromRoute($route_name, $params);
201  }
202
203  /**
204   * Returns the URL for the display builder from an instance id.
205   *
206   * @param string $instance_id
207   *   The builder instance ID.
208   *
209   * @return \Drupal\Core\Url
210   *   The url of the instance.
211   *
212   * @see Drupal\display_builder\DisplayBuildableInterface
213   */
214  public static function getDisplayUrlFromInstanceId(string $instance_id): Url {
215    $params = self::getUrlParamsFromInstanceId($instance_id);
216    $route_name = \sprintf('entity.entity_view_display.%s.view_mode', $params['entity']);
217
218    return Url::fromRoute($route_name, $params);
219  }
220
221  /**
222   * Returns the field name used to store overridden displays.
223   *
224   * @return string|null
225   *   The field name used to store overridden displays, or NULL if not set.
226   *
227   * @see \Drupal\display_builder_entity_view\Entity\DisplayBuilderOverridableInterface
228   */
229  public function getDisplayBuilderOverrideField(): ?string {
230    return $this->getThirdPartySetting('display_builder', ConfigFormBuilderInterface::OVERRIDE_FIELD_PROPERTY);
231  }
232
233  /**
234   * Returns the display builder override profile.
235   *
236   * @return \Drupal\display_builder\ProfileInterface|null
237   *   The display builder override profile, or NULL if not set.
238   *
239   * @see \Drupal\display_builder_entity_view\Entity\DisplayBuilderOverridableInterface
240   */
241  public function getDisplayBuilderOverrideProfile(): ?ProfileInterface {
242    $display_builder_id = $this->getThirdPartySetting('display_builder', ConfigFormBuilderInterface::OVERRIDE_PROFILE_PROPERTY);
243
244    if ($display_builder_id === NULL) {
245      return NULL;
246    }
247
248    return $this->loadDisplayBuilder($display_builder_id);
249  }
250
251  /**
252   * Returns TRUE if the display can be overridden.
253   *
254   * @return bool
255   *   TRUE if the display can be overridden, FALSE otherwise.
256   *
257   * @see \Drupal\display_builder_entity_view\Entity\DisplayBuilderOverridableInterface
258   */
259  public function isDisplayBuilderOverridable(): bool {
260    return !empty($this->getDisplayBuilderOverrideField())
261      && $this->getDisplayBuilderOverrideProfile() !== NULL;
262  }
263
264  /**
265   * Returns the display builder instance.
266   *
267   * @return \Drupal\display_builder\ProfileInterface|null
268   *   The display builder instance, or NULL if not set.
269   *
270   * @see \Drupal\display_builder\DisplayBuildableInterface
271   */
272  public function getProfile(): ?ProfileInterface {
273    $display_builder_id = $this->getThirdPartySetting('display_builder', ConfigFormBuilderInterface::PROFILE_PROPERTY);
274
275    if ($display_builder_id === NULL) {
276      return NULL;
277    }
278
279    return $this->loadDisplayBuilder($display_builder_id);
280  }
281
282  /**
283   * Returns the instance ID for the display builder.
284   *
285   * @return string|null
286   *   The instance ID for the display builder, or NULL if the entity is new.
287   *
288   * @see \Drupal\display_builder\DisplayBuildableInterface
289   */
290  public function getInstanceId(): ?string {
291    // Usually an entity is new if no ID exists for it yet.
292    if ($this->isNew()) {
293      return NULL;
294    }
295
296    return \sprintf('%s%s', EntityViewDisplay::getPrefix(), \str_replace('.', '__', $this->id));
297  }
298
299  /**
300   * Checks access.
301   *
302   * @param string $instance_id
303   *   Instance entity ID.
304   * @param \Drupal\Core\Session\AccountInterface $account
305   *   The user session for which to check access.
306   *
307   * @return \Drupal\Core\Access\AccessResultInterface
308   *   The access result.
309   *
310   * @see \Drupal\display_builder\InstanceAccessControlHandler
311   */
312  public static function checkAccess(string $instance_id, AccountInterface $account): AccessResultInterface {
313    $params = self::getUrlParamsFromInstanceId($instance_id);
314    $permission = 'administer ' . $params['entity'] . ' display';
315
316    return $account->hasPermission($permission) ? AccessResult::allowed() : AccessResult::forbidden();
317  }
318
319  /**
320   * Initializes the display builder instance if it is missing.
321   *
322   * @see \Drupal\display_builder\DisplayBuildableInterface
323   */
324  public function initInstanceIfMissing(): void {
325    /** @var \Drupal\display_builder\InstanceStorage $storage */
326    $storage = $this->entityTypeManager->getStorage('display_builder_instance');
327
328    /** @var \Drupal\display_builder\InstanceInterface $instance */
329    $instance = $storage->load($this->getInstanceId());
330
331    if (!$instance) {
332      $instance = $storage->createFromImplementation($this);
333      $instance->save();
334    }
335  }
336
337  /**
338   * {@inheritdoc}
339   */
340  public function getInitialSources(): array {
341    // Get the sources stored in config.
342    $sources = $this->getSources();
343
344    if (empty($sources)) {
345      // initialImport() has two implementations:
346      // - EntityViewDisplay::initialImport()
347      // - LayoutBuilderEntityViewDisplay::initialImport()
348      $sources = $this->initialImport();
349    }
350
351    return $sources;
352  }
353
354  /**
355   * {@inheritdoc}
356   */
357  public function getInitialContext(): array {
358    $entity_type_id = $this->getTargetEntityTypeId();
359    $bundle = $this->getTargetBundle();
360    $view_mode = $this->getMode();
361    $sampleEntity = $this->sampleEntityGenerator->get($entity_type_id, $bundle);
362    $contexts = [
363      'entity' => EntityContext::fromEntity($sampleEntity),
364      'bundle' => new Context(ContextDefinition::create('string'), $bundle),
365      'view_mode' => new Context(ContextDefinition::create('string'), $view_mode),
366    ];
367
368    return RequirementsContext::addToContext([self::getContextRequirement()], $contexts);
369  }
370
371  /**
372   * Returns the sources of the display builder.
373   *
374   * @return array
375   *   The sources of the display builder.
376   *
377   * @see \Drupal\display_builder\DisplayBuildableInterface
378   */
379  public function getSources(): array {
380    return $this->getThirdPartySetting('display_builder', ConfigFormBuilderInterface::SOURCES_PROPERTY, []);
381  }
382
383  /**
384   * Saves the sources of the display builder.
385   *
386   * @see \Drupal\display_builder\DisplayBuildableInterface
387   */
388  public function saveSources(): void {
389    $data = $this->getInstance()->getCurrentState();
390    $this->setThirdPartySetting('display_builder', ConfigFormBuilderInterface::SOURCES_PROPERTY, $data);
391    $this->save();
392  }
393
394  /**
395   * Post-save operations for the display builder.
396   *
397   * @param \Drupal\Core\Entity\EntityStorageInterface $storage
398   *   The entity storage.
399   * @param bool $update
400   *   Whether the entity is being updated.
401   *
402   * @see \Drupal\Core\Entity\Display\EntityViewDisplayInterface
403   */
404  public function postSave(EntityStorageInterface $storage, $update = TRUE): void {
405    if ($profile = $this->getProfile()) {
406      $this->initInstanceIfMissing();
407
408      // Save the profile in the instance if changed.
409      $instance = $this->getInstance();
410      $profile_id = (string) $profile->id();
411
412      if ($instance->getProfile()->id() !== $profile_id) {
413        $instance->setProfile($profile_id);
414        $instance->save();
415      }
416    }
417
418    // Do also overrides.
419    if ($profile = $this->getDisplayBuilderOverrideProfile()) {
420      $profile_id = (string) $profile->id();
421      $storage = $this->entityTypeManager->getStorage('display_builder_instance');
422
423      foreach ($storage->loadMultiple() as $override) {
424        /** @var \Drupal\display_builder\InstanceInterface $override */
425        if (!$this->isOverrideOfCurrentDisplay($override)) {
426          continue;
427        }
428
429        if ($override->getProfile()->id() === $profile_id) {
430          continue;
431        }
432        $override->setProfile($profile_id);
433        $override->save();
434      }
435    }
436
437    parent::postSave($storage, $update);
438  }
439
440  /**
441   * Deletes the display builder instance if it exists.
442   *
443   * @see \Drupal\Core\Entity\Display\EntityViewDisplayInterface
444   */
445  public function delete(): void {
446    if ($instance = $this->getInstance()) {
447      $instance->delete();
448    }
449    parent::delete();
450  }
451
452  /**
453   * Builds a renderable array for the components of a set of entities.
454   *
455   * @param \Drupal\Core\Entity\FieldableEntityInterface[] $entities
456   *   The entities being displayed.
457   *
458   * @return array
459   *   A renderable array for the entities, indexed by the same keys as the
460   *   $entities array parameter.
461   *
462   * @see \Drupal\Core\Entity\Display\EntityViewDisplayInterface
463   */
464  public function buildMultiple(array $entities): array {
465    $build_list = parent::buildMultiple($entities);
466
467    // If no display builder enabled, stop here and return:
468    // - 'Manage Display' build if this trait is used in EntityViewDisplay
469    // - 'Layout Builder' build if used in LayoutBuilderEntityViewDisplay.
470    if (!$this->isDisplayBuilderEnabled()) {
471      // This is also preventing the availability of Display Builder overrides
472      // when Display Builder is not used for the entity view display.
473      // @todo Is it something we want to keep like that?
474      // @see https://www.drupal.org/project/display_builder/issues/3540048
475      return $build_list;
476    }
477
478    foreach ($entities as $id => $entity) {
479      $sources = [];
480
481      if ($this->isDisplayBuilderOverridable()) {
482        $display_builder_field = $this->getDisplayBuilderOverrideField();
483        $overridden_field = $entity->get($display_builder_field);
484        \assert($overridden_field instanceof DisplayBuildableInterface);
485        $sources = $overridden_field->getSources();
486      }
487
488      // If the overridden field is empty fallback to the entity view.
489      if (\count($sources) === 0) {
490        $sources = $this->getSources();
491      }
492
493      // @see entity.html.twig
494      $build_list[$id]['content'] = $this->buildSources($entity, $sources);
495    }
496
497    return $build_list;
498  }
499
500  /**
501   * Chef if the instance is overriding this display.
502   *
503   * @param \Drupal\display_builder\InstanceInterface $instance
504   *   A list of display builder instances.
505   *
506   * @return bool
507   *   Is the instance overriding this display?
508   */
509  protected function isOverrideOfCurrentDisplay(InstanceInterface $instance): bool {
510    $parts = DisplayBuilderItemList::checkInstanceId((string) $instance->id());
511
512    if (!$parts) {
513      return FALSE;
514    }
515
516    if ($parts['entity_type_id'] !== $this->getTargetEntityTypeId()) {
517      return FALSE;
518    }
519
520    if ($parts['field_name'] !== $this->getDisplayBuilderOverrideField()) {
521      return FALSE;
522    }
523
524    return TRUE;
525  }
526
527  /**
528   * Gets the available contexts for a given entity.
529   *
530   * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
531   *   The entity.
532   *
533   * @return \Drupal\Core\Plugin\Context\ContextInterface[]
534   *   An array of context objects for a given entity.
535   */
536  protected function getContextsForEntity(FieldableEntityInterface $entity): array {
537    $available_context_ids = \array_keys($this->contextRepository()->getAvailableContexts());
538
539    return [
540      'view_mode' => new Context(ContextDefinition::create('string'), $this->getMode()),
541      'entity' => EntityContext::fromEntity($entity),
542      'display' => EntityContext::fromEntity($this),
543    ] + $this->contextRepository()->getRuntimeContexts($available_context_ids);
544  }
545
546  /**
547   * Wraps the context repository service.
548   *
549   * @return \Drupal\Core\Plugin\Context\ContextRepositoryInterface
550   *   The context repository service.
551   */
552  protected function contextRepository(): ContextRepositoryInterface {
553    return \Drupal::service('context.repository');
554  }
555
556  /**
557   * Gets the Display Builder instance.
558   *
559   * @return \Drupal\display_builder\InstanceInterface|null
560   *   A display builder instance.
561   */
562  protected function getInstance(): ?InstanceInterface {
563    /** @var \Drupal\display_builder\InstanceInterface|null $instance */
564    $instance = $this->entityTypeManager->getStorage('display_builder_instance')->load($this->getInstanceId());
565    $this->instance = $instance;
566
567    return $this->instance;
568  }
569
570  /**
571   * Loads display builder by id.
572   *
573   * @param string $display_builder_id
574   *   The display builder ID.
575   *
576   * @return \Drupal\display_builder\ProfileInterface|null
577   *   The display builder, or NULL if not found.
578   */
579  private function loadDisplayBuilder(string $display_builder_id): ?ProfileInterface {
580    if (empty($display_builder_id)) {
581      return NULL;
582    }
583    $storage = $this->entityTypeManager->getStorage('display_builder_profile');
584
585    /** @var \Drupal\display_builder\ProfileInterface $display_builder */
586    $display_builder = $storage->load($display_builder_id);
587
588    return $display_builder;
589  }
590
591  /**
592   * Builds the render array for the sources of a given entity.
593   *
594   * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
595   *   The entity.
596   * @param array $sources
597   *   The sources to build.
598   *
599   * @return array
600   *   The render array representing the sources of the entity.
601   */
602  private function buildSources(FieldableEntityInterface $entity, array $sources): array {
603    $contexts = $this->getContextsForEntity($entity);
604    $cacheability = new CacheableMetadata();
605    $fake_build = [];
606
607    foreach ($sources as $source_data) {
608      $fake_build = $this->componentElementBuilder->buildSource($fake_build, 'content', [], $source_data, $contexts);
609    }
610    $build = $fake_build['#slots']['content'] ?? [];
611    $build['#cache'] = $fake_build['#cache'] ?? [];
612    // The render array is built based on decisions made by SourceStorage
613    // plugins, and therefore it needs to depend on the accumulated
614    // cacheability of those decisions.
615    $cacheability->applyTo($build);
616
617    return $build;
618  }
619
620  /**
621   * Returns the URL for the display builder from an instance id.
622   *
623   * @param string $instance_id
624   *   The builder instance ID.
625   *
626   * @return array
627   *   The url parameters for this instance id.
628   */
629  private static function getUrlParamsFromInstanceId(string $instance_id): array {
630    [, $entity, $bundle, $view_mode] = \explode('__', $instance_id);
631    $fieldable_entity_type = \Drupal::service('entity_type.manager')->getDefinition($entity);
632    $bundle_parameter_key = $fieldable_entity_type->getBundleEntityType() ?: 'bundle';
633
634    return [
635      $bundle_parameter_key => $bundle,
636      'view_mode_name' => $view_mode,
637      'entity' => $entity,
638    ];
639  }
640
641}