Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
16.13% covered (danger)
16.13%
15 / 93
7.55% covered (danger)
7.55%
4 / 53
9.30% covered (danger)
9.30%
4 / 43
13.79% covered (danger)
13.79%
4 / 29
CRAP
0.00% covered (danger)
0.00%
0 / 1
IslandPluginBase
16.13% covered (danger)
16.13%
15 / 93
7.55% covered (danger)
7.55%
4 / 53
9.30% covered (danger)
9.30%
4 / 43
13.79% covered (danger)
13.79%
4 / 29
1233.74
0.00% covered (danger)
0.00%
0 / 1
 __construct
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
 create
100.00% covered (success)
100.00%
10 / 10
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
 build
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
20
 afterBuild
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 isApplicable
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 label
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
 keyboardShortcuts
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
 getTypeId
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
 getHtmlId
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
 getIcon
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
 onActive
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
 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
 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
 onPresetSave
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
 getConfiguration
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
 setConfiguration
100.00% covered (success)
100.00%
1 / 1
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
 defaultConfiguration
100.00% covered (success)
100.00%
1 / 1
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
 configurationSummary
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
 alterRenderable
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
 reloadWithGlobalData
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
 reloadWithLocalData
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
 reloadWithInstanceData
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
 formBuilder
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
 getArgs
0.00% covered (danger)
0.00%
0 / 6
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;
6
7use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
8use Drupal\Component\Plugin\PluginBase;
9use Drupal\Core\Entity\EntityTypeManagerInterface;
10use Drupal\Core\Form\FormBuilderInterface;
11use Drupal\Core\Form\FormState;
12use Drupal\Core\Form\FormStateInterface;
13use Drupal\Core\StringTranslation\StringTranslationTrait;
14use Drupal\Core\Theme\ComponentPluginManager;
15use Drupal\ui_patterns\SourcePluginManager;
16use Psr\Log\LoggerInterface;
17use Symfony\Component\DependencyInjection\ContainerInterface;
18
19/**
20 * Base class for island plugins.
21 */
22abstract class IslandPluginBase extends PluginBase implements IslandInterface {
23
24  use HtmxTrait;
25  use RenderableBuilderTrait;
26  use StringTranslationTrait;
27
28  /**
29   * The island data.
30   */
31  protected array $data;
32
33  /**
34   * The builder id.
35   */
36  protected string $builderId;
37
38  /**
39   * The builder instance entity.
40   */
41  protected ?InstanceInterface $builder = NULL;
42
43  /**
44   * The current island id which trigger action.
45   */
46  protected string $currentIslandId;
47
48  /**
49   * The tree node id (when the island is executed in the context of a node).
50   */
51  protected ?string $nodeId = NULL;
52
53  /**
54   * The form builder.
55   */
56  protected ?FormBuilderInterface $formBuilder = NULL;
57
58  /**
59   * {@inheritdoc}
60   */
61  public function __construct(
62    array $configuration,
63    $plugin_id,
64    $plugin_definition,
65    protected ComponentPluginManager $sdcManager,
66    protected HtmxEvents $htmxEvents,
67    protected EntityTypeManagerInterface $entityTypeManager,
68    protected SourcePluginManager $sourceManager,
69    protected LoggerInterface $logger,
70  ) {
71    parent::__construct($configuration, $plugin_id, $plugin_definition);
72    $this->data = $configuration;
73    $this->setConfiguration($configuration);
74  }
75
76  /**
77   * {@inheritdoc}
78   */
79  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
80    return new static(
81      $configuration,
82      $plugin_id,
83      $plugin_definition,
84      $container->get('plugin.manager.sdc'),
85      $container->get('display_builder.htmx_events'),
86      $container->get('entity_type.manager'),
87      $container->get('plugin.manager.ui_patterns_source'),
88      $container->get('logger.factory')->get('display_builder')
89    );
90  }
91
92  /**
93   * {@inheritdoc}
94   */
95  public function build(InstanceInterface $builder, array $data = [], array $options = []): array {
96    $this->builder = $builder;
97
98    $builder_id = (string) $builder->id();
99    $this->builderId = $builder_id;
100    $this->nodeId = $data['node_id'] ?? NULL;
101
102    // First, get specific data for the plugin.
103    if (isset($data['third_party_settings'][$this->getPluginId()])) {
104      $this->data = $data['third_party_settings'][$this->getPluginId()];
105    }
106    // Otherwise, fallback on global data.
107    else {
108      $this->data = $data;
109    }
110
111    if (!$this->isApplicable()) {
112      return [];
113    }
114
115    if (!$this instanceof IslandWithFormInterface) {
116      return [];
117    }
118
119    $contexts = $this->configuration['contexts'] ?? [];
120
121    $form_state = new FormState();
122
123    // We have to force form to not rebuild, otherwise, we are losing data of an
124    // island plugin when another is submitted.
125    // Example: submitting Styles Panel make lose default form values for
126    // Instance Form Panel.
127    $form_state->setRebuild(FALSE);
128    $form_state->setExecuted();
129
130    $form_state->addBuildInfo('args', [$this->getArgs(), $contexts, $options]);
131    $form_state->setTemporaryValue('gathered_contexts', $contexts);
132
133    $build = $this->formBuilder()->buildForm($this::getFormClass(), $form_state);
134
135    return $this->afterBuild($build, $form_state);
136  }
137
138  /**
139   * {@inheritdoc}
140   */
141  public function afterBuild(array $element, FormStateInterface $form_state): array {
142    if (!$this->isApplicable()) {
143      return [];
144    }
145
146    $definition = $this->getPluginDefinition();
147    $island_id = $definition instanceof PluginDefinitionInterface ? $definition->id() : ($definition['id'] ?? '');
148
149    return $this->htmxEvents->onThirdPartyFormChange($element, $this->builderId, $this->nodeId, $island_id);
150  }
151
152  /**
153   * {@inheritdoc}
154   */
155  public function isApplicable(): bool {
156    $definition = $this->getPluginDefinition();
157
158    return $this->nodeId !== NULL && \is_array($definition) && !empty($this->data);
159  }
160
161  /**
162   * {@inheritdoc}
163   */
164  public function label(): string {
165    // Cast the label to a string since it is a TranslatableMarkup object.
166    return (string) $this->pluginDefinition['label'];
167  }
168
169  /**
170   * {@inheritdoc}
171   */
172  public static function keyboardShortcuts(): array {
173    return [];
174  }
175
176  /**
177   * {@inheritdoc}
178   */
179  public function getTypeId(): string {
180    return $this->pluginDefinition['type']->value;
181  }
182
183  /**
184   * {@inheritdoc}
185   */
186  public function getHtmlId(string $builder_id): string {
187    return \implode('-', ['island', $builder_id, $this->pluginDefinition['id']]);
188  }
189
190  /**
191   * {@inheritdoc}
192   */
193  public function getIcon(): ?string {
194    return $this->pluginDefinition['icon'] ?? NULL;
195  }
196
197  /**
198   * {@inheritdoc}
199   */
200  public function onAttachToRoot(string $instance_id, string $node_id): array {
201    return [];
202  }
203
204  /**
205   * {@inheritdoc}
206   */
207  public function onAttachToSlot(string $instance_id, string $node_id, string $parent_id): array {
208    return [];
209  }
210
211  /**
212   * {@inheritdoc}
213   */
214  public function onMove(string $instance_id, string $node_id): array {
215    return [];
216  }
217
218  /**
219   * {@inheritdoc}
220   */
221  public function onActive(string $instance_id, array $data): array {
222    return [];
223  }
224
225  /**
226   * {@inheritdoc}
227   */
228  public function onUpdate(string $instance_id, string $node_id): array {
229    return [];
230  }
231
232  /**
233   * {@inheritdoc}
234   */
235  public function onDelete(string $instance_id, string $parent_id): array {
236    return [];
237  }
238
239  /**
240   * {@inheritdoc}
241   */
242  public function onHistoryChange(string $instance_id): array {
243    return [];
244  }
245
246  /**
247   * {@inheritdoc}
248   */
249  public function onSave(string $instance_id): array {
250    return [];
251  }
252
253  /**
254   * {@inheritdoc}
255   */
256  public function onPresetSave(string $instance_id): array {
257    return [];
258  }
259
260  /**
261   * {@inheritdoc}
262   */
263  public function getConfiguration(): array {
264    return \array_merge($this->defaultConfiguration(), $this->configuration);
265  }
266
267  /**
268   * {@inheritdoc}
269   */
270  public function setConfiguration(array $configuration): void {
271    $this->configuration = \array_merge($this->defaultConfiguration(), $configuration);
272  }
273
274  /**
275   * {@inheritdoc}
276   */
277  public function defaultConfiguration(): array {
278    return [];
279  }
280
281  /**
282   * {@inheritdoc}
283   */
284  public function configurationSummary(): array {
285    return [];
286  }
287
288  /**
289   * {@inheritdoc}
290   */
291  public function alterRenderable(InstanceInterface $instance, array $build): array {
292    return $build;
293  }
294
295  /**
296   * Helper method to reload island with global data.
297   *
298   * @param string $builder_id
299   *   The builder ID.
300   *
301   * @return array
302   *   Returns a render array with out-of-band commands.
303   */
304  protected function reloadWithGlobalData(string $builder_id): array {
305    if (!$this->builder) {
306      // @todo pass \Drupal\display_builder\InstanceInterface object in
307      // parameters instead of loading again.
308      /** @var \Drupal\display_builder\InstanceInterface $builder */
309      $builder = $this->entityTypeManager->getStorage('display_builder_instance')->load($builder_id);
310      $this->builder = $builder;
311    }
312    $data = $this->builder->getCurrentState();
313
314    return $this->addOutOfBand(
315      $this->build($this->builder, $data),
316      '#' . $this->getHtmlId($builder_id),
317      'innerHTML'
318    );
319  }
320
321  /**
322   * Helper method to reload island with provided local data.
323   *
324   * @param string $builder_id
325   *   The builder ID.
326   * @param array $data
327   *   The local data array to use for building the island.
328   *
329   * @return array
330   *   Returns a render array with out-of-band commands.
331   */
332  protected function reloadWithLocalData(string $builder_id, array $data): array {
333    if (!$this->builder) {
334      // @todo pass \Drupal\display_builder\InstanceInterface object in
335      // parameters instead of loading again.
336      /** @var \Drupal\display_builder\InstanceInterface $builder */
337      $builder = $this->entityTypeManager->getStorage('display_builder_instance')->load($builder_id);
338      $this->builder = $builder;
339    }
340
341    return $this->addOutOfBand(
342      $this->build($this->builder, $data),
343      '#' . $this->getHtmlId($builder_id),
344      'innerHTML'
345    );
346  }
347
348  /**
349   * Helper method to reload island with instance-specific data.
350   *
351   * @param string $instance_id
352   *   The Display Builder instance ID.
353   * @param string $node_id
354   *   The tree node ID.
355   *
356   * @return array
357   *   Returns a render array with out-of-band commands.
358   */
359  protected function reloadWithInstanceData(string $instance_id, string $node_id): array {
360    if (!$this->builder) {
361      // @todo pass \Drupal\display_builder\InstanceInterface object in
362      // parameters instead of loading again.
363      /** @var \Drupal\display_builder\InstanceInterface $builder */
364      $builder = $this->entityTypeManager->getStorage('display_builder_instance')->load($instance_id);
365      $this->builder = $builder;
366    }
367    $data = $this->builder->getNode($node_id);
368
369    return $this->addOutOfBand(
370      $this->build($this->builder, $data),
371      '#' . $this->getHtmlId($instance_id),
372      'innerHTML'
373    );
374  }
375
376  /**
377   * Returns the form builder service.
378   *
379   * @return \Drupal\Core\Form\FormBuilderInterface
380   *   The form builder service.
381   */
382  protected function formBuilder() {
383    if (!$this->formBuilder) {
384      $this->formBuilder = \Drupal::formBuilder();
385    }
386
387    return $this->formBuilder;
388  }
389
390  /**
391   * Get args passed to plugin.
392   *
393   * @return array
394   *   Array of arguments.
395   */
396  private function getArgs(): array {
397    return [
398      'island_id' => $this->getPluginId(),
399      'builder_id' => $this->builderId,
400      'instance_id' => $this->nodeId,
401      'instance' => $this->data,
402    ];
403  }
404
405}