Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
18.87% covered (danger)
18.87%
10 / 53
7.41% covered (danger)
7.41%
2 / 27
4.88% covered (danger)
4.88%
2 / 41
40.00% covered (danger)
40.00%
2 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
DisplayBuilderSubscriber
18.87% covered (danger)
18.87%
10 / 53
7.41% covered (danger)
7.41%
2 / 27
4.88% covered (danger)
4.88%
2 / 41
40.00% covered (danger)
40.00%
2 / 5
208.65
0.00% covered (danger)
0.00%
0 / 1
 __construct
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
 getSubscribedEvents
100.00% covered (success)
100.00%
4 / 4
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
 onPublish
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
42
 onRevert
21.74% covered (danger)
21.74%
5 / 23
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 32
0.00% covered (danger)
0.00%
0 / 1
23.26
 getEntityViewDisplayEntity
0.00% covered (danger)
0.00%
0 / 8
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
1<?php
2
3declare(strict_types=1);
4
5namespace Drupal\display_builder_entity_view\EventSubscriber;
6
7use Drupal\Core\Entity\Display\EntityDisplayInterface;
8use Drupal\Core\Entity\EntityTypeManagerInterface;
9use Drupal\Core\Entity\FieldableEntityInterface;
10use Drupal\Core\Plugin\Context\ContextInterface;
11use Drupal\display_builder\DisplayBuildablePluginManager;
12use Drupal\display_builder\Event\DisplayBuilderEvent;
13use Drupal\display_builder\Event\DisplayBuilderEvents;
14use Drupal\display_builder_entity_view\Plugin\display_builder\Buildable\EntityView;
15use Drupal\display_builder_entity_view\Plugin\display_builder\Buildable\EntityViewOverride;
16use Symfony\Component\EventDispatcher\EventSubscriberInterface;
17
18/**
19 * The event subscriber for display builder islands.
20 */
21class DisplayBuilderSubscriber implements EventSubscriberInterface {
22
23  public function __construct(
24    protected EntityTypeManagerInterface $entityTypeManager,
25    protected DisplayBuildablePluginManager $displayBuildableManager,
26  ) {}
27
28  /**
29   * {@inheritdoc}
30   */
31  public static function getSubscribedEvents(): array {
32    return [
33      DisplayBuilderEvents::ON_PUBLISH => 'onPublish',
34      DisplayBuilderEvents::ON_REVERT => 'onRevert',
35    ];
36  }
37
38  /**
39   * Event handler for when a display builder is saved.
40   *
41   * @param \Drupal\display_builder\Event\DisplayBuilderEvent $event
42   *   The event object.
43   */
44  public function onPublish(DisplayBuilderEvent $event): void {
45    $instance = $event->getInstance();
46    $instance_id = (string) $instance->id();
47    $contexts = $instance->getContexts();
48
49    // Entity view display overrides.
50    if ($params = EntityViewOverride::checkInstanceId($instance_id)) {
51      /** @var \Drupal\Core\Entity\FieldableEntityInterface $entity */
52      $entity = $this->entityTypeManager->getStorage($params['entity_type_id'])
53        ->load($params['entity_id']);
54      /** @var \Drupal\Core\Entity\FieldableEntityInterface $override */
55      $override = $entity->get($params['field_name']);
56
57      if ($override) {
58        /** @var \Drupal\display_builder\DisplayBuildableInterface $buildable */
59        $buildable = $this->displayBuildableManager->createInstance('entity_view_override', ['field' => $override]);
60        $buildable->saveSources();
61      }
62    }
63
64    // Entity view displays.
65    elseif (EntityView::checkInstanceId($instance_id)) {
66      if (!$instance->hasSaveContextsRequirement(EntityView::getContextRequirement(), $contexts)) {
67        return;
68      }
69      // Entity view display parameters are also in route match.
70      $display = $this->getEntityViewDisplayEntity($contexts['entity'], $contexts['view_mode']);
71
72      if ($display) {
73        /** @var \Drupal\display_builder\DisplayBuildableInterface $buildable */
74        $buildable = $this->displayBuildableManager->createInstance('entity_view', ['entity' => $display]);
75        $buildable->saveSources();
76      }
77    }
78  }
79
80  /**
81   * Event handler for when a display builder override is reverted.
82   *
83   * Clears the entity field override, then reloads sources from the base
84   * entity view display config so the instance reflects the default layout.
85   *
86   * @param \Drupal\display_builder\Event\DisplayBuilderEvent $event
87   *   The event object.
88   */
89  public function onRevert(DisplayBuilderEvent $event): void {
90    $instance = $event->getInstance();
91    $instance_id = (string) $instance->id();
92    $instanceInfos = EntityViewOverride::checkInstanceId($instance_id);
93
94    if (!isset($instanceInfos['entity_type_id'], $instanceInfos['entity_id'], $instanceInfos['field_name'])) {
95      return;
96    }
97
98    // Do not get the profile entity ID from Instance context because the
99    // data stored there is not reliable yet.
100    // See: https://www.drupal.org/project/display_builder/issues/3544545
101    $entity = $this->entityTypeManager->getStorage($instanceInfos['entity_type_id'])
102      ->load($instanceInfos['entity_id']);
103
104    if (!$entity instanceof FieldableEntityInterface) {
105      return;
106    }
107
108    // Remove the saved state as the field values will be deleted.
109    $instance->setNewPresent([], 'Revert 1/2: clear overridden data and save');
110    $instance->save();
111    $instance->setSave($instance->getCurrentState());
112
113    // Clear field value.
114    $entity->get($instanceInfos['field_name'])->setValue(NULL);
115    $entity->save();
116
117    $contexts = $instance->get('contexts')->first()->getValue();
118
119    if (isset($contexts['view_mode']) && $contexts['view_mode'] instanceof ContextInterface) {
120      $viewMode = $contexts['view_mode']->getContextValue();
121      $display_id = "{$instanceInfos['entity_type_id']}.{$entity->bundle()}.{$viewMode}";
122
123      /** @var \Drupal\display_builder\DisplayBuildableInterface|null $display */
124      $display = $this->entityTypeManager->getStorage('entity_view_display')
125        ->load($display_id);
126
127      if ($display) {
128        $instance->setNewPresent($display->getSources(), 'Revert 2/2: retrieve existing data from config');
129        $instance->save();
130      }
131    }
132  }
133
134  /**
135   * Get entity view display entity.
136   *
137   * @param \Drupal\Core\Plugin\Context\ContextInterface $entity_context
138   *   The entity context.
139   * @param \Drupal\Core\Plugin\Context\ContextInterface $view_mode_context
140   *   The view mode context.
141   *
142   * @return \Drupal\Core\Entity\Display\EntityDisplayInterface|null
143   *   The entity view display entity or NULL if not found.
144   */
145  protected function getEntityViewDisplayEntity(ContextInterface $entity_context, ContextInterface $view_mode_context): ?EntityDisplayInterface {
146    /** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
147    $entity = $entity_context->getContextValue();
148    $entity_type_id = $entity->getEntityTypeId();
149    $bundle = $entity->bundle();
150    $view_mode = $view_mode_context->getContextValue();
151    $display_id = "{$entity_type_id}.{$bundle}.{$view_mode}";
152
153    /** @var \Drupal\Core\Entity\Display\EntityDisplayInterface|null $display */
154    $display = $this->entityTypeManager->getStorage('entity_view_display')
155      ->load($display_id);
156
157    return $display;
158  }
159
160}