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}

Branches

Below are the source code lines that represent each code branch as identified by Xdebug. Please note a branch is not necessarily coterminous with a line, a line may contain multiple branches and therefore show up more than once. Please also be aware that some branches may be implicit rather than explicit, e.g. an if statement always has an else as part of its logical flow even if you didn't write one.