Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 102
0.00% covered (danger)
0.00%
0 / 62
0.00% covered (danger)
0.00%
0 / 87
0.00% covered (danger)
0.00%
0 / 16
CRAP
0.00% covered (danger)
0.00%
0 / 1
StateButtons
0.00% covered (danger)
0.00%
0 / 96
0.00% covered (danger)
0.00%
0 / 62
0.00% covered (danger)
0.00%
0 / 87
0.00% covered (danger)
0.00%
0 / 16
1332
0.00% covered (danger)
0.00%
0 / 1
 create
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
 build
0.00% covered (danger)
0.00%
0 / 12
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
 onSave
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
 onAttachToRoot
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
 onAttachToSlot
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
 onMove
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
 onHistoryChange
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
 onUpdate
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
 onDelete
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
 buildStateButtons
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 54
0.00% covered (danger)
0.00%
0 / 1
72
 hasButtons
0.00% covered (danger)
0.00%
0 / 14
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
 isOverridden
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
30
 buildPublishButton
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 buildRestoreButton
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 buildRevertButton
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 rebuild
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
1<?php
2
3declare(strict_types=1);
4
5namespace Drupal\display_builder\Plugin\display_builder\Island;
6
7use Drupal\Core\Entity\FieldableEntityInterface;
8use Drupal\Core\Extension\ModuleHandlerInterface;
9use Drupal\Core\StringTranslation\TranslatableMarkup;
10use Drupal\display_builder\Attribute\Island;
11use Drupal\display_builder\InstanceInterface;
12use Drupal\display_builder\IslandPluginToolbarButtonConfigurationBase;
13use Drupal\display_builder\IslandType;
14use Drupal\display_builder_entity_view\Field\DisplayBuilderItemList;
15use Symfony\Component\DependencyInjection\ContainerInterface;
16
17/**
18 * State buttons island plugin implementation.
19 */
20#[Island(
21  id: 'state',
22  enabled_by_default: TRUE,
23  label: new TranslatableMarkup('State'),
24  description: new TranslatableMarkup('Publish and reset the display.'),
25  type: IslandType::Button,
26)]
27class StateButtons extends IslandPluginToolbarButtonConfigurationBase {
28
29  /**
30   * The module handler.
31   */
32  protected ModuleHandlerInterface $moduleHandler;
33
34  /**
35   * {@inheritdoc}
36   */
37  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
38    $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
39    $instance->moduleHandler = $container->get('module_handler');
40
41    return $instance;
42  }
43
44  /**
45   * {@inheritdoc}
46   */
47  public function build(InstanceInterface $builder, array $data = [], array $options = []): array {
48    if (!$builder->canSaveContextsRequirement()) {
49      return [];
50    }
51
52    $buttons = $this->buildStateButtons($builder);
53
54    if (empty($buttons)) {
55      return [];
56    }
57
58    return [
59      '#type' => 'component',
60      '#component' => 'display_builder:button_group',
61      '#slots' => [
62        'buttons' => $buttons,
63      ],
64    ];
65  }
66
67  /**
68   * {@inheritdoc}
69   */
70  public function onSave(string $builder_id): array {
71    return $this->reloadWithGlobalData($builder_id);
72  }
73
74  /**
75   * {@inheritdoc}
76   */
77  public function onAttachToRoot(string $builder_id, string $instance_id): array {
78    return $this->rebuild($builder_id);
79  }
80
81  /**
82   * {@inheritdoc}
83   */
84  public function onAttachToSlot(string $builder_id, string $instance_id, string $parent_id): array {
85    return $this->rebuild($builder_id);
86  }
87
88  /**
89   * {@inheritdoc}
90   */
91  public function onMove(string $builder_id, string $instance_id): array {
92    return $this->rebuild($builder_id);
93  }
94
95  /**
96   * {@inheritdoc}
97   */
98  public function onHistoryChange(string $builder_id): array {
99    return $this->rebuild($builder_id);
100  }
101
102  /**
103   * {@inheritdoc}
104   */
105  public function onUpdate(string $builder_id, string $instance_id): array {
106    return $this->rebuild($builder_id);
107  }
108
109  /**
110   * {@inheritdoc}
111   */
112  public function onDelete(string $builder_id, string $parent_id): array {
113    return $this->rebuild($builder_id);
114  }
115
116  /**
117   * Build state buttons.
118   *
119   * @param \Drupal\display_builder\InstanceInterface $instance
120   *   The current display builder instance.
121   *
122   * @return array
123   *   A renderable array of buttons.
124   */
125  protected function buildStateButtons(InstanceInterface $instance): array {
126    $instance_d = (string) $instance->id();
127    $buttons = [];
128    $hasSave = $instance->hasSave();
129    $saveIsCurrent = $hasSave ? $instance->saveIsCurrent() : FALSE;
130
131    if ($this->isButtonEnabled('publish') && !$saveIsCurrent) {
132      $buttons[] = $this->htmxEvents->onSave($this->buildPublishButton(), $instance_d);
133    }
134
135    if ($this->isButtonEnabled('restore') && !$saveIsCurrent) {
136      $buttons[] = $this->htmxEvents->onReset($this->buildRestoreButton(), $instance_d);
137    }
138
139    if ($this->isButtonEnabled('revert') && $this->isOverridden($instance_d)) {
140      $buttons[] = $this->htmxEvents->onRevert($this->buildRevertButton(), $instance_d);
141    }
142
143    return $buttons;
144  }
145
146  /**
147   * {@inheritdoc}
148   */
149  protected function hasButtons(): array {
150    return [
151      'publish' => [
152        'title' => $this->t('Publish'),
153        'default' => 'label',
154      ],
155      'restore' => [
156        'title' => $this->t('Restore'),
157        'default' => 'icon',
158      ],
159      'revert' => [
160        'title' => $this->t('Revert'),
161        'default' => 'icon',
162      ],
163    ];
164  }
165
166  /**
167   * Check if the display builder is on an entity override.
168   *
169   * @param string $builder_id
170   *   The ID of the builder.
171   *
172   * @return bool
173   *   Returns TRUE if the display builder is on an entity override.
174   */
175  protected function isOverridden(string $builder_id): bool {
176    if (!$this->moduleHandler->moduleExists('display_builder_entity_view')) {
177      return FALSE;
178    }
179
180    $instanceInfos = DisplayBuilderItemList::checkInstanceId($builder_id);
181
182    if (!isset($instanceInfos['entity_type_id'], $instanceInfos['entity_id'], $instanceInfos['field_name'])) {
183      return FALSE;
184    }
185
186    // Do not get the profile entity ID from Instance context because the
187    // data stored there is not reliable yet.
188    // See: https://www.drupal.org/project/display_builder/issues/3544545
189    $entity = $this->entityTypeManager->getStorage($instanceInfos['entity_type_id'])
190      ->load($instanceInfos['entity_id']);
191
192    if (!($entity instanceof FieldableEntityInterface)) {
193      return FALSE;
194    }
195
196    $overriddenField = $entity->get($instanceInfos['field_name']);
197
198    if ($overriddenField->isEmpty()) {
199      return FALSE;
200    }
201
202    return TRUE;
203  }
204
205  /**
206   * Builds the publish button.
207   *
208   * @return array
209   *   The publish button render array.
210   */
211  private function buildPublishButton(): array {
212    $button = $this->buildButton(
213      $this->showLabel('publish') ? $this->t('Publish') : '',
214      'publish',
215      $this->showIcon('publish') ? 'upload' : '',
216      $this->t('Publish this display in current state. (shortcut: P)'), ['P' => $this->t('Publish this display (shift+P)')]
217    );
218    $button['#props']['variant'] = 'primary';
219    $button['#attributes']['outline'] = TRUE;
220
221    return $button;
222  }
223
224  /**
225   * Builds the restore button.
226   *
227   * @return array
228   *   The restore button render array.
229   */
230  private function buildRestoreButton(): array {
231    $button = $this->buildButton(
232      $this->showLabel('restore') ? $this->t('Restore') : '',
233      'restore',
234      $this->showIcon('restore') ? 'arrow-repeat' : '',
235      $this->t('Restore to last saved version')
236    );
237    $button['#props']['variant'] = 'warning';
238    $button['#attributes']['outline'] = TRUE;
239
240    return $button;
241  }
242
243  /**
244   * Builds the revert button.
245   *
246   * @return array
247   *   The revert button render array.
248   */
249  private function buildRevertButton(): array {
250    $button = $this->buildButton(
251      $this->showLabel('revert') ? $this->t('Revert') : '',
252      'revert',
253      $this->showIcon('revert') ? 'box-arrow-in-down' : '',
254      $this->t('Revert to default display (not overridden)')
255    );
256    $button['#props']['variant'] = 'danger';
257    $button['#attributes']['outline'] = TRUE;
258
259    return $button;
260  }
261
262  /**
263   * Rebuilds the island with the given builder ID.
264   *
265   * @param string $builder_id
266   *   The ID of the builder.
267   *
268   * @return array
269   *   The rebuilt island.
270   */
271  private function rebuild(string $builder_id): array {
272    if (!$this->builder) {
273      // @todo pass \Drupal\display_builder\InstanceInterface object in
274      // parameters instead of loading again.
275      /** @var \Drupal\display_builder\InstanceStorage $storage */
276      $storage = $this->entityTypeManager->getStorage('display_builder_instance');
277      /** @var \Drupal\display_builder\InstanceInterface $builder */
278      $builder = $storage->load($builder_id);
279      $this->builder = $builder;
280    }
281
282    return $this->addOutOfBand(
283      $this->build($this->builder),
284      '#' . $this->getHtmlId($builder_id),
285      'innerHTML'
286    );
287  }
288
289}

Branches

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

StateButtons->build
47  public function build(InstanceInterface $builder, array $data = [], array $options = []): array {
48    if (!$builder->canSaveContextsRequirement()) {
49      return [];
52    $buttons = $this->buildStateButtons($builder);
53
54    if (empty($buttons)) {
55      return [];
59      '#type' => 'component',
60      '#component' => 'display_builder:button_group',
61      '#slots' => [
62        'buttons' => $buttons,
StateButtons->buildPublishButton
212    $button = $this->buildButton(
213      $this->showLabel('publish') ? $this->t('Publish') : '',
213      $this->showLabel('publish') ? $this->t('Publish') : '',
213      $this->showLabel('publish') ? $this->t('Publish') : '',
213      $this->showLabel('publish') ? $this->t('Publish') : '',
214      'publish',
215      $this->showIcon('publish') ? 'upload' : '',
215      $this->showIcon('publish') ? 'upload' : '',
215      $this->showIcon('publish') ? 'upload' : '',
215      $this->showIcon('publish') ? 'upload' : '',
216      $this->t('Publish this display in current state. (shortcut: P)'), ['P' => $this->t('Publish this display (shift+P)')]
217    );
218    $button['#props']['variant'] = 'primary';
219    $button['#attributes']['outline'] = TRUE;
220
221    return $button;
StateButtons->buildRestoreButton
231    $button = $this->buildButton(
232      $this->showLabel('restore') ? $this->t('Restore') : '',
232      $this->showLabel('restore') ? $this->t('Restore') : '',
232      $this->showLabel('restore') ? $this->t('Restore') : '',
232      $this->showLabel('restore') ? $this->t('Restore') : '',
233      'restore',
234      $this->showIcon('restore') ? 'arrow-repeat' : '',
234      $this->showIcon('restore') ? 'arrow-repeat' : '',
234      $this->showIcon('restore') ? 'arrow-repeat' : '',
234      $this->showIcon('restore') ? 'arrow-repeat' : '',
235      $this->t('Restore to last saved version')
236    );
237    $button['#props']['variant'] = 'warning';
238    $button['#attributes']['outline'] = TRUE;
239
240    return $button;
StateButtons->buildRevertButton
250    $button = $this->buildButton(
251      $this->showLabel('revert') ? $this->t('Revert') : '',
251      $this->showLabel('revert') ? $this->t('Revert') : '',
251      $this->showLabel('revert') ? $this->t('Revert') : '',
251      $this->showLabel('revert') ? $this->t('Revert') : '',
252      'revert',
253      $this->showIcon('revert') ? 'box-arrow-in-down' : '',
253      $this->showIcon('revert') ? 'box-arrow-in-down' : '',
253      $this->showIcon('revert') ? 'box-arrow-in-down' : '',
253      $this->showIcon('revert') ? 'box-arrow-in-down' : '',
254      $this->t('Revert to default display (not overridden)')
255    );
256    $button['#props']['variant'] = 'danger';
257    $button['#attributes']['outline'] = TRUE;
258
259    return $button;
StateButtons->buildStateButtons
125  protected function buildStateButtons(InstanceInterface $instance): array {
126    $instance_d = (string) $instance->id();
127    $buttons = [];
128    $hasSave = $instance->hasSave();
129    $saveIsCurrent = $hasSave ? $instance->saveIsCurrent() : FALSE;
129    $saveIsCurrent = $hasSave ? $instance->saveIsCurrent() : FALSE;
129    $saveIsCurrent = $hasSave ? $instance->saveIsCurrent() : FALSE;
129    $saveIsCurrent = $hasSave ? $instance->saveIsCurrent() : FALSE;
130
131    if ($this->isButtonEnabled('publish') && !$saveIsCurrent) {
131    if ($this->isButtonEnabled('publish') && !$saveIsCurrent) {
132      $buttons[] = $this->htmxEvents->onSave($this->buildPublishButton(), $instance_d);
133    }
134
135    if ($this->isButtonEnabled('restore') && !$saveIsCurrent) {
135    if ($this->isButtonEnabled('restore') && !$saveIsCurrent) {
135    if ($this->isButtonEnabled('restore') && !$saveIsCurrent) {
136      $buttons[] = $this->htmxEvents->onReset($this->buildRestoreButton(), $instance_d);
137    }
138
139    if ($this->isButtonEnabled('revert') && $this->isOverridden($instance_d)) {
139    if ($this->isButtonEnabled('revert') && $this->isOverridden($instance_d)) {
139    if ($this->isButtonEnabled('revert') && $this->isOverridden($instance_d)) {
140      $buttons[] = $this->htmxEvents->onRevert($this->buildRevertButton(), $instance_d);
141    }
142
143    return $buttons;
143    return $buttons;
StateButtons->create
37  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
38    $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
39    $instance->moduleHandler = $container->get('module_handler');
40
41    return $instance;
StateButtons->hasButtons
152        'title' => $this->t('Publish'),
153        'default' => 'label',
154      ],
155      'restore' => [
156        'title' => $this->t('Restore'),
157        'default' => 'icon',
158      ],
159      'revert' => [
160        'title' => $this->t('Revert'),
161        'default' => 'icon',
StateButtons->isOverridden
175  protected function isOverridden(string $builder_id): bool {
176    if (!$this->moduleHandler->moduleExists('display_builder_entity_view')) {
177      return FALSE;
180    $instanceInfos = DisplayBuilderItemList::checkInstanceId($builder_id);
181
182    if (!isset($instanceInfos['entity_type_id'], $instanceInfos['entity_id'], $instanceInfos['field_name'])) {
182    if (!isset($instanceInfos['entity_type_id'], $instanceInfos['entity_id'], $instanceInfos['field_name'])) {
182    if (!isset($instanceInfos['entity_type_id'], $instanceInfos['entity_id'], $instanceInfos['field_name'])) {
183      return FALSE;
189    $entity = $this->entityTypeManager->getStorage($instanceInfos['entity_type_id'])
190      ->load($instanceInfos['entity_id']);
191
192    if (!($entity instanceof FieldableEntityInterface)) {
193      return FALSE;
196    $overriddenField = $entity->get($instanceInfos['field_name']);
197
198    if ($overriddenField->isEmpty()) {
199      return FALSE;
202    return TRUE;
StateButtons->onAttachToRoot
77  public function onAttachToRoot(string $builder_id, string $instance_id): array {
78    return $this->rebuild($builder_id);
StateButtons->onAttachToSlot
84  public function onAttachToSlot(string $builder_id, string $instance_id, string $parent_id): array {
85    return $this->rebuild($builder_id);
StateButtons->onDelete
112  public function onDelete(string $builder_id, string $parent_id): array {
113    return $this->rebuild($builder_id);
StateButtons->onHistoryChange
98  public function onHistoryChange(string $builder_id): array {
99    return $this->rebuild($builder_id);
StateButtons->onMove
91  public function onMove(string $builder_id, string $instance_id): array {
92    return $this->rebuild($builder_id);
StateButtons->onSave
70  public function onSave(string $builder_id): array {
71    return $this->reloadWithGlobalData($builder_id);
StateButtons->onUpdate
105  public function onUpdate(string $builder_id, string $instance_id): array {
106    return $this->rebuild($builder_id);
StateButtons->rebuild
271  private function rebuild(string $builder_id): array {
272    if (!$this->builder) {
276      $storage = $this->entityTypeManager->getStorage('display_builder_instance');
277      /** @var \Drupal\display_builder\InstanceInterface $builder */
278      $builder = $storage->load($builder_id);
279      $this->builder = $builder;
280    }
281
282    return $this->addOutOfBand(
282    return $this->addOutOfBand(
283      $this->build($this->builder),
284      '#' . $this->getHtmlId($builder_id),
285      'innerHTML'