Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 70
0.00% covered (danger)
0.00%
0 / 40
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 15
CRAP
0.00% covered (danger)
0.00%
0 / 1
ViewDisplay
0.00% covered (danger)
0.00%
0 / 66
0.00% covered (danger)
0.00%
0 / 40
0.00% covered (danger)
0.00%
0 / 28
0.00% covered (danger)
0.00%
0 / 15
702
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
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 / 5
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 / 7
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 / 1
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
 getUrlFromInstanceId
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
 getDisplayUrlFromInstanceId
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
 getProfile
0.00% covered (danger)
0.00%
0 / 8
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
 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 / 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
 getInstanceId
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
 collectInstances
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
 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
 getInitialSources
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
 getInitialContext
0.00% covered (danger)
0.00%
0 / 4
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_views\Plugin\display_builder\Buildable;
6
7use Drupal\Core\Access\AccessResult;
8use Drupal\Core\Access\AccessResultInterface;
9use Drupal\Core\Entity\EntityTypeManagerInterface;
10use Drupal\Core\Plugin\Context\EntityContext;
11use Drupal\Core\Session\AccountInterface;
12use Drupal\Core\StringTranslation\TranslatableMarkup;
13use Drupal\Core\Url;
14use Drupal\display_builder\Attribute\DisplayBuildable;
15use Drupal\display_builder\DisplayBuildableInterface;
16use Drupal\display_builder\DisplayBuildablePluginBase;
17use Drupal\display_builder\DisplayBuilderHelpers;
18use Drupal\display_builder\ProfileInterface;
19use Drupal\ui_patterns\Plugin\Context\RequirementsContext;
20use Drupal\views\Plugin\views\PluginBase;
21
22/**
23 * Plugin implementation of the display_buildable.
24 */
25#[DisplayBuildable(
26  id: 'view_display',
27  label: new TranslatableMarkup('Views'),
28  instance_prefix: 'views__',
29)]
30final class ViewDisplay extends DisplayBuildablePluginBase {
31
32  /**
33   * View display extender plugin.
34   */
35  protected PluginBase $extender;
36
37  /**
38   * {@inheritdoc}
39   */
40  public function __construct(array $configuration, $plugin_id, $plugin_definition) {
41    parent::__construct($configuration, $plugin_id, $plugin_definition);
42    $this->extender = $configuration['extender'];
43  }
44
45  /**
46   * {@inheritdoc}
47   */
48  public static function getContextRequirement(): string {
49    // @see \Drupal\ui_patterns_views\Plugin\UiPatterns\Source\ViewRowsSource.
50    return 'views:style';
51  }
52
53  /**
54   * {@inheritdoc}
55   */
56  public function getBuilderUrl(): Url {
57    $params = [
58      'view' => $this->extender->view->id(),
59      'display' => $this->extender->view->current_display,
60    ];
61
62    return Url::fromRoute('display_builder_views.views.manage', $params);
63  }
64
65  /**
66   * {@inheritdoc}
67   */
68  public static function checkInstanceId(string $instance_id): ?array {
69    if (!\str_starts_with($instance_id, self::getPrefix())) {
70      return NULL;
71    }
72    [, $view, $display] = \explode('__', $instance_id);
73
74    return [
75      'view' => $view,
76      'display' => $display,
77    ];
78  }
79
80  /**
81   * {@inheritdoc}
82   */
83  public static function checkAccess(string $instance_id, AccountInterface $account): AccessResultInterface {
84    return $account->hasPermission('administer views') ? AccessResult::allowed() : AccessResult::forbidden();
85  }
86
87  /**
88   * {@inheritdoc}
89   */
90  public static function getUrlFromInstanceId(string $instance_id): Url {
91    $params = self::checkInstanceId($instance_id);
92
93    if (!$params) {
94      // Fallback to the list of instances.
95      return Url::fromRoute('entity.display_builder_instance.collection');
96    }
97
98    return Url::fromRoute('display_builder_views.views.manage', $params);
99  }
100
101  /**
102   * {@inheritdoc}
103   */
104  public static function getDisplayUrlFromInstanceId(string $instance_id): Url {
105    $params = self::checkInstanceId($instance_id);
106
107    if (!$params) {
108      // Fallback to the list of instances.
109      return Url::fromRoute('entity.display_builder_instance.collection');
110    }
111
112    return Url::fromRoute('entity.view.edit_form', $params);
113  }
114
115  /**
116   * {@inheritdoc}
117   */
118  public function getProfile(): ?ProfileInterface {
119    if (!isset($this->extender->options[DisplayBuildableInterface::PROFILE_PROPERTY])) {
120      return NULL;
121    }
122    $display_builder_id = $this->extender->options[DisplayBuildableInterface::PROFILE_PROPERTY];
123
124    if (empty($display_builder_id)) {
125      return NULL;
126    }
127    $storage = $this->entityTypeManager->getStorage('display_builder_profile');
128
129    /** @var \Drupal\display_builder\ProfileInterface $display_builder */
130    $display_builder = $storage->load($display_builder_id);
131
132    return $display_builder;
133  }
134
135  /**
136   * {@inheritdoc}
137   */
138  public function getSources(): array {
139    return $this->extender->options[DisplayBuildableInterface::SOURCES_PROPERTY] ?? [];
140  }
141
142  /**
143   * {@inheritdoc}
144   */
145  public function saveSources(): void {
146    $sources = $this->getInstance()->getCurrentState();
147    // First, we save in the "live" object.
148    $this->extender->options[DisplayBuildableInterface::SOURCES_PROPERTY] = $sources;
149    // Then, we save in the permanent storage.
150    $displays = $this->extender->view->storage->get('display');
151    $display_id = $this->extender->view->current_display;
152    // It is risky to alter a View like that. We need to be careful to not
153    // break the storage integrity, but we didn't find a better way.
154    $displays[$display_id]['display_options']['display_extenders']['display_builder'][DisplayBuildableInterface::SOURCES_PROPERTY] = $sources;
155    $this->extender->view->storage->set('display', $displays);
156    $this->extender->view->storage->save();
157    // @todo Test if we still need to invalidate the cache manually here.
158    $this->extender->view->storage->invalidateCaches();
159  }
160
161  /**
162   * {@inheritdoc}
163   */
164  public function getInstanceId(): string {
165    return \sprintf('%s%s__%s', self::getPrefix(), $this->extender->view->id(), $this->extender->view->current_display);
166  }
167
168  /**
169   * {@inheritdoc}
170   */
171  public static function collectInstances(?EntityTypeManagerInterface $entityTypeManager = NULL): array {
172    $entityTypeManager = \Drupal::service('entity_type.manager');
173    $storage = $entityTypeManager->getStorage('view');
174    $instance_storage = $entityTypeManager->getStorage('display_builder_instance');
175    $instances = [];
176
177    foreach ($storage->loadMultiple() as $view) {
178      // @phpstan-ignore-next-line
179      foreach ($view->display as $display_id => $display) {
180        $profile = $display['display_options']['display_extenders']['display_builder'][DisplayBuildableInterface::PROFILE_PROPERTY] ?? NULL;
181
182        if (!$profile) {
183          continue;
184        }
185        $instance_id = \sprintf('%s%s__%s', self::getPrefix(), $view->id(), $display_id);
186        // We are OK with keeping the null values if the instance entity
187        // doesn't exists in storage. So the caller can decide to create
188        // the missing Instance entities.
189        $instances[$instance_id] = $instance_storage->load($instance_id);
190      }
191    }
192
193    return $instances;
194  }
195
196  /**
197   * {@inheritdoc}
198   */
199  protected function getInitializationMessage(): TranslatableMarkup {
200    if ($this->initialDataSource === 'fixture') {
201      return $this->t('Initialization from default configuration.');
202    }
203
204    return $this->t('Initialization from existing View configuration.');
205  }
206
207  /**
208   * {@inheritdoc}
209   */
210  protected function getInitialSources(): array {
211    // Get the sources stored in config.
212    $sources = $this->getSources();
213
214    if (empty($sources)) {
215      // Fallback to a fixture mimicking the standard view layout.
216      $sources = DisplayBuilderHelpers::getFixtureDataFromExtension('display_builder_views', 'default_view');
217      $this->initialDataSource = 'fixture';
218    }
219
220    return $sources;
221  }
222
223  /**
224   * {@inheritdoc}
225   */
226  protected function getInitialContext(): array {
227    $contexts = [];
228    // Mark for usage with views.
229    $contexts = RequirementsContext::addToContext([self::getContextRequirement()], $contexts);
230    // Add view entity that we need in our sources or even UI Patterns Views
231    // sources.
232    $contexts['ui_patterns_views:view_entity'] = EntityContext::fromEntity($this->extender->view->storage);
233
234    return $contexts;
235  }
236
237}