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