Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 208 |
|
0.00% |
0 / 6 |
CRAP | |
0.00% |
0 / 1 |
ProfileForm | |
0.00% |
0 / 208 |
|
0.00% |
0 / 6 |
870 | |
0.00% |
0 / 1 |
form | |
0.00% |
0 / 76 |
|
0.00% |
0 / 1 |
20 | |||
submitForm | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
save | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
42 | |||
buildIslandTypeTable | |
0.00% |
0 / 29 |
|
0.00% |
0 / 1 |
20 | |||
buildIslandRow | |
0.00% |
0 / 73 |
|
0.00% |
0 / 1 |
72 | |||
copyFormValuesToEntity | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
20 |
1 | <?php |
2 | |
3 | declare(strict_types=1); |
4 | |
5 | namespace Drupal\display_builder\Form; |
6 | |
7 | use Drupal\Component\Utility\Html; |
8 | use Drupal\Component\Utility\NestedArray; |
9 | use Drupal\Core\DependencyInjection\AutowireTrait; |
10 | use Drupal\Core\Entity\EntityForm; |
11 | use Drupal\Core\Entity\EntityInterface; |
12 | use Drupal\Core\Entity\EntityWithPluginCollectionInterface; |
13 | use Drupal\Core\Form\FormStateInterface; |
14 | use Drupal\Core\Plugin\PluginFormInterface; |
15 | use Drupal\display_builder\Entity\Profile; |
16 | use Drupal\display_builder\IslandInterface; |
17 | use Drupal\display_builder\IslandType; |
18 | use Drupal\display_builder\IslandTypeViewDisplay; |
19 | use Drupal\display_builder\ProfileInterface; |
20 | use Drupal\user\RoleInterface; |
21 | |
22 | /** |
23 | * Display builder form. |
24 | */ |
25 | final class ProfileForm extends EntityForm { |
26 | |
27 | use AutowireTrait; |
28 | |
29 | /** |
30 | * {@inheritdoc} |
31 | */ |
32 | public function form(array $form, FormStateInterface $form_state): array { |
33 | $form = parent::form($form, $form_state); |
34 | /** @var \Drupal\display_builder\ProfileInterface $entity */ |
35 | $entity = $this->entity; |
36 | |
37 | $form['label'] = [ |
38 | '#type' => 'textfield', |
39 | '#title' => $this->t('Label'), |
40 | '#maxlength' => 255, |
41 | '#default_value' => $entity->label(), |
42 | '#required' => TRUE, |
43 | ]; |
44 | |
45 | $form['id'] = [ |
46 | '#type' => 'machine_name', |
47 | '#default_value' => $this->entity->id(), |
48 | '#machine_name' => [ |
49 | 'exists' => [Profile::class, 'load'], |
50 | ], |
51 | '#disabled' => !$entity->isNew(), |
52 | ]; |
53 | |
54 | $form['description'] = [ |
55 | '#type' => 'textarea', |
56 | '#title' => $this->t('Description'), |
57 | '#default_value' => $entity->get('description'), |
58 | ]; |
59 | |
60 | // Add user role access selection. Not available at creation because the |
61 | // permissions are not set yet by ProfilePermissions. |
62 | if (!$entity->isNew()) { |
63 | $roles = $this->entityTypeManager->getStorage('user_role')->loadMultiple(); |
64 | \ksort($roles); |
65 | $form['roles'] = [ |
66 | '#type' => 'checkboxes', |
67 | '#title' => $this->t('Roles'), |
68 | '#options' => \array_map(static fn (RoleInterface $role) => Html::escape((string) $role->label()), $roles), |
69 | '#default_value' => \array_keys($entity->getRoles()), |
70 | ]; |
71 | } |
72 | |
73 | // Inform on two time save for the island specific configurations. |
74 | if ($this->entity->isNew()) { |
75 | $form['islands_notice'] = [ |
76 | '#prefix' => '<div class="messages messages--warning">', |
77 | '#markup' => $this->t('Island configuration will be available only after saving this form.'), |
78 | '#suffix' => '</div>', |
79 | ]; |
80 | } |
81 | |
82 | $form['islands'] = [ |
83 | '#type' => 'details', |
84 | '#title' => $this->t('Islands configuration'), |
85 | '#tree' => TRUE, |
86 | '#open' => TRUE, |
87 | ]; |
88 | |
89 | $island_configuration = $entity->get('islands') ?? []; |
90 | |
91 | /** @var \Drupal\display_builder\IslandPluginManagerInterface $islandPluginManager */ |
92 | $islandPluginManager = \Drupal::service('plugin.manager.db_island'); // phpcs:ignore |
93 | $island_by_types = $islandPluginManager->getIslandsByTypes(); |
94 | |
95 | \ksort($island_by_types); |
96 | |
97 | foreach ($island_by_types as $type => $islands) { |
98 | $form['islands']['title_' . $type] = [ |
99 | '#type' => 'fieldgroup', |
100 | '#title' => $this->t('@type islands', ['@type' => $type]), |
101 | '#description' => IslandType::description($type), |
102 | ]; |
103 | $form['islands'][$type] = $this->buildIslandTypeTable(IslandType::from($type), $islands, $island_configuration); |
104 | } |
105 | |
106 | $form['library'] = [ |
107 | '#type' => 'select', |
108 | '#title' => $this->t('Shoelace library'), |
109 | '#description' => $this->t('Select the library mode. If local must be installed in libraries folder, see README.'), |
110 | '#options' => [ |
111 | 'cdn' => $this->t('CDN'), |
112 | 'local' => $this->t('Local'), |
113 | ], |
114 | '#default_value' => $entity->get('library'), |
115 | ]; |
116 | |
117 | $form['debug'] = [ |
118 | '#type' => 'checkbox', |
119 | '#title' => $this->t('Debug mode'), |
120 | '#description' => $this->t('Enable verbose JavaScript and error logs.'), |
121 | '#default_value' => $entity->get('debug'), |
122 | ]; |
123 | |
124 | $form['status'] = [ |
125 | '#type' => 'checkbox', |
126 | '#title' => $this->t('Enabled'), |
127 | '#default_value' => $entity->status(), |
128 | ]; |
129 | |
130 | return $form; |
131 | } |
132 | |
133 | /** |
134 | * {@inheritdoc} |
135 | */ |
136 | public function submitForm(array &$form, FormStateInterface $form_state): ProfileInterface { |
137 | parent::submitForm($form, $form_state); |
138 | |
139 | // Save user permissions. |
140 | /** @var \Drupal\display_builder\ProfileInterface $entity */ |
141 | $entity = $this->entity; |
142 | |
143 | if ($permission = $entity->getPermissionName()) { |
144 | foreach ($form_state->getValue('roles') ?? [] as $rid => $enabled) { |
145 | user_role_change_permissions($rid, [$permission => $enabled]); |
146 | } |
147 | } |
148 | |
149 | return $entity; |
150 | } |
151 | |
152 | /** |
153 | * {@inheritdoc} |
154 | */ |
155 | public function save(array $form, FormStateInterface $form_state): int { |
156 | $result = parent::save($form, $form_state); |
157 | |
158 | // Clear the plugin cache so changes are applied on front theme builder. |
159 | /** @var \Drupal\Core\Plugin\CachedDiscoveryClearerInterface $pluginCacheClearer */ |
160 | $pluginCacheClearer = \Drupal::service('plugin.cache_clearer'); // phpcs:ignore |
161 | $pluginCacheClearer->clearCachedDefinitions(); |
162 | |
163 | $message_args = ['%label' => $this->entity->label()]; |
164 | $this->messenger()->addStatus( |
165 | match ($result) { |
166 | SAVED_NEW => $this->t('Created new display builder config %label.', $message_args), |
167 | SAVED_UPDATED => $this->t('Updated display builder config %label.', $message_args), |
168 | default => '', |
169 | } |
170 | ); |
171 | |
172 | // Set the initial default configuration and stay on the form to allow |
173 | // islands configuration. |
174 | if ($result === SAVED_NEW) { |
175 | $form_state->setRedirect('entity.display_builder_profile.edit_form', ['display_builder_profile' => $this->entity->id()]); |
176 | } |
177 | elseif ($result === SAVED_UPDATED) { |
178 | $form_state->setRedirect('entity.display_builder_profile.collection'); |
179 | } |
180 | |
181 | return $result; |
182 | } |
183 | |
184 | /** |
185 | * Build island type table. |
186 | * |
187 | * @param \Drupal\display_builder\IslandType $type |
188 | * Island type from IslandType enum. |
189 | * @param array $islands |
190 | * List of island plugins. |
191 | * @param array $configuration |
192 | * Configuration of all islands from this type. |
193 | * |
194 | * @return array |
195 | * A renderable array. |
196 | */ |
197 | protected function buildIslandTypeTable(IslandType $type, array $islands, array $configuration): array { |
198 | $type = $type->value; |
199 | $table = [ |
200 | '#type' => 'table', |
201 | '#header' => [ |
202 | 'drag' => '', |
203 | 'enable' => $this->t('Enable'), |
204 | 'name' => $this->t('Island'), |
205 | 'summary' => $this->t('Configuration'), |
206 | 'region' => ($type === IslandType::View->value) ? $this->t('Region') : '', |
207 | 'actions' => $this->t('Actions'), |
208 | 'weight' => $this->t('Weight'), |
209 | ], |
210 | '#attributes' => ['id' => 'db-islands-' . $type], |
211 | '#tabledrag' => [ |
212 | [ |
213 | 'action' => 'order', |
214 | 'relationship' => 'sibling', |
215 | 'group' => 'draggable-weight-' . $type, |
216 | ], |
217 | ], |
218 | // We don't want to submit the island type level. We already know the |
219 | // type of each islands thanks to IslandInterface::getTypeId() so let's |
220 | // keep the storage flat. |
221 | '#parents' => ['islands'], |
222 | ]; |
223 | |
224 | foreach ($islands as $id => $island) { |
225 | $table[$id] = $this->buildIslandRow($island, $configuration[$id] ?? []); |
226 | } |
227 | |
228 | // Order rows by weight. |
229 | \uasort($table, static function ($a, $b) { |
230 | if (isset($a['#weight'], $b['#weight'])) { |
231 | return (int) $a['#weight'] - (int) $b['#weight']; |
232 | } |
233 | }); |
234 | |
235 | return $table; |
236 | } |
237 | |
238 | /** |
239 | * Build island row. |
240 | * |
241 | * @param \Drupal\display_builder\IslandInterface $island |
242 | * Island plugin. |
243 | * @param array $configuration |
244 | * Configuration of this specific island. |
245 | * |
246 | * @return array |
247 | * A renderable array. |
248 | */ |
249 | protected function buildIslandRow(IslandInterface $island, array $configuration): array { |
250 | $id = $island->getPluginId(); |
251 | $definition = (array) $island->getPluginDefinition(); |
252 | $type = $island->getTypeId(); |
253 | /** @var \Drupal\display_builder\IslandPluginManagerInterface $islandPluginManager */ |
254 | $islandPluginManager = \Drupal::service('plugin.manager.db_island'); // phpcs:ignore |
255 | /** @var \Drupal\display_builder\IslandConfigurationFormInterface $instance */ |
256 | $instance = $islandPluginManager->createInstance($id, $configuration); |
257 | $weight = isset($configuration['weight']) ? (string) $configuration['weight'] : '0'; |
258 | |
259 | $row = []; |
260 | $row['#attributes']['class'] = ['draggable']; |
261 | $row['#weight'] = (int) $weight; |
262 | |
263 | $row[''] = []; |
264 | $row['enable'] = [ |
265 | '#type' => 'checkbox', |
266 | '#title' => $this->t('Enable'), |
267 | '#title_display' => 'invisible', |
268 | '#default_value' => $configuration['enable'] ?? $definition['enabled_by_default'] ?? FALSE, |
269 | ]; |
270 | $row['name'] = [ |
271 | '#type' => 'inline_template', |
272 | '#template' => '<strong >{{ name }}</strong><br>{{ description }}', |
273 | '#context' => [ |
274 | 'name' => $definition['label'] ?? '', |
275 | 'description' => $definition['description'] ?? '', |
276 | ], |
277 | ]; |
278 | $row['summary'] = [ |
279 | '#markup' => \implode('<br>', $instance->configurationSummary()), |
280 | ]; |
281 | |
282 | if ($type === IslandType::View->value) { |
283 | // If new, only library is on sidebar by default. |
284 | // @todo move this position option to Island configuration. |
285 | if ($id !== 'library' && !isset($configuration['region']) && isset($definition['enabled_by_default'])) { |
286 | $region = IslandTypeViewDisplay::Main->value; |
287 | } |
288 | else { |
289 | $region = $configuration['region'] ?? NULL; |
290 | } |
291 | $row['region'] = [ |
292 | '#type' => 'radios', |
293 | '#title' => $this->t('Display'), |
294 | '#title_display' => 'invisible', |
295 | '#options' => IslandTypeViewDisplay::regions(), |
296 | '#default_value' => $region, |
297 | ]; |
298 | } |
299 | else { |
300 | $row['region'] = []; |
301 | } |
302 | |
303 | if ($island instanceof PluginFormInterface && !$this->entity->isNew()) { |
304 | $row['actions'] = [ |
305 | '#type' => 'link', |
306 | '#title' => $this->t('Configure'), |
307 | '#url' => $this->entity->toUrl('edit-plugin-form', [ |
308 | 'island_id' => $id, |
309 | 'query' => [ |
310 | 'destination' => $this->entity->toUrl()->toString(), |
311 | ], |
312 | ]), |
313 | '#attributes' => [ |
314 | 'class' => ['use-ajax', 'button', 'button--small'], |
315 | 'data-dialog-type' => 'modal', |
316 | 'data-dialog-options' => \json_encode([ |
317 | 'width' => 700, |
318 | ]), |
319 | ], |
320 | '#states' => [ |
321 | 'visible' => [ |
322 | 'input[name="islands[button][' . $id . '][enable]"]' => ['checked' => TRUE], |
323 | ], |
324 | ], |
325 | ]; |
326 | } |
327 | else { |
328 | $row['actions'] = ['#markup' => '']; |
329 | } |
330 | |
331 | $row['weight'] = [ |
332 | '#type' => 'weight', |
333 | '#default_value' => $weight, |
334 | '#title' => $this->t('Weight'), |
335 | '#title_display' => 'invisible', |
336 | '#attributes' => [ |
337 | 'class' => ['draggable-weight-' . $type], |
338 | ], |
339 | ]; |
340 | |
341 | return $row; |
342 | } |
343 | |
344 | /** |
345 | * {@inheritdoc} |
346 | */ |
347 | protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state): void { |
348 | $values = $form_state->getValues(); |
349 | |
350 | /** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $entity */ |
351 | $entity = $entity; |
352 | |
353 | if ($this->entity instanceof EntityWithPluginCollectionInterface) { |
354 | // Do not manually update values represented by plugin collections. |
355 | $values = \array_diff_key($values, $this->entity->getPluginCollections()); |
356 | } |
357 | |
358 | foreach ($values as $key => $value) { |
359 | if ($key === 'islands') { |
360 | $value = NestedArray::mergeDeep($entity->get('islands'), $value); |
361 | } |
362 | $entity->set($key, $value); |
363 | } |
364 | } |
365 | |
366 | } |