Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 182
n/a
0 / 0
n/a
0 / 0
0.00% covered (danger)
0.00%
0 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
EntityViewDisplayFormTrait
0.00% covered (danger)
0.00%
0 / 182
n/a
0 / 0
n/a
0 / 0
0.00% covered (danger)
0.00%
0 / 11
1722
0.00% covered (danger)
0.00%
0 / 1
 submitForm
0.00% covered (danger)
0.00%
0 / 27
n/a
0 / 0
n/a
0 / 0
0.00% covered (danger)
0.00%
0 / 1
30
 entityViewDisplayForm
0.00% covered (danger)
0.00%
0 / 27
n/a
0 / 0
n/a
0 / 0
0.00% covered (danger)
0.00%
0 / 1
20
 buildOverridesForm
0.00% covered (danger)
0.00%
0 / 49
n/a
0 / 0
n/a
0 / 0
0.00% covered (danger)
0.00%
0 / 1
20
 getAlreadyMappedFields
0.00% covered (danger)
0.00%
0 / 11
n/a
0 / 0
n/a
0 / 0
0.00% covered (danger)
0.00%
0 / 1
30
 getSourceFieldAsOptions
0.00% covered (danger)
0.00%
0 / 16
n/a
0 / 0
n/a
0 / 0
0.00% covered (danger)
0.00%
0 / 1
42
 buildExtraFieldRow
0.00% covered (danger)
0.00%
0 / 3
n/a
0 / 0
n/a
0 / 0
0.00% covered (danger)
0.00%
0 / 1
6
 buildFieldRow
0.00% covered (danger)
0.00%
0 / 3
n/a
0 / 0
n/a
0 / 0
0.00% covered (danger)
0.00%
0 / 1
6
 copyFormValuesToEntity
0.00% covered (danger)
0.00%
0 / 6
n/a
0 / 0
n/a
0 / 0
0.00% covered (danger)
0.00%
0 / 1
12
 displayBuildable
0.00% covered (danger)
0.00%
0 / 2
n/a
0 / 0
n/a
0 / 0
0.00% covered (danger)
0.00%
0 / 1
2
 createOverrideField
0.00% covered (danger)
0.00%
0 / 31
n/a
0 / 0
n/a
0 / 0
0.00% covered (danger)
0.00%
0 / 1
42
 setOverrideFieldLocked
0.00% covered (danger)
0.00%
0 / 7
n/a
0 / 0
n/a
0 / 0
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3declare(strict_types=1);
4
5namespace Drupal\display_builder_entity_view\Form;
6
7use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
8use Drupal\Core\Entity\EntityInterface;
9use Drupal\Core\Field\FieldDefinitionInterface;
10use Drupal\Core\Form\FormStateInterface;
11use Drupal\Core\StringTranslation\StringTranslationTrait;
12use Drupal\Core\StringTranslation\TranslatableMarkup;
13use Drupal\display_builder\DisplayBuildableInterface;
14use Drupal\display_builder_entity_view\Entity\DisplayBuilderEntityDisplayInterface;
15use Drupal\field\Entity\FieldConfig;
16use Drupal\field\Entity\FieldStorageConfig;
17
18/**
19 * Common methods for entity view display form.
20 */
21trait EntityViewDisplayFormTrait {
22
23  use StringTranslationTrait;
24
25  /**
26   * Form submission handler.
27   *
28   * @param array $form
29   *   An associative array containing the structure of the form.
30   * @param \Drupal\Core\Form\FormStateInterface $form_state
31   *   The current state of the form.
32   */
33  public function submitForm(array &$form, FormStateInterface $form_state): void {
34    parent::submitForm($form, $form_state);
35
36    $profile = $form_state->getValue(DisplayBuildableInterface::PROFILE_PROPERTY, '');
37    $override_status = (bool) $form_state->getValue('override_status', FALSE);
38
39    // Empty means disable main and override.
40    if (empty($profile)) {
41      $this->setOverrideFieldLocked(FALSE);
42      $this->entity->unsetThirdPartySetting('display_builder', DisplayBuildableInterface::PROFILE_PROPERTY);
43      $this->entity->unsetThirdPartySetting('display_builder', DisplayBuildableInterface::OVERRIDE_FIELD_PROPERTY);
44      $this->entity->unsetThirdPartySetting('display_builder', DisplayBuildableInterface::OVERRIDE_PROFILE_PROPERTY);
45    }
46    else {
47      $this->entity->setThirdPartySetting('display_builder', DisplayBuildableInterface::PROFILE_PROPERTY, $profile);
48    }
49
50    if ($override_status) {
51      $profile_override = $form_state->getValue(DisplayBuildableInterface::OVERRIDE_PROFILE_PROPERTY, NULL);
52
53      if ($profile_override) {
54        $this->entity->setThirdPartySetting('display_builder', DisplayBuildableInterface::OVERRIDE_PROFILE_PROPERTY, $profile_override);
55      }
56
57      $override_field = $form_state->getValue(DisplayBuildableInterface::OVERRIDE_FIELD_PROPERTY, NULL);
58
59      if (!$override_field) {
60        $field_name = \sprintf('display_%s', $this->entity->getMode());
61        $field_name = $this->createOverrideField($field_name);
62      }
63      else {
64        $field_name = $override_field;
65      }
66      // In case of field change, need to unlock the previous field.
67      $this->setOverrideFieldLocked(FALSE);
68      $this->entity->setThirdPartySetting('display_builder', DisplayBuildableInterface::OVERRIDE_FIELD_PROPERTY, $field_name);
69      $this->setOverrideFieldLocked(TRUE);
70    }
71    else {
72      $this->setOverrideFieldLocked(FALSE);
73      $this->entity->unsetThirdPartySetting('display_builder', DisplayBuildableInterface::OVERRIDE_FIELD_PROPERTY);
74      $this->entity->unsetThirdPartySetting('display_builder', DisplayBuildableInterface::OVERRIDE_PROFILE_PROPERTY);
75    }
76
77    $this->entity->save();
78    $this->localTaskManager->clearCachedDefinitions();
79    $this->routeBuilder->rebuild();
80  }
81
82  /**
83   * Provides form elements to enable Display Builder.
84   *
85   * @param array $form
86   *   The form structure.
87   *
88   * @return array
89   *   The modified form.
90   */
91  protected function entityViewDisplayForm(array $form): array {
92    $is_display_builder_enabled = $this->entity->isDisplayBuilderEnabled();
93
94    if ($is_display_builder_enabled) {
95      // Hide the table of fields.
96      $form['fields']['#access'] = FALSE;
97      $form['#fields'] = [];
98      $form['#extra'] = [];
99    }
100
101    $form['manage_display_builder'] = [
102      '#type' => 'link',
103      '#title' => $this->t('Display builder'),
104      '#weight' => -11,
105      '#attributes' => ['class' => ['button']],
106      '#url' => $this->displayBuildable()->getBuilderUrl(),
107      '#access' => $is_display_builder_enabled,
108    ];
109
110    if (isset($form['modes'])) {
111      $form['modes']['#weight'] = 0;
112    }
113
114    $form['display_builder_wrapper'] = [
115      '#type' => 'details',
116      '#open' => TRUE,
117      '#title' => $this->t('Display builder'),
118      '#weight' => 1,
119    ];
120
121    $title = new TranslatableMarkup('Enable with profile');
122    $form['display_builder_wrapper'][DisplayBuildableInterface::PROFILE_PROPERTY] = $this->displayBuildable()->buildInstanceForm(FALSE, $title, FALSE);
123
124    /** @var \Drupal\display_builder_entity_view\Entity\DisplayBuilderEntityDisplayInterface $entity */
125    $entity = $this->getEntity();
126
127    if ($entity instanceof DisplayBuilderEntityDisplayInterface) {
128      $form['display_builder_wrapper'][DisplayBuildableInterface::PROFILE_PROPERTY]['override_form'] = $this->buildOverridesForm($entity);
129    }
130
131    return $form;
132  }
133
134  /**
135   * Build the form for entity display overrides per content.
136   *
137   * @param \Drupal\display_builder_entity_view\Entity\DisplayBuilderEntityDisplayInterface $entity
138   *   The entity.
139   *
140   * @return array
141   *   The renderable form array.
142   */
143  protected function buildOverridesForm(DisplayBuilderEntityDisplayInterface $entity): array {
144    /** @var \Drupal\display_builder_entity_view\Entity\DisplayBuilderEntityDisplayInterface $overridable */
145    $overridable = $entity;
146
147    $options = $this->getSourceFieldAsOptions();
148    $overrideFieldIsSet = $overridable->getDisplayBuilderOverrideField();
149
150    $form = [
151      'override' => [
152        '#type' => 'container',
153        '#title' => $this->t('Content Override'),
154        '#states' => [
155          'visible' => [
156            ':input[name="profile"]' => ['!value' => ''],
157          ],
158        ],
159      ],
160    ];
161
162    $form['override']['override_status'] = [
163      '#type' => 'checkbox',
164      '#title' => $this->t('Enable content overrides'),
165      '#description' => $this->t('Each content will have the option to define a custom display.'),
166      '#default_value' => $overrideFieldIsSet ? TRUE : FALSE,
167    ];
168
169    $form['override']['settings'] = [
170      '#type' => 'container',
171      '#title' => $this->t('Content Override'),
172      '#states' => [
173        'visible' => [
174          ':input[name="override_status"]' => ['checked' => TRUE],
175        ],
176      ],
177    ];
178
179    $form['override']['settings'][DisplayBuildableInterface::OVERRIDE_PROFILE_PROPERTY] = [
180      '#type' => 'select',
181      '#title' => $this->t('Profile for overrides'),
182      '#description' => $this->t('The profile used for content overrides. Can be changed at any time.'),
183      '#options' => $this->displayBuildable()->getAllowedProfiles(),
184      '#default_value' => $overridable->getDisplayBuilderOverrideProfile()?->id() ?? 'default',
185    ];
186
187    if (!empty($options)) {
188      $form['override']['settings'][DisplayBuildableInterface::OVERRIDE_FIELD_PROPERTY] = [
189        '#type' => 'select',
190        '#title' => $this->t('Display Override Field'),
191        '#description' => $this->t('The field where per-content display overrides are stored.'),
192        '#options' => $options,
193        '#default_value' => $overrideFieldIsSet,
194      ];
195    }
196
197    if (!$this->displayBuildable()->isAllowed()) {
198      $form['override']['override_status']['#disabled'] = TRUE;
199      $form['override']['settings'][DisplayBuildableInterface::OVERRIDE_PROFILE_PROPERTY]['#disabled'] = TRUE;
200      $form['override']['settings'][DisplayBuildableInterface::OVERRIDE_FIELD_PROPERTY]['#disabled'] = TRUE;
201    }
202
203    return $form;
204  }
205
206  /**
207   * Returns an array of UI Patterns Source fields which are already mapped.
208   *
209   * @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $current_display
210   *   The current display.
211   *
212   * @return array
213   *   An array of field names that are already mapped to the current display.
214   */
215  protected function getAlreadyMappedFields(EntityViewDisplayInterface $current_display): array {
216    /** @var \Drupal\display_builder_entity_view\Entity\DisplayBuilderEntityDisplayInterface[] $displays */
217    $displays = $this->entityTypeManager->getStorage('entity_view_display')->loadByProperties([
218      'targetEntityType' => $current_display->getTargetEntityTypeId(),
219      'bundle' => $current_display->getTargetBundle(),
220    ]);
221    $field_names = [];
222
223    foreach ($displays as $display) {
224      if ($display instanceof DisplayBuilderEntityDisplayInterface) {
225        if ($display->isDisplayBuilderOverridable()
226          && $current_display->id() !== $display->id()) {
227          $field_names[] = $display->getDisplayBuilderOverrideField();
228        }
229      }
230    }
231
232    return $field_names;
233  }
234
235  /**
236   * Returns UI Patterns source fields as options.
237   *
238   * @return array
239   *   An associative array of field names and labels.
240   */
241  protected function getSourceFieldAsOptions(): array {
242    /** @var \Drupal\display_builder_entity_view\Entity\DisplayBuilderEntityDisplayInterface $display */
243    $display = $this->getEntity();
244    $fields = [];
245    // Load field instance definitions.
246    $field_storage_definitions = $this->entityFieldManager->getFieldDefinitions(
247      $display->getTargetEntityTypeId(),
248      $display->getTargetBundle(),
249    );
250
251    if ($display instanceof DisplayBuilderEntityDisplayInterface) {
252      $already_mapped = $this->getAlreadyMappedFields($display);
253
254      foreach ($field_storage_definitions as $field_name => $field_definition) {
255        if ($field_definition->getType() !== 'ui_patterns_source') {
256          continue;
257        }
258
259        if (\in_array($field_name, $already_mapped, TRUE)) {
260          continue;
261        }
262        $field = FieldConfig::loadByName($this->entity->getTargetEntityTypeId(), $this->entity->getTargetBundle(), $field_name);
263        $fields[$field_name] = $field ? $field->label() : $field_name;
264      }
265    }
266
267    return $fields;
268  }
269
270  /**
271   * Builds the table row structure for a single extra field.
272   *
273   * @param string $field_id
274   *   The field ID.
275   * @param array $extra_field
276   *   The pseudo-field element.
277   *
278   * @return array
279   *   A table row array.
280   */
281  protected function buildExtraFieldRow($field_id, $extra_field): array {
282    if ($this->entity->isDisplayBuilderEnabled()) {
283      return [];
284    }
285
286    return parent::buildExtraFieldRow($field_id, $extra_field);
287  }
288
289  /**
290   * Builds the table row structure for a single field.
291   *
292   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
293   *   The field definition.
294   * @param array $form
295   *   An associative array containing the structure of the form.
296   * @param \Drupal\Core\Form\FormStateInterface $form_state
297   *   The current state of the form.
298   *
299   * @return array
300   *   A table row array.
301   */
302  protected function buildFieldRow(FieldDefinitionInterface $field_definition, array $form, FormStateInterface $form_state): array {
303    if ($this->entity->isDisplayBuilderEnabled()) {
304      return [];
305    }
306
307    return parent::buildFieldRow($field_definition, $form, $form_state);
308  }
309
310  /**
311   * Copies top-level form values to entity properties.
312   *
313   * This should not change existing entity properties that are not being edited
314   * by this form.
315   *
316   * @param \Drupal\Core\Entity\EntityInterface $entity
317   *   The entity the current form should operate upon.
318   * @param array $form
319   *   A nested array of form elements comprising the form.
320   * @param \Drupal\Core\Form\FormStateInterface $form_state
321   *   The current state of the form.
322   *
323   * @see \Drupal\Core\Form\ConfigFormBase::copyFormValuesToConfig()
324   */
325  protected function copyFormValuesToEntity(EntityInterface $entity, array $form, FormStateInterface $form_state): void {
326    /** @var \Drupal\display_builder_entity_view\Entity\DisplayBuilderEntityDisplayInterface $entity */
327    // Do not process field values if Display Builder is or will be enabled.
328    $set_enabled = (bool) $form_state->getValue(['display_builder', 'enabled'], FALSE);
329    $already_enabled = $entity->isDisplayBuilderEnabled();
330
331    if ($already_enabled || $set_enabled) {
332      $form['#fields'] = [];
333      $form['#extra'] = [];
334    }
335
336    parent::copyFormValuesToEntity($entity, $form, $form_state);
337  }
338
339  /**
340   * Gets the display buildable manager.
341   *
342   * @return \Drupal\display_builder\DisplayBuildableInterface
343   *   The manager for display buildable.
344   */
345  protected function displayBuildable(): DisplayBuildableInterface {
346    /** @var \Drupal\display_builder\DisplayBuildableInterface $buildable */
347    $buildable = $this->displayBuildableManager->createInstance('entity_view', ['display' => $this->getEntity()]);
348
349    return $buildable;
350  }
351
352  /**
353   * Create a field based on a name.
354   *
355   * @param string $field_name
356   *   The field name, field prefix will be added.
357   */
358  private function createOverrideField(string $field_name): string {
359    try {
360      $field_prefix = $this->configFactory()->get('field_ui.settings')->get('field_prefix');
361    }
362    catch (\Throwable $th) {
363      $field_prefix = 'field_';
364    }
365
366    $field_name = $field_prefix . $field_name;
367
368    if (\strlen($field_name) > FieldStorageConfig::NAME_MAX_LENGTH) {
369      $field_name = \substr($field_name, 0, FieldStorageConfig::NAME_MAX_LENGTH);
370    }
371    $field_storage = FieldStorageConfig::loadByName($this->entity->getTargetEntityTypeId(), $field_name);
372
373    if (!$field_storage) {
374      $field_storage = FieldStorageConfig::create([
375        'field_name' => $field_name,
376        'entity_type' => $this->entity->getTargetEntityTypeId(),
377        'type' => 'ui_patterns_source',
378      ]);
379      $field_storage->setTranslatable(TRUE);
380      $field_storage->setCardinality(-1);
381      $field_storage->save();
382    }
383
384    // Add the field prefix to the field name and cut to max size if needed.
385    $field_definition = FieldConfig::loadByName($this->entity->getTargetEntityTypeId(), $this->entity->getTargetBundle(), $field_name);
386
387    if (!$field_definition) {
388      $view_mode_id = $this->entity->getMode();
389      $view_mode_id = ($view_mode_id === 'default') ? 'full' : $view_mode_id;
390      $view_mode_id = \sprintf('%s.%s', $this->entity->getTargetEntityTypeId(), $view_mode_id);
391      $view_mode = $this->entityTypeManager->getStorage('entity_view_mode')->load($view_mode_id);
392      $field_definition = FieldConfig::create([
393        'field_storage' => $field_storage,
394        'bundle' => $this->entity->getTargetBundle(),
395        'field_name' => $field_name,
396        'label' => $this->t('@display display override', ['@display' => $view_mode->label()]),
397      ]);
398      $field_definition->setTranslatable(TRUE);
399      $field_definition->save();
400    }
401
402    return $field_name;
403  }
404
405  /**
406   * Set the lock status of the override field if it exists.
407   *
408   * @param bool $locked
409   *   Whether to lock or unlock the field.
410   */
411  private function setOverrideFieldLocked(bool $locked): void {
412    $field_name = $this->entity->getDisplayBuilderOverrideField();
413
414    if (!$field_name) {
415      return;
416    }
417    $field_storage = FieldStorageConfig::loadByName($this->entity->getTargetEntityTypeId(), $field_name);
418
419    if ($field_storage) {
420      $field_storage->setLocked($locked);
421      $field_storage->save();
422    }
423  }
424
425}