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

Paths

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