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