Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 88 |
|
0.00% |
0 / 15 |
CRAP | |
0.00% |
0 / 1 |
InstanceFormPanel | |
0.00% |
0 / 82 |
|
0.00% |
0 / 15 |
1482 | |
0.00% |
0 / 1 |
create | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
label | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
buildForm | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
56 | |||
build | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
6 | |||
onAttachToRoot | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
onAttachToSlot | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
onActive | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
onUpdate | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
onDelete | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isApplicable | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
alterFormValues | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
72 | |||
alterFormForComponent | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
12 | |||
getComponentMetadata | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
6 | |||
isMultipleItemsSlotSource | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
42 | |||
removeItemSelector | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | declare(strict_types=1); |
4 | |
5 | namespace Drupal\display_builder\Plugin\display_builder\Island; |
6 | |
7 | use Drupal\Core\Form\FormStateInterface; |
8 | use Drupal\Core\StringTranslation\TranslatableMarkup; |
9 | use Drupal\display_builder\Attribute\Island; |
10 | use Drupal\display_builder\InstanceInterface; |
11 | use Drupal\display_builder\IslandPluginBase; |
12 | use Drupal\display_builder\IslandType; |
13 | use Drupal\display_builder\IslandWithFormInterface; |
14 | use Drupal\display_builder\IslandWithFormTrait; |
15 | use Drupal\ui_patterns\PropTypePluginManager; |
16 | use Drupal\ui_patterns\SourcePluginManager; |
17 | use Symfony\Component\DependencyInjection\ContainerInterface; |
18 | |
19 | /** |
20 | * Instance form island plugin implementation. |
21 | */ |
22 | #[Island( |
23 | id: 'instance_form', |
24 | enabled_by_default: TRUE, |
25 | label: new TranslatableMarkup('Instance form'), |
26 | description: new TranslatableMarkup('Configuration of the active element.'), |
27 | type: IslandType::Contextual, |
28 | )] |
29 | class InstanceFormPanel extends IslandPluginBase implements IslandWithFormInterface { |
30 | |
31 | use IslandWithFormTrait; |
32 | |
33 | /** |
34 | * The prop type plugin manager. |
35 | */ |
36 | protected PropTypePluginManager $propTypeManager; |
37 | |
38 | /** |
39 | * The UI Patterns source plugin manager. |
40 | */ |
41 | protected SourcePluginManager $sourceManager; |
42 | |
43 | /** |
44 | * {@inheritdoc} |
45 | */ |
46 | public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static { |
47 | $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition); |
48 | $instance->propTypeManager = $container->get('plugin.manager.ui_patterns_prop_type'); |
49 | $instance->sourceManager = $container->get('plugin.manager.ui_patterns_source'); |
50 | |
51 | return $instance; |
52 | } |
53 | |
54 | /** |
55 | * {@inheritdoc} |
56 | */ |
57 | public function label(): string { |
58 | return 'Config'; |
59 | } |
60 | |
61 | /** |
62 | * {@inheritdoc} |
63 | */ |
64 | public function buildForm(array &$form, FormStateInterface $form_state): void { |
65 | try { |
66 | $contexts = $form_state->getBuildInfo()['args'][1] ?? []; |
67 | |
68 | $this->alterFormValues($form_state); |
69 | $source = $this->sourceManager->getSource($this->data['_node_id'], [], $this->data, $contexts); |
70 | $form = $source ? $source->settingsForm([], $form_state) : []; |
71 | |
72 | if ($this->isMultipleItemsSlotSource($this->data['source'])) { |
73 | $form = $this->removeItemSelector($form); |
74 | } |
75 | |
76 | $component_id = ($this->data['source_id'] === 'component') ? $this->data['source']['component']['component_id'] ?? NULL : NULL; |
77 | |
78 | if ($component_id && ($this->data['source_id'] === 'component')) { |
79 | $this->alterFormForComponent($form, $component_id); |
80 | } |
81 | } |
82 | catch (\Exception) { |
83 | } |
84 | } |
85 | |
86 | /** |
87 | * {@inheritdoc} |
88 | */ |
89 | public function build(InstanceInterface $builder, array $data = [], array $options = []): array { |
90 | $build = parent::build($builder, $data, $options); |
91 | |
92 | if (empty($build)) { |
93 | return $build; |
94 | } |
95 | |
96 | $build = [ |
97 | 'source' => $build, |
98 | ]; |
99 | |
100 | $build['update'] = [ |
101 | '#type' => 'button', |
102 | '#value' => 'Update', |
103 | '#submit_button' => FALSE, |
104 | '#attributes' => [ |
105 | 'type' => 'button', |
106 | 'data-wysiwyg-fix' => TRUE, |
107 | ], |
108 | ]; |
109 | |
110 | $build = $this->htmxEvents->onInstanceFormChange($build, $this->builderId, $this->getPluginId(), $this->data['_node_id']); |
111 | |
112 | return $this->htmxEvents->onInstanceUpdateButtonClick($build, $this->builderId, $this->getPluginId(), $this->data['_node_id']); |
113 | } |
114 | |
115 | /** |
116 | * {@inheritdoc} |
117 | */ |
118 | public function onAttachToRoot(string $builder_id, string $instance_id): array { |
119 | return $this->reloadWithInstanceData($builder_id, $instance_id); |
120 | } |
121 | |
122 | /** |
123 | * {@inheritdoc} |
124 | */ |
125 | public function onAttachToSlot(string $builder_id, string $instance_id, string $parent_id): array { |
126 | return $this->reloadWithInstanceData($builder_id, $instance_id); |
127 | } |
128 | |
129 | /** |
130 | * {@inheritdoc} |
131 | */ |
132 | public function onActive(string $builder_id, array $data): array { |
133 | return $this->reloadWithLocalData($builder_id, $data); |
134 | } |
135 | |
136 | /** |
137 | * {@inheritdoc} |
138 | */ |
139 | public function onUpdate(string $builder_id, string $instance_id): array { |
140 | // Reload the form itself on update. |
141 | // @todo pass \Drupal\display_builder\InstanceInterface object in |
142 | // parameters instead of loading again. |
143 | /** @var \Drupal\display_builder\InstanceInterface $builder */ |
144 | $builder = $this->entityTypeManager->getStorage('display_builder_instance')->load($builder_id); |
145 | $data = $builder->get($instance_id); |
146 | |
147 | return $this->reloadWithLocalData($builder_id, $data); |
148 | } |
149 | |
150 | /** |
151 | * {@inheritdoc} |
152 | */ |
153 | public function onDelete(string $builder_id, string $parent_id): array { |
154 | return $this->reloadWithLocalData($builder_id, []); |
155 | } |
156 | |
157 | /** |
158 | * {@inheritdoc} |
159 | */ |
160 | public function isApplicable(): bool { |
161 | return isset($this->data['source_id']) && isset($this->data['_node_id']); |
162 | } |
163 | |
164 | /** |
165 | * Alter the form values. |
166 | * |
167 | * @param \Drupal\Core\Form\FormStateInterface $form_state |
168 | * The form state. |
169 | */ |
170 | protected function alterFormValues(FormStateInterface $form_state): void { |
171 | // When this is an Ajax CALL, we directly inject the data into the source |
172 | // settings, but not during the rebuilt. |
173 | $values = $form_state->getValues(); |
174 | |
175 | if (isset($values['_drupal_ajax']) && $values['_drupal_ajax'] && !$form_state->isRebuilding()) { |
176 | if ($this->data['source_id'] !== 'component') { |
177 | $this->data['source'] = $values; |
178 | } |
179 | } |
180 | |
181 | // When rebuilding the form, we need to inject the values into the source |
182 | // settings. |
183 | if ($form_state->isRebuilding()) { |
184 | // Allow to get the posted values through ajax, and give them to the |
185 | // source plugin through its settings (essential). |
186 | if (isset($values['source'])) { |
187 | unset($values['source']); |
188 | } |
189 | |
190 | if (!empty($values)) { |
191 | $this->data['source'] = $values; |
192 | } |
193 | } |
194 | } |
195 | |
196 | /** |
197 | * Alter the form in a case of a component. |
198 | * |
199 | * @param array $form |
200 | * The form. |
201 | * @param string|null $component_id |
202 | * The component ID. |
203 | */ |
204 | protected function alterFormForComponent(array &$form, ?string $component_id): void { |
205 | if (!$component_id) { |
206 | return; |
207 | } |
208 | |
209 | if (!isset($form['component']['component_id'])) { |
210 | $form['component']['component_id'] = [ |
211 | '#type' => 'hidden', |
212 | '#value' => $component_id, |
213 | ]; |
214 | } |
215 | $form['component']['#render_slots'] = FALSE; |
216 | $form['component']['#component_id'] = $component_id; |
217 | |
218 | $form = \array_merge( |
219 | ['info' => $this->getComponentMetadata($component_id)], |
220 | $form |
221 | ); |
222 | } |
223 | |
224 | /** |
225 | * Get component metadata. |
226 | * |
227 | * @param string $component_id |
228 | * The component ID. |
229 | * |
230 | * @return array |
231 | * A renderable array. |
232 | */ |
233 | protected function getComponentMetadata(string $component_id): array { |
234 | $component = $this->sdcManager->find($component_id); |
235 | $build = []; |
236 | |
237 | if ($description = $component->metadata->description) { |
238 | $build[] = [ |
239 | '#type' => 'html_tag', |
240 | '#tag' => 'p', |
241 | '#value' => $description, |
242 | ]; |
243 | } |
244 | |
245 | return $build; |
246 | } |
247 | |
248 | /** |
249 | * Has the slot source multiple items? |
250 | * |
251 | * Some slot sources have 'multiple' items, with a select form element first, |
252 | * then an item specific form changing with Ajax. They have both a plugin_id |
253 | * key and a dynamic key with the value of the plugin_id. |
254 | * |
255 | * @param array $data |
256 | * The slot source data containing: |
257 | * - plugin_id: The plugin ID. |
258 | * |
259 | * @return bool |
260 | * Is multiple or not. |
261 | */ |
262 | private function isMultipleItemsSlotSource(array $data): bool { |
263 | if (!isset($data['plugin_id']) || !\is_string($data['plugin_id'])) { |
264 | return FALSE; |
265 | } |
266 | |
267 | if (\count($data) === 1) { |
268 | // If there is only plugin_id, without any settings, it is OK. |
269 | return TRUE; |
270 | } |
271 | // If there are settings, we need at least the one specific to the item. |
272 | $plugin_id = (string) $data['plugin_id']; |
273 | |
274 | if (isset($data[$plugin_id]) && \is_array($data[$plugin_id])) { |
275 | return TRUE; |
276 | } |
277 | |
278 | return FALSE; |
279 | } |
280 | |
281 | /** |
282 | * Remove the item selector from a form. |
283 | * |
284 | * For multiple items slot sources, we don't want to show the item selector |
285 | * since it is already selected in the slot configuration. |
286 | * |
287 | * @param array $form |
288 | * The form array. |
289 | * |
290 | * @return array |
291 | * The modified form array. |
292 | */ |
293 | private function removeItemSelector(array $form): array { |
294 | $form['plugin_id']['#type'] = 'hidden'; |
295 | unset($form['plugin_id']['#options']); |
296 | |
297 | return $form; |
298 | } |
299 | |
300 | } |