Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
62.90% covered (warning)
62.90%
39 / 62
60.61% covered (warning)
60.61%
20 / 33
31.82% covered (danger)
31.82%
7 / 22
22.22% covered (danger)
22.22%
2 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
DisplayExtender
62.90% covered (warning)
62.90%
39 / 62
60.61% covered (warning)
60.61%
20 / 33
31.82% covered (danger)
31.82%
7 / 22
22.22% covered (danger)
22.22%
2 / 9
160.78
0.00% covered (danger)
0.00%
0 / 1
 create
100.00% covered (success)
100.00%
5 / 5
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
 buildOptionsForm
75.00% covered (warning)
75.00%
3 / 4
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
 submitOptionsForm
57.14% covered (warning)
57.14%
8 / 14
66.67% covered (warning)
66.67%
6 / 9
16.67% covered (danger)
16.67%
1 / 6
0.00% covered (danger)
0.00%
0 / 1
19.47
 optionsSummary
88.89% covered (warning)
88.89%
8 / 9
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
 preExecute
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
 buildThemeRegistryEntry
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
 getInstance
85.71% covered (warning)
85.71%
6 / 7
80.00% covered (warning)
80.00%
4 / 5
33.33% covered (danger)
33.33%
1 / 3
0.00% covered (danger)
0.00%
0 / 1
5.67
 isApplicable
66.67% covered (warning)
66.67%
6 / 9
57.14% covered (warning)
57.14%
4 / 7
25.00% covered (danger)
25.00%
1 / 4
0.00% covered (danger)
0.00%
0 / 1
10.75
 displayBuildable
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
1<?php
2
3declare(strict_types=1);
4
5namespace Drupal\display_builder_views\Plugin\views\display_extender;
6
7use Drupal\Core\Extension\ModuleExtensionList;
8use Drupal\Core\Form\FormStateInterface;
9use Drupal\Core\StringTranslation\TranslatableMarkup;
10use Drupal\Core\Theme\Registry;
11use Drupal\display_builder\DisplayBuildableInterface;
12use Drupal\display_builder\InstanceInterface;
13use Drupal\views\Attribute\ViewsDisplayExtender;
14use Drupal\views\Plugin\views\display_extender\DisplayExtenderPluginBase;
15use Symfony\Component\DependencyInjection\ContainerInterface;
16
17/**
18 * Styles display extender plugin.
19 *
20 * @ingroup views_display_extender_plugins
21 */
22#[ViewsDisplayExtender(
23  id: 'display_builder',
24  title: new TranslatableMarkup('Display Builder'),
25  help: new TranslatableMarkup('Use display builder as output for this view.'),
26  no_ui: FALSE,
27)]
28final class DisplayExtender extends DisplayExtenderPluginBase {
29
30  /**
31   * The entity type interface.
32   *
33   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
34   */
35  protected $entityTypeManager;
36
37  /**
38   * The theme registry.
39   */
40  protected Registry $themeRegistry;
41
42  /**
43   * The list of modules.
44   */
45  protected ModuleExtensionList $modules;
46
47  /**
48   * The loaded display builder instance.
49   */
50  protected ?InstanceInterface $instance;
51
52  /**
53   * {@inheritdoc}
54   */
55  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
56    $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
57    $instance->entityTypeManager = $container->get('entity_type.manager');
58    $instance->themeRegistry = $container->get('theme.registry');
59    $instance->modules = $container->get('extension.list.module');
60
61    return $instance;
62  }
63
64  /**
65   * {@inheritdoc}
66   */
67  public function buildOptionsForm(&$form, FormStateInterface $form_state): void {
68    if ($form_state->get('section') !== 'display_builder') {
69      return;
70    }
71
72    $form['#title'] .= $this->t('Display Builder');
73    $form[DisplayBuildableInterface::PROFILE_PROPERTY] = $this->displayBuildable()->buildInstanceForm(FALSE);
74  }
75
76  /**
77   * {@inheritdoc}
78   */
79  public function submitOptionsForm(&$form, FormStateInterface $form_state): void {
80    if ($form_state->get('section') !== 'display_builder') {
81      return;
82    }
83
84    // @todo we should have always a fallback.
85    $profile_id = $form_state->getValue(DisplayBuildableInterface::PROFILE_PROPERTY, 'default');
86    $this->options[DisplayBuildableInterface::PROFILE_PROPERTY] = $profile_id;
87    $buildable = $this->displayBuildable();
88
89    if (empty($profile_id)) {
90      // If no Display Builder selected, we delete the related instance.
91      // @todo Do we move that to the View's EntityInterface::delete() method?
92      // @todo Also, when the changed are canceled from UI leaving the View
93      // without Display Builder.
94      $storage = $this->entityTypeManager->getStorage('display_builder_instance');
95      $storage->delete([$this->getInstance()]);
96
97      return;
98    }
99
100    $buildable->initInstanceIfMissing();
101
102    // Save the profile in the instance if changed.
103    $instance = $this->getInstance();
104
105    if ($instance && $buildable->getProfile()->id() !== $profile_id) {
106      $instance->setProfile($profile_id);
107      $instance->save();
108    }
109  }
110
111  /**
112   * {@inheritdoc}
113   */
114  public function optionsSummary(&$categories, &$options): void {
115    $buildable = $this->displayBuildable();
116
117    if (!$this->isApplicable()) {
118      return;
119    }
120
121    $options['display_builder'] = [
122      'category' => 'other',
123      'title' => $this->t('Display Builder'),
124      'desc' => $this->t('Use display builder as output for this view.'),
125      'value' => $buildable->getProfile()?->label() ?? $this->t('Disabled'),
126    ];
127  }
128
129  /**
130   * {@inheritdoc}
131   */
132  public function preExecute(): void {
133    $buildable = $this->displayBuildable();
134
135    if (!$buildable->getProfile()) {
136      return;
137    }
138    // We alter the registry here instead of implementing
139    // hook_theme_registry_alter in order keep the alteration specific to each
140    // view.
141    $view = $this->view;
142    // Theme hook suggestion of the current view display.
143    $suggestion = \implode('__', ['views_view', $view->id(), $view->getDisplay()->getPluginId()]);
144    $entry = $this->buildThemeRegistryEntry();
145    $this->themeRegistry->getRuntime()->set($suggestion, $entry);
146  }
147
148  /**
149   * Build theme registry entry.
150   *
151   * @return array
152   *   A theme registry entry.
153   */
154  protected function buildThemeRegistryEntry(): array {
155    $theme_registry = $this->themeRegistry->get();
156    // Identical to views_view with a specific path.
157    $entry = $theme_registry['views_view'];
158    $entry['path'] = $this->modules->getPath('display_builder_views') . '/templates';
159
160    return $entry;
161  }
162
163  /**
164   * Gets the Display Builder instance.
165   *
166   * @return \Drupal\display_builder\InstanceInterface|null
167   *   A display builder instance.
168   */
169  protected function getInstance(): ?InstanceInterface {
170    if (!$this->displayBuildable()->getInstanceId()) {
171      return NULL;
172    }
173
174    if (!isset($this->instance)) {
175      $instance_id = $this->displayBuildable()->getInstanceId();
176      /** @var \Drupal\display_builder\InstanceInterface|null $instance */
177      $instance = $this->entityTypeManager->getStorage('display_builder_instance')->load($instance_id);
178      $this->instance = $instance;
179    }
180
181    return $this->instance;
182  }
183
184  /**
185   * If display builder can be applied to this display.
186   *
187   * @return bool
188   *   Applicable or not.
189   */
190  private function isApplicable(): bool {
191    $display = $this->view->getDisplay();
192    $display_definition = $display->getPluginDefinition();
193
194    if (!isset($display_definition['class'])) {
195      return FALSE;
196    }
197
198    // Do not include with feed and entity reference, as they have no output to
199    // apply a display builder to.
200    if ($display_definition['class'] === 'Drupal\views\Plugin\views\display\Feed') {
201      return FALSE;
202    }
203
204    if ($display_definition['class'] === 'Drupal\views\Plugin\views\display\EntityReference') {
205      return FALSE;
206    }
207
208    // @todo safer to not allow third party display?
209    // phpcs:disable
210    // if (str_contains($display_definition['class'], 'Drupal\views\Plugin\views\display')) {
211    //   return FALSE;
212    // }
213    // phpcs:enable
214
215    return TRUE;
216  }
217
218  /**
219   * Gets the display buildable manager.
220   *
221   * @return \Drupal\display_builder\DisplayBuildableInterface
222   *   The manager for display buildable.
223   */
224  private function displayBuildable(): DisplayBuildableInterface {
225    /** @var \Drupal\display_builder\DisplayBuildablePluginManager $manager */
226    $manager = \Drupal::service('plugin.manager.display_buildable');
227    /** @var \Drupal\display_builder\DisplayBuildableInterface $buildable */
228    $buildable = $manager->createInstance('view_display', ['extender' => $this]);
229
230    return $buildable;
231  }
232
233}

Paths

Below are the source code lines that represent each code path as identified by Xdebug. Please note a path is not necessarily coterminous with a line, a line may contain multiple paths and therefore show up more than once. Please also be aware that some paths may include implicit rather than explicit branches, e.g. an if statement always has an else as part of its logical flow even if you didn't write one.

DisplayExtender->buildOptionsForm
67  public function buildOptionsForm(&$form, FormStateInterface $form_state): void {
68    if ($form_state->get('section') !== 'display_builder') {
 
69      return;
67  public function buildOptionsForm(&$form, FormStateInterface $form_state): void {
68    if ($form_state->get('section') !== 'display_builder') {
 
72    $form['#title'] .= $this->t('Display Builder');
73    $form[DisplayBuildableInterface::PROFILE_PROPERTY] = $this->displayBuildable()->buildInstanceForm(FALSE);
74  }
DisplayExtender->buildThemeRegistryEntry
155    $theme_registry = $this->themeRegistry->get();
156    // Identical to views_view with a specific path.
157    $entry = $theme_registry['views_view'];
158    $entry['path'] = $this->modules->getPath('display_builder_views') . '/templates';
159
160    return $entry;
161  }
DisplayExtender->create
55  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
56    $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
57    $instance->entityTypeManager = $container->get('entity_type.manager');
58    $instance->themeRegistry = $container->get('theme.registry');
59    $instance->modules = $container->get('extension.list.module');
60
61    return $instance;
62  }
DisplayExtender->displayBuildable
226    $manager = \Drupal::service('plugin.manager.display_buildable');
227    /** @var \Drupal\display_builder\DisplayBuildableInterface $buildable */
228    $buildable = $manager->createInstance('view_display', ['extender' => $this]);
229
230    return $buildable;
231  }
DisplayExtender->getInstance
170    if (!$this->displayBuildable()->getInstanceId()) {
 
171      return NULL;
170    if (!$this->displayBuildable()->getInstanceId()) {
 
174    if (!isset($this->instance)) {
 
175      $instance_id = $this->displayBuildable()->getInstanceId();
176      /** @var \Drupal\display_builder\InstanceInterface|null $instance */
177      $instance = $this->entityTypeManager->getStorage('display_builder_instance')->load($instance_id);
178      $this->instance = $instance;
179    }
180
181    return $this->instance;
 
181    return $this->instance;
182  }
170    if (!$this->displayBuildable()->getInstanceId()) {
 
174    if (!isset($this->instance)) {
 
181    return $this->instance;
182  }
DisplayExtender->isApplicable
191    $display = $this->view->getDisplay();
192    $display_definition = $display->getPluginDefinition();
193
194    if (!isset($display_definition['class'])) {
 
195      return FALSE;
191    $display = $this->view->getDisplay();
192    $display_definition = $display->getPluginDefinition();
193
194    if (!isset($display_definition['class'])) {
 
200    if ($display_definition['class'] === 'Drupal\views\Plugin\views\display\Feed') {
 
201      return FALSE;
191    $display = $this->view->getDisplay();
192    $display_definition = $display->getPluginDefinition();
193
194    if (!isset($display_definition['class'])) {
 
200    if ($display_definition['class'] === 'Drupal\views\Plugin\views\display\Feed') {
 
204    if ($display_definition['class'] === 'Drupal\views\Plugin\views\display\EntityReference') {
 
205      return FALSE;
191    $display = $this->view->getDisplay();
192    $display_definition = $display->getPluginDefinition();
193
194    if (!isset($display_definition['class'])) {
 
200    if ($display_definition['class'] === 'Drupal\views\Plugin\views\display\Feed') {
 
204    if ($display_definition['class'] === 'Drupal\views\Plugin\views\display\EntityReference') {
 
215    return TRUE;
216  }
DisplayExtender->optionsSummary
114  public function optionsSummary(&$categories, &$options): void {
115    $buildable = $this->displayBuildable();
116
117    if (!$this->isApplicable()) {
 
118      return;
114  public function optionsSummary(&$categories, &$options): void {
115    $buildable = $this->displayBuildable();
116
117    if (!$this->isApplicable()) {
 
122      'category' => 'other',
123      'title' => $this->t('Display Builder'),
124      'desc' => $this->t('Use display builder as output for this view.'),
125      'value' => $buildable->getProfile()?->label() ?? $this->t('Disabled'),
126    ];
127  }
DisplayExtender->preExecute
133    $buildable = $this->displayBuildable();
134
135    if (!$buildable->getProfile()) {
 
136      return;
133    $buildable = $this->displayBuildable();
134
135    if (!$buildable->getProfile()) {
 
141    $view = $this->view;
142    // Theme hook suggestion of the current view display.
143    $suggestion = \implode('__', ['views_view', $view->id(), $view->getDisplay()->getPluginId()]);
144    $entry = $this->buildThemeRegistryEntry();
145    $this->themeRegistry->getRuntime()->set($suggestion, $entry);
146  }
DisplayExtender->submitOptionsForm
79  public function submitOptionsForm(&$form, FormStateInterface $form_state): void {
80    if ($form_state->get('section') !== 'display_builder') {
 
81      return;
79  public function submitOptionsForm(&$form, FormStateInterface $form_state): void {
80    if ($form_state->get('section') !== 'display_builder') {
 
85    $profile_id = $form_state->getValue(DisplayBuildableInterface::PROFILE_PROPERTY, 'default');
86    $this->options[DisplayBuildableInterface::PROFILE_PROPERTY] = $profile_id;
87    $buildable = $this->displayBuildable();
88
89    if (empty($profile_id)) {
 
94      $storage = $this->entityTypeManager->getStorage('display_builder_instance');
95      $storage->delete([$this->getInstance()]);
96
97      return;
79  public function submitOptionsForm(&$form, FormStateInterface $form_state): void {
80    if ($form_state->get('section') !== 'display_builder') {
 
85    $profile_id = $form_state->getValue(DisplayBuildableInterface::PROFILE_PROPERTY, 'default');
86    $this->options[DisplayBuildableInterface::PROFILE_PROPERTY] = $profile_id;
87    $buildable = $this->displayBuildable();
88
89    if (empty($profile_id)) {
 
100    $buildable->initInstanceIfMissing();
101
102    // Save the profile in the instance if changed.
103    $instance = $this->getInstance();
104
105    if ($instance && $buildable->getProfile()->id() !== $profile_id) {
 
105    if ($instance && $buildable->getProfile()->id() !== $profile_id) {
 
105    if ($instance && $buildable->getProfile()->id() !== $profile_id) {
 
106      $instance->setProfile($profile_id);
107      $instance->save();
108    }
109  }
 
109  }
79  public function submitOptionsForm(&$form, FormStateInterface $form_state): void {
80    if ($form_state->get('section') !== 'display_builder') {
 
85    $profile_id = $form_state->getValue(DisplayBuildableInterface::PROFILE_PROPERTY, 'default');
86    $this->options[DisplayBuildableInterface::PROFILE_PROPERTY] = $profile_id;
87    $buildable = $this->displayBuildable();
88
89    if (empty($profile_id)) {
 
100    $buildable->initInstanceIfMissing();
101
102    // Save the profile in the instance if changed.
103    $instance = $this->getInstance();
104
105    if ($instance && $buildable->getProfile()->id() !== $profile_id) {
 
105    if ($instance && $buildable->getProfile()->id() !== $profile_id) {
 
105    if ($instance && $buildable->getProfile()->id() !== $profile_id) {
 
109  }
79  public function submitOptionsForm(&$form, FormStateInterface $form_state): void {
80    if ($form_state->get('section') !== 'display_builder') {
 
85    $profile_id = $form_state->getValue(DisplayBuildableInterface::PROFILE_PROPERTY, 'default');
86    $this->options[DisplayBuildableInterface::PROFILE_PROPERTY] = $profile_id;
87    $buildable = $this->displayBuildable();
88
89    if (empty($profile_id)) {
 
100    $buildable->initInstanceIfMissing();
101
102    // Save the profile in the instance if changed.
103    $instance = $this->getInstance();
104
105    if ($instance && $buildable->getProfile()->id() !== $profile_id) {
 
105    if ($instance && $buildable->getProfile()->id() !== $profile_id) {
 
106      $instance->setProfile($profile_id);
107      $instance->save();
108    }
109  }
 
109  }
79  public function submitOptionsForm(&$form, FormStateInterface $form_state): void {
80    if ($form_state->get('section') !== 'display_builder') {
 
85    $profile_id = $form_state->getValue(DisplayBuildableInterface::PROFILE_PROPERTY, 'default');
86    $this->options[DisplayBuildableInterface::PROFILE_PROPERTY] = $profile_id;
87    $buildable = $this->displayBuildable();
88
89    if (empty($profile_id)) {
 
100    $buildable->initInstanceIfMissing();
101
102    // Save the profile in the instance if changed.
103    $instance = $this->getInstance();
104
105    if ($instance && $buildable->getProfile()->id() !== $profile_id) {
 
105    if ($instance && $buildable->getProfile()->id() !== $profile_id) {
 
109  }