Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 99
0.00% covered (danger)
0.00%
0 / 43
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 15
CRAP
0.00% covered (danger)
0.00%
0 / 1
EntityView
0.00% covered (danger)
0.00%
0 / 95
0.00% covered (danger)
0.00%
0 / 43
0.00% covered (danger)
0.00%
0 / 31
0.00% covered (danger)
0.00%
0 / 15
930
0.00% covered (danger)
0.00%
0 / 1
 __construct
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
 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
 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
 getBuilderUrl
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
6
 getUrlFromInstanceId
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
 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
 getProfile
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
 getSources
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
 saveSources
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
 getInstanceId
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
 collectInstances
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 getRuntimeContexts
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
 getInitialSources
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
30
 getInitializationMessage
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 getUrlParamsFromInstanceId
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
6
1<?php
2
3declare(strict_types=1);
4
5namespace Drupal\display_builder_entity_view\Plugin\display_builder\Buildable;
6
7use Drupal\Core\Access\AccessResult;
8use Drupal\Core\Access\AccessResultInterface;
9use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
10use Drupal\Core\Entity\Entity\EntityViewDisplay;
11use Drupal\Core\Plugin\Context\Context;
12use Drupal\Core\Plugin\Context\ContextDefinition;
13use Drupal\Core\Plugin\Context\EntityContext;
14use Drupal\Core\Session\AccountInterface;
15use Drupal\Core\StringTranslation\TranslatableMarkup;
16use Drupal\Core\Url;
17use Drupal\display_builder\Attribute\DisplayBuildable;
18use Drupal\display_builder\DisplayBuildableInterface;
19use Drupal\display_builder\DisplayBuildablePluginBase;
20use Drupal\display_builder\Entity\ProfileInterface;
21use Drupal\display_builder_entity_view\BuilderDataConverter;
22use Drupal\display_builder_entity_view\Entity\LayoutBuilderEntityViewDisplay;
23use Drupal\ui_patterns\Entity\SampleEntityGeneratorInterface;
24use Drupal\ui_patterns\Plugin\Context\RequirementsContext;
25
26/**
27 * Plugin implementation of the display_buildable.
28 */
29#[DisplayBuildable(
30  id: 'entity_view',
31  label: new TranslatableMarkup('Entity view'),
32  instance_prefix: 'entity_view__',
33)]
34final class EntityView extends DisplayBuildablePluginBase {
35
36  /**
37   * The sample entity generator.
38   */
39  protected SampleEntityGeneratorInterface $sampleEntityGenerator;
40
41  /**
42   * The data converter from Manage Display and Layout Builder.
43   */
44  protected BuilderDataConverter $dataConverter;
45
46  /**
47   * The display entity.
48   */
49  protected ?EntityViewDisplayInterface $entity;
50
51  /**
52   * {@inheritdoc}
53   */
54  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
55    parent::__construct($configuration, $plugin_id, $plugin_definition);
56    $this->sampleEntityGenerator = \Drupal::service('ui_patterns.sample_entity_generator');
57    $this->dataConverter = \Drupal::service('display_builder_entity_view.builder_data_converter');
58
59    if (isset($configuration['display'])) {
60      $this->entity = $configuration['display'];
61      unset($this->configuration['display']);
62      $this->configuration['display_id'] = $this->entity->id();
63
64      return;
65    }
66
67    // Configuration (as stored in Instance entity):
68    // - display_id (string)
69    // No dependency injection in plugin constructors.
70    $this->entity = EntityViewDisplay::load($configuration['display_id'] ?? '');
71  }
72
73  /**
74   * {@inheritdoc}
75   */
76  public static function checkInstanceId(string $instance_id): ?array {
77    if (!\str_starts_with($instance_id, self::getPrefix())) {
78      return NULL;
79    }
80    [, $entity, $bundle, $view_mode] = \explode('__', $instance_id);
81
82    return [
83      'entity' => $entity,
84      'bundle' => $bundle,
85      'view_mode' => $view_mode,
86    ];
87  }
88
89  /**
90   * {@inheritdoc}
91   */
92  public static function checkAccess(string $instance_id, AccountInterface $account): AccessResultInterface {
93    $params = self::getUrlParamsFromInstanceId($instance_id);
94    $permission = 'administer ' . $params['entity'] . ' display';
95
96    return $account->hasPermission($permission) ? AccessResult::allowed() : AccessResult::forbidden();
97  }
98
99  /**
100   * {@inheritdoc}
101   */
102  public function getBuilderUrl(): Url {
103    $fieldable_entity_type = $this->entityTypeManager->getDefinition($this->entity->getTargetEntityTypeId());
104    $bundle_parameter_key = $fieldable_entity_type->getBundleEntityType() ?: 'bundle';
105    $parameters = [
106      $bundle_parameter_key => $this->entity->getTargetBundle(),
107      'view_mode_name' => $this->entity->getMode(),
108    ];
109    $route_name = \sprintf('display_builder_entity_view.%s', $this->entity->getTargetEntityTypeId());
110
111    return Url::fromRoute($route_name, $parameters);
112  }
113
114  /**
115   * {@inheritdoc}
116   */
117  public static function getUrlFromInstanceId(string $instance_id): Url {
118    $params = self::getUrlParamsFromInstanceId($instance_id);
119    $route_name = \sprintf('display_builder_entity_view.%s', $params['entity']);
120
121    return Url::fromRoute($route_name, $params);
122  }
123
124  /**
125   * {@inheritdoc}
126   */
127  public static function getDisplayUrlFromInstanceId(string $instance_id): Url {
128    $params = self::getUrlParamsFromInstanceId($instance_id);
129    $route_name = \sprintf('entity.entity_view_display.%s.view_mode', $params['entity']);
130
131    return Url::fromRoute($route_name, $params);
132  }
133
134  /**
135   * {@inheritdoc}
136   */
137  public function getProfile(): ?ProfileInterface {
138    $profile_id = $this->entity->getThirdPartySetting('display_builder', DisplayBuildableInterface::PROFILE_PROPERTY);
139
140    if ($profile_id === NULL) {
141      return NULL;
142    }
143
144    /** @var \Drupal\display_builder\Entity\ProfileInterface $profile */
145    $profile = $this->entityTypeManager->getStorage('display_builder_profile')->load($profile_id);
146
147    return $profile;
148  }
149
150  /**
151   * {@inheritdoc}
152   */
153  public function getSources(): array {
154    return $this->entity->getThirdPartySetting('display_builder', DisplayBuildableInterface::SOURCES_PROPERTY, []);
155  }
156
157  /**
158   * {@inheritdoc}
159   */
160  public function saveSources(): void {
161    $data = $this->getInstance()->getCurrentState();
162    $this->entity->setThirdPartySetting('display_builder', DisplayBuildableInterface::SOURCES_PROPERTY, $data);
163    $this->entity->save();
164  }
165
166  /**
167   * {@inheritdoc}
168   */
169  public function getInstanceId(): ?string {
170    // Usually an entity is new if no ID exists for it yet.
171    if ($this->entity->isNew()) {
172      return NULL;
173    }
174
175    return \sprintf('%s%s', self::getPrefix(), \str_replace('.', '__', (string) $this->entity->id()));
176  }
177
178  /**
179   * {@inheritdoc}
180   */
181  public static function collectInstances(): array {
182    $entityTypeManager = \Drupal::service('entity_type.manager');
183    $displayBuildableManager = \Drupal::service('plugin.manager.display_buildable');
184
185    $instances = [];
186    $storage = $entityTypeManager->getStorage('entity_view_display');
187
188    foreach ($storage->loadMultiple() as $display) {
189      /** @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display */
190      $display_builder = $display->getThirdPartySettings('display_builder');
191
192      if (!empty($display_builder[DisplayBuildableInterface::PROFILE_PROPERTY] ?? NULL)) {
193        /** @var \Drupal\display_builder\DisplayBuildableInterface $buildable */
194        $buildable = $displayBuildableManager->createInstance(
195          'entity_view',
196          ['display' => $display]
197        );
198        $buildable->initInstanceIfMissing();
199        $instance = $buildable->getInstance();
200        $instances[(string) $instance->id()] = $instance;
201      }
202    }
203
204    return $instances;
205  }
206
207  /**
208   * {@inheritdoc}
209   */
210  public function getRuntimeContexts(array $unqualified_context_ids): array {
211    // Contexts not needed by Display Builder but expected by UiPatterns source
212    // plugins.
213    $contexts = [];
214    $entity_type_id = $this->entity->getTargetEntityTypeId();
215    $bundle = $this->entity->getTargetBundle();
216    $sampleEntity = $this->sampleEntityGenerator->get($entity_type_id, $bundle);
217    $contexts['entity'] = EntityContext::fromEntity($sampleEntity);
218    $contexts['view_mode'] = new Context(ContextDefinition::create('string'), $this->entity->getMode());
219    $contexts['bundle'] = new Context(ContextDefinition::create('string'), $bundle);
220    $contexts = RequirementsContext::addToContext(['entity'], $contexts);
221
222    return $contexts;
223  }
224
225  /**
226   * {@inheritdoc}
227   */
228  protected function getInitialSources(): array {
229    // Get the sources stored in config.
230    $sources = $this->getSources();
231
232    if (empty($sources)) {
233      // initialImport() has two implementations:
234      // - EntityViewDisplay::initialImport()
235      // - LayoutBuilderEntityViewDisplay::initialImport()
236      /** @var \Drupal\display_builder_entity_view\Entity\DisplayBuilderEntityDisplayInterface $display */
237      $display = $this->entity;
238
239      if ($display instanceof LayoutBuilderEntityViewDisplay && $display->getThirdPartySetting('layout_builder', 'enabled')) {
240        $sections = $display->getThirdPartySetting('layout_builder', 'sections', []);
241
242        if (!\is_array($sections)) {
243          $sections = [];
244        }
245
246        $sources = $this->dataConverter->convertFromLayoutBuilder($sections);
247        $this->initialDataSource = 'layout_builder';
248      }
249      else {
250        $sources = $this->dataConverter->convertFromManageDisplay($display->getTargetEntityTypeId(), $display->getTargetBundle(), $display->getComponents());
251        $this->initialDataSource = 'manage_display';
252      }
253    }
254
255    return $sources;
256  }
257
258  /**
259   * {@inheritdoc}
260   */
261  protected function getInitializationMessage(): TranslatableMarkup {
262    if ($this->initialDataSource === 'layout_builder') {
263      return $this->t('Import display from Layout Builder configuration');
264    }
265
266    if ($this->initialDataSource === 'manage_display') {
267      return $this->t('Import display from Manage Display configuration');
268    }
269
270    return $this->t('Initialize display from existing Entity View Display configuration');
271  }
272
273  /**
274   * Returns the URL for the display builder from an instance id.
275   *
276   * @param string $instance_id
277   *   The builder instance ID.
278   *
279   * @return array
280   *   The url parameters for this instance id.
281   */
282  private static function getUrlParamsFromInstanceId(string $instance_id): array {
283    [, $entity, $bundle, $view_mode] = \explode('__', $instance_id);
284    $fieldable_entity_type = \Drupal::service('entity_type.manager')->getDefinition($entity);
285    $bundle_parameter_key = $fieldable_entity_type->getBundleEntityType() ?: 'bundle';
286
287    return [
288      $bundle_parameter_key => $bundle,
289      'view_mode_name' => $view_mode,
290      'entity' => $entity,
291    ];
292  }
293
294}