Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 145
0.00% covered (danger)
0.00%
0 / 81
0.00% covered (danger)
0.00%
0 / 77
0.00% covered (danger)
0.00%
0 / 16
CRAP
0.00% covered (danger)
0.00%
0 / 1
BuilderDataConverter
0.00% covered (danger)
0.00%
0 / 145
0.00% covered (danger)
0.00%
0 / 81
0.00% covered (danger)
0.00%
0 / 77
0.00% covered (danger)
0.00%
0 / 16
2070
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 convertFromManageDisplay
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
30
 convertFromLayoutBuilder
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 convertSingleField
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 convertExtraField
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 convertUiPatternsLayout
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
 extractThirdPartySettings
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
20
 moveUiStylesAttributesSource
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 convertLayout
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
 extractUiStylesData
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
12
 convertLayoutBuilderComponent
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
56
 convertFieldBlock
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 convertExtraFieldBlock
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 convertUiPatternsBlock
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
6
 convertBlock
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 updateContextMapping
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
30
1<?php
2
3declare(strict_types=1);
4
5namespace Drupal\display_builder_entity_view;
6
7use Drupal\Component\Plugin\Definition\PluginDefinitionInterface;
8use Drupal\Component\Utility\NestedArray;
9use Drupal\Component\Utility\SortArray;
10use Drupal\Core\Block\BlockPluginInterface;
11use Drupal\Core\Entity\EntityFieldManagerInterface;
12use Drupal\layout_builder\Section;
13use Drupal\layout_builder\SectionComponent;
14
15/**
16 * Convert data between Layout Builder and Display Builder.
17 */
18class BuilderDataConverter {
19
20  public function __construct(
21    protected EntityFieldManagerInterface $entityFieldManager,
22  ) {}
23
24  /**
25   * Convert "Manage display" formatters to UI Patterns sources.
26   *
27   * @param string $entity_type
28   *   Entity type ID.
29   * @param string $bundle
30   *   Bundle.
31   * @param array $fields
32   *   Configuration of activated fields.
33   *
34   * @return array
35   *   List of UI Patterns sources.
36   */
37  public function convertFromManageDisplay(string $entity_type, string $bundle, array $fields): array {
38    $sources = [];
39    \uasort($fields, [SortArray::class, 'sortByWeightElement']);
40
41    $definitions = $this->entityFieldManager->getFieldDefinitions($entity_type, $bundle);
42
43    foreach ($fields as $field_id => $field) {
44      if (!isset($field['type'])) {
45        // Probably an extra field.
46        $sources[] = $this->convertExtraField($field_id);
47
48        // @todo Do we need to check if it is really an extra field?
49        continue;
50      }
51
52      if (isset($definitions[$field_id]) && !$definitions[$field_id]->isDisplayConfigurable('view')) {
53        // Hidden from Manage Display.
54        continue;
55      }
56      $sources[] = $this->convertSingleField($entity_type, $bundle, $field_id, $field);
57    }
58
59    return $sources;
60  }
61
62  /**
63   * Convert from Layout Builder.
64   *
65   * @param array $sections
66   *   A list of layout builder section, so the root of Layout Builder data.
67   *
68   * @return array
69   *   A list of UI Patterns sources.
70   */
71  public function convertFromLayoutBuilder(array $sections): array {
72    $sources = [];
73
74    foreach ($sections as $section) {
75      $deriver = $section->getLayout()->getPluginDefinition()->getDeriver();
76
77      if ($deriver === 'Drupal\ui_patterns_layouts\Plugin\Derivative\ComponentLayout') {
78        $sources[] = $this->convertUiPatternsLayout($section);
79
80        continue;
81      }
82      $sources[] = $this->convertLayout($section);
83    }
84
85    return $sources;
86  }
87
88  /**
89   * Convert field formatter plugin data to a source.
90   *
91   * Used for conversion from both Manage Display and Layout Builder.
92   *
93   * @param string $entity_type
94   *   Entity type ID.
95   * @param string $bundle
96   *   Bundle.
97   * @param string $field
98   *   Field name.
99   * @param array $data
100   *   Field formatter data.
101   *
102   * @return array
103   *   A single UI Patterns source.
104   */
105  protected function convertSingleField(string $entity_type, string $bundle, string $field, array $data): array {
106    $derivable_context = \implode(':', [$entity_type, $bundle, $field]);
107    $source = [
108      'source_id' => 'field_formatter:' . $derivable_context,
109      'source' => $data,
110    ];
111
112    return [
113      'source_id' => 'entity_field',
114      'source' => [
115        'derivable_context' => 'field:' . $derivable_context,
116        'field:' . $derivable_context => [
117          'value' => [
118            'sources' => [$source],
119          ],
120        ],
121      ],
122    ];
123  }
124
125  /**
126   * Convert an extra field.
127   *
128   * @param string $field_name
129   *   The machine name of the extra field.
130   *
131   * @return array
132   *   A single UI Patterns source.
133   */
134  protected function convertExtraField(string $field_name): array {
135    return [
136      'source_id' => 'extra_field',
137      'source' => [
138        'field' => $field_name,
139      ],
140    ];
141  }
142
143  /**
144   * Convert UI Patterns layout plugin.
145   *
146   * @param \Drupal\layout_builder\Section $section
147   *   A single layout builder section.
148   *
149   * @return array
150   *   A single UI Patterns source.
151   */
152  protected function convertUiPatternsLayout(Section $section): array {
153    $slots = [];
154    $components = $section->getComponents();
155
156    foreach ($components as $component) {
157      $source = $this->convertLayoutBuilderComponent($component);
158
159      if ($source) {
160        $source = $this->extractUiStylesData($component, $source);
161        $slots[$component->getRegion()]['sources'][] = $source;
162      }
163    }
164
165    $data = [
166      'source_id' => 'component',
167      'source' => [
168        'component' => $section->getLayoutSettings()['ui_patterns'],
169      ],
170    ];
171    // Sometimes, this value is null, so let's override it.
172    $data['source']['component']['component_id'] = \str_replace('ui_patterns:', '', $section->getLayoutId());
173
174    $data = $this->extractThirdPartySettings($data, $section);
175    $data = $this->moveUiStylesAttributesSource($data);
176
177    if ($slots) {
178      $data['source']['component']['slots'] = $slots;
179    }
180
181    return $data;
182  }
183
184  /**
185   * Extract third party settings.
186   *
187   * @param array $data
188   *   A single UI Patterns source.
189   * @param \Drupal\layout_builder\Section $section
190   *   A single layout builder section.
191   *
192   * @return array
193   *   A single UI Patterns source.
194   */
195  protected function extractThirdPartySettings(array $data, Section $section): array {
196    foreach ($section->getThirdPartyProviders() ?: [] as $provider_id) {
197      // In Layout builder, ThirdPartyProviders are Drupal modules. In Display
198      // Builder, they are Island plugins. So, 'ui_styles' become 'styles'. We
199      // are not calling the island 'ui_styles' in order to be ready when the
200      // API will land in Core. See https://www.drupal.org/i/3517033.
201      if ($provider_id === 'ui_styles') {
202        $data['third_party_settings']['styles'] = $section->getThirdPartySettings($provider_id);
203
204        continue;
205      }
206      $data['third_party_settings'][$provider_id] = $section->getThirdPartySettings($provider_id);
207    }
208
209    return $data;
210  }
211
212  /**
213   * Move data from UI Style's attribute source to 3rd party settings.
214   *
215   * @param array $data
216   *   A UI Patterns source data.
217   *
218   * @return array
219   *   The same UI Patterns source data, maybe altered.
220   */
221  protected function moveUiStylesAttributesSource(array $data): array {
222    if ($data['source']['component']['props']['attributes']['source_id'] !== 'ui_styles_attributes') {
223      return $data;
224    }
225    // We keep this order because it is the priority order in UI styles.
226    $styles_1 = [
227      'selected' => $data['source']['component']['props']['attributes']['source']['styles']['selected'],
228      'extra' => $data['source']['component']['props']['attributes']['source']['extra'],
229    ];
230    $styles_2 = $data['third_party_settings']['styles'] ?? [];
231    $data['third_party_settings']['styles'] = NestedArray::mergeDeep($styles_1, $styles_2);
232    $data['source']['component']['props']['attributes'] = [];
233
234    return $data;
235  }
236
237  /**
238   * Convert regular layout plugin.
239   *
240   * @param \Drupal\layout_builder\Section $section
241   *   A single layout builder section.
242   *
243   * @return array
244   *   A single UI Patterns source.
245   */
246  protected function convertLayout(Section $section): array {
247    $slots = [];
248    $components = $section->getComponents();
249
250    foreach ($components as $component) {
251      $source = $this->convertLayoutBuilderComponent($component);
252      $source = $this->extractUiStylesData($component, $source);
253
254      if ($source) {
255        $source = $this->extractUiStylesData($component, $source);
256        $slots[$component->getRegion()][] = $source;
257      }
258    }
259
260    $data = [
261      'source_id' => 'layout',
262      'source' => [
263        'layout_id' => $section->getLayoutId(),
264        'settings' => $section->getLayoutSettings(),
265      ],
266    ];
267
268    if ($slots) {
269      $data['source']['regions'] = $slots;
270    }
271
272    $data = $this->extractThirdPartySettings($data, $section);
273
274    return $data;
275  }
276
277  /**
278   * Extract UI Styles data.
279   *
280   * In Display Builder, we don't render through ThemeManager::render() so we
281   * don't load block.html.twig and we don't have block wrapper and block title
282   * attributes. So, let's ignore title styles and let's merge wrapper styles
283   * with block styles (which keep priority).
284   *
285   * @param \Drupal\layout_builder\SectionComponent $component
286   *   A single layout builder component.
287   * @param array $source
288   *   The already converted UI Patterns source.
289   *
290   * @return array
291   *   The altered UI Patterns source.
292   */
293  protected function extractUiStylesData(SectionComponent $component, array $source): array {
294    $additional = $component->toArray()['additional'] ?? [];
295
296    $styles = \array_unique(\array_merge($additional['ui_styles_wrapper'] ?? [], $additional['ui_styles'] ?? []));
297
298    if ($styles) {
299      $source['third_party_settings']['styles']['selected'] = $styles;
300    }
301
302    $extra = \trim(($additional['ui_styles_wrapper_extra'] ?? '') . ' ' . ($additional['ui_styles_extra'] ?? ''));
303
304    if ($extra) {
305      $source['third_party_settings']['styles']['extra'] = $extra;
306    }
307
308    return $source;
309  }
310
311  /**
312   * Convert layout builder component.
313   *
314   * @param \Drupal\layout_builder\SectionComponent $component
315   *   A single layout builder component.
316   *
317   * @return array
318   *   A single UI Patterns source.
319   */
320  protected function convertLayoutBuilderComponent(SectionComponent $component): array {
321    /** @var \Drupal\Core\Block\BlockPluginInterface $block */
322    $block = $component->getPlugin();
323    $definition = $block->getPluginDefinition();
324
325    $class = $definition instanceof PluginDefinitionInterface ? $definition->getClass() : $definition['class'];
326
327    if ($class === 'Drupal\layout_builder\Plugin\Block\FieldBlock') {
328      return $this->convertFieldBlock($block);
329    }
330
331    $provider = $definition instanceof PluginDefinitionInterface ? $definition->getProvider() : $definition['provider'];
332
333    if ($provider === 'ui_patterns_blocks') {
334      return $this->convertUiPatternsBlock($block);
335    }
336
337    $id = $definition instanceof PluginDefinitionInterface ? $definition->id() : $definition['id'];
338
339    if ($id === 'extra_field_block') {
340      return $this->convertExtraFieldBlock($block);
341    }
342
343    return $this->convertBlock($block);
344  }
345
346  /**
347   * Convert a Layout Builder field block.
348   *
349   * @param \Drupal\Core\Block\BlockPluginInterface $block
350   *   A block plugin.
351   *
352   * @return array
353   *   A single UI Patterns source.
354   */
355  protected function convertFieldBlock(BlockPluginInterface $block): array {
356    $config = $block->getConfiguration();
357    [, $entity_type, $bundle, $field_name] = \explode(':', $config['id']);
358
359    return $this->convertSingleField($entity_type, $bundle, $field_name, $config['formatter']);
360  }
361
362  /**
363   * Convert an extra field block.
364   *
365   * @param \Drupal\Core\Block\BlockPluginInterface $block
366   *   A block plugin.
367   *
368   * @return array
369   *   A single UI Patterns source.
370   */
371  protected function convertExtraFieldBlock(BlockPluginInterface $block): array {
372    [,,, $field_name] = \explode(':', $block->getPluginId());
373
374    return $this->convertExtraField($field_name);
375  }
376
377  /**
378   * Convert a UI Patterns block plugin.
379   *
380   * @param \Drupal\Core\Block\BlockPluginInterface $block
381   *   A block plugin.
382   *
383   * @return array
384   *   A single UI Patterns source.
385   */
386  protected function convertUiPatternsBlock(BlockPluginInterface $block): array {
387    $config = $block->getConfiguration();
388
389    // Sometimes, this value is null, so let's override it.
390    if (!isset($config['ui_patterns']['component_id'])) {
391      // See: \Drupal\ui_patterns_blocks\Plugin\Block\ComponentBlock.
392      $config['ui_patterns']['component_id'] = \str_replace('ui_patterns:', '', $config['id']);
393      // See: \Drupal\ui_patterns_blocks\Plugin\Block\EntityComponentBlock.
394      $config['ui_patterns']['component_id'] = \str_replace('ui_patterns_entity:', '', $config['id']);
395    }
396
397    return [
398      'source_id' => 'component',
399      'source' => [
400        'component' => $config['ui_patterns'],
401      ],
402    ];
403  }
404
405  /**
406   * Convert a generic block plugin.
407   *
408   * @param \Drupal\Core\Block\BlockPluginInterface $block
409   *   A block plugin.
410   *
411   * @return array
412   *   A single UI Patterns source.
413   */
414  protected function convertBlock(BlockPluginInterface $block): array {
415    $block_id = $block->getPluginId();
416    $blockConfiguration = $this->updateContextMapping($block->getConfiguration());
417
418    return [
419      'source_id' => 'block',
420      'source' => [
421        'plugin_id' => $block_id,
422        $block_id => $blockConfiguration,
423      ],
424    ];
425  }
426
427  /**
428   * Update plugin configuration context mapping.
429   *
430   * @param array $configuration
431   *   A configuration array from a plugin.
432   *
433   * @return array
434   *   The updated configuration.
435   */
436  protected function updateContextMapping(array $configuration): array {
437    if (isset($configuration['context_mapping']) && \is_array($configuration['context_mapping'])) {
438      foreach ($configuration['context_mapping'] as $key => $value) {
439        if ($value === 'layout_builder.entity') {
440          $configuration['context_mapping'][$key] = 'entity';
441        }
442      }
443    }
444
445    return $configuration;
446  }
447
448}