Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 208
0.00% covered (danger)
0.00%
0 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
ProfileForm
0.00% covered (danger)
0.00%
0 / 208
0.00% covered (danger)
0.00%
0 / 6
870
0.00% covered (danger)
0.00%
0 / 1
 form
0.00% covered (danger)
0.00%
0 / 76
0.00% covered (danger)
0.00%
0 / 1
20
 submitForm
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 save
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
42
 buildIslandTypeTable
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 1
20
 buildIslandRow
0.00% covered (danger)
0.00%
0 / 73
0.00% covered (danger)
0.00%
0 / 1
72
 copyFormValuesToEntity
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
1<?php
2
3declare(strict_types=1);
4
5namespace Drupal\display_builder\Form;
6
7use Drupal\Component\Utility\Html;
8use Drupal\Component\Utility\NestedArray;
9use Drupal\Core\DependencyInjection\AutowireTrait;
10use Drupal\Core\Entity\EntityForm;
11use Drupal\Core\Entity\EntityInterface;
12use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
13use Drupal\Core\Form\FormStateInterface;
14use Drupal\Core\Plugin\PluginFormInterface;
15use Drupal\display_builder\Entity\Profile;
16use Drupal\display_builder\IslandInterface;
17use Drupal\display_builder\IslandType;
18use Drupal\display_builder\IslandTypeViewDisplay;
19use Drupal\display_builder\ProfileInterface;
20use Drupal\user\RoleInterface;
21
22/**
23 * Display builder form.
24 */
25final 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}