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