Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 65
0.00% covered (danger)
0.00%
0 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
StateButtons
0.00% covered (danger)
0.00%
0 / 59
0.00% covered (danger)
0.00%
0 / 11
420
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
2
 build
0.00% covered (danger)
0.00%
0 / 27
0.00% covered (danger)
0.00%
0 / 1
30
 onSave
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
2
 onAttachToSlot
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
2
 onHistoryChange
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
2
 onDelete
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 / 1
30
 rebuild
0.00% covered (danger)
0.00%
0 / 9
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\IslandPluginBase;
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('Buttons to publish and reset the display.'),
25  type: IslandType::Button,
26)]
27class StateButtons extends IslandPluginBase {
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    $builder_id = (string) $builder->id();
49
50    if (!$builder->canSaveContextsRequirement()) {
51      return [];
52    }
53
54    $buttonGroup = [
55      '#type' => 'component',
56      '#component' => 'display_builder:button_group',
57      '#slots' => [
58        'buttons' => [],
59      ],
60    ];
61
62    $hasSave = $builder->hasSave();
63    $saveIsCurrent = $hasSave ? $builder->saveIsCurrent() : FALSE;
64
65    if (!$saveIsCurrent) {
66      $save = $this->buildButton('', 'save', 'floppy', $this->t('Save this display in current state, this will publish your display. (shortcut: S)'), ['S' => $this->t('Save this display (shift+S)')]);
67      $save['#props']['variant'] = 'primary';
68      $save['#attributes']['outline'] = TRUE;
69      $buttonGroup['#slots']['buttons'][] = $this->htmxEvents->onSave($save, $builder_id);
70
71      $restore = $this->buildButton('', 'restore', 'arrow-repeat', $this->t('Restore to last saved version'));
72      $restore['#props']['variant'] = 'warning';
73      $restore['#attributes']['outline'] = TRUE;
74      $buttonGroup['#slots']['buttons'][] = $this->htmxEvents->onReset($restore, $builder_id);
75    }
76
77    if ($this->isOverridden($builder_id)) {
78      $save = $this->buildButton('', 'revert', 'box-arrow-in-down', $this->t('Revert to default display (not overridden)'));
79      $revert['#props']['variant'] = 'danger';
80      $revert['#attributes']['outline'] = TRUE;
81      $buttonGroup['#slots']['buttons'][] = $this->htmxEvents->onRevert($revert, $builder_id);
82    }
83
84    return $buttonGroup;
85  }
86
87  /**
88   * {@inheritdoc}
89   */
90  public function onSave(string $builder_id): array {
91    return $this->reloadWithGlobalData($builder_id);
92  }
93
94  /**
95   * {@inheritdoc}
96   */
97  public function onAttachToRoot(string $builder_id, string $instance_id): array {
98    return $this->rebuild($builder_id);
99  }
100
101  /**
102   * {@inheritdoc}
103   */
104  public function onAttachToSlot(string $builder_id, string $instance_id, string $parent_id): array {
105    return $this->rebuild($builder_id);
106  }
107
108  /**
109   * {@inheritdoc}
110   */
111  public function onMove(string $builder_id, string $instance_id): array {
112    return $this->rebuild($builder_id);
113  }
114
115  /**
116   * {@inheritdoc}
117   */
118  public function onHistoryChange(string $builder_id): array {
119    return $this->rebuild($builder_id);
120  }
121
122  /**
123   * {@inheritdoc}
124   */
125  public function onUpdate(string $builder_id, string $instance_id): array {
126    return $this->rebuild($builder_id);
127  }
128
129  /**
130   * {@inheritdoc}
131   */
132  public function onDelete(string $builder_id, string $parent_id): array {
133    return $this->rebuild($builder_id);
134  }
135
136  /**
137   * Check if the display builder is on an entity override.
138   *
139   * @param string $builder_id
140   *   The ID of the builder.
141   *
142   * @return bool
143   *   Returns TRUE if the display builder is on an entity override.
144   */
145  protected function isOverridden(string $builder_id): bool {
146    if (!$this->moduleHandler->moduleExists('display_builder_entity_view')) {
147      return FALSE;
148    }
149
150    $instanceInfos = DisplayBuilderItemList::checkInstanceId($builder_id);
151
152    if (!isset($instanceInfos['entity_type_id'], $instanceInfos['entity_id'], $instanceInfos['field_name'])) {
153      return FALSE;
154    }
155
156    // Do not get the profile entity ID from Instance context because the
157    // data stored there is not reliable yet.
158    // See: https://www.drupal.org/project/display_builder/issues/3544545
159    $entity = $this->entityTypeManager->getStorage($instanceInfos['entity_type_id'])
160      ->load($instanceInfos['entity_id']);
161
162    if (!($entity instanceof FieldableEntityInterface)) {
163      return FALSE;
164    }
165
166    $overriddenField = $entity->get($instanceInfos['field_name']);
167
168    if ($overriddenField->isEmpty()) {
169      return FALSE;
170    }
171
172    return TRUE;
173  }
174
175  /**
176   * Rebuilds the island with the given builder ID.
177   *
178   * @param string $builder_id
179   *   The ID of the builder.
180   *
181   * @return array
182   *   The rebuilt island.
183   */
184  private function rebuild(string $builder_id): array {
185    if (!$this->builder) {
186      // @todo pass \Drupal\display_builder\InstanceInterface object in
187      // parameters instead of loading again.
188      /** @var \Drupal\display_builder\InstanceStorage $storage */
189      $storage = $this->entityTypeManager->getStorage('display_builder_instance');
190      /** @var \Drupal\display_builder\InstanceInterface $builder */
191      $builder = $storage->load($builder_id);
192      $this->builder = $builder;
193    }
194
195    return $this->addOutOfBand(
196      $this->build($this->builder),
197      '#' . $this->getHtmlId($builder_id),
198      'innerHTML'
199    );
200  }
201
202}