Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 122
0.00% covered (danger)
0.00%
0 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
BuilderDataConverter
0.00% covered (danger)
0.00%
0 / 122
0.00% covered (danger)
0.00%
0 / 14
1260
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
2
 convertFromManageDisplay
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
20
 convertFromLayoutBuilder
0.00% covered (danger)
0.00%
0 / 8
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
2
 convertExtraField
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 convertUiPatternsLayout
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
42
 moveUiStylesAttributesSource
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 convertLayout
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 extractUiStylesData
0.00% covered (danger)
0.00%
0 / 8
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 / 1
56
 convertFieldBlock
0.00% covered (danger)
0.00%
0 / 3
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
2
 convertUiPatternsBlock
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 convertBlock
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
2
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 * Converter 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 (!$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 = \array_merge($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    foreach ($section->getThirdPartyProviders() ?: [] as $provider_id) {
175      $data['_third_party_settings'][$provider_id] = $section->getThirdPartySettings($provider_id);
176    }
177    $data = $this->moveUiStylesAttributesSource($data);
178
179    if ($slots) {
180      $data['source']['component']['slots'] = $slots;
181    }
182
183    return $data;
184  }
185
186  /**
187   * Move data from UI Style's attribute source to 3rd party settings.
188   *
189   * @param array $data
190   *   A UI Patterns source data.
191   *
192   * @return array
193   *   The same UI Patterns source data, maybe altered.
194   */
195  protected function moveUiStylesAttributesSource(array $data): array {
196    if ($data['source']['component']['props']['attributes']['source_id'] !== 'ui_styles_attributes') {
197      return $data;
198    }
199    // We keep this order because it is the priority order in UI styles.
200    $styles_1 = [
201      'selected' => $data['source']['component']['props']['attributes']['source']['styles']['selected'],
202      'extra' => $data['source']['component']['props']['attributes']['source']['extra'],
203    ];
204    $styles_2 = $data['_third_party_settings']['ui_styles'] ?? [];
205    $data['_third_party_settings']['ui_styles'] = NestedArray::mergeDeep($styles_1, $styles_2);
206    $data['source']['component']['props']['attributes'] = [];
207
208    return $data;
209  }
210
211  /**
212   * Convert regular layout plugin.
213   *
214   * We don't really convert the layout here, we extract the blocks and put
215   * them as a flat list where the layout is.
216   *
217   * @todo better layout support https://www.drupal.org/project/display_builder/issues/3531521
218   *
219   * @param \Drupal\layout_builder\Section $section
220   *   A single layout builder section.
221   *
222   * @return array
223   *   A list of UI Patterns source.
224   */
225  protected function convertLayout(Section $section): array {
226    $sources = [];
227    $components = $section->getComponents();
228
229    foreach ($components as $component) {
230      $source = $this->convertLayoutBuilderComponent($component);
231      $source = $this->extractUiStylesData($component, $source);
232      $sources[] = $source;
233    }
234
235    return $sources;
236  }
237
238  /**
239   * Extract UI Styles data.
240   *
241   * In Display Builder, we don't render through ThemeManager::render() so we
242   * don't load block.html.twig and we don't have block wrapper and block title
243   * attributes. So, let's ignore title styles and let's merge wrapper styles
244   * with block styles (which keep priority).
245   *
246   * @param \Drupal\layout_builder\SectionComponent $component
247   *   A single layout builder component.
248   * @param array $source
249   *   The already converted UI Patterns source.
250   *
251   * @return array
252   *   The altered UI Patterns source.
253   */
254  protected function extractUiStylesData(SectionComponent $component, array $source): array {
255    $additional = $component->toArray()['additional'] ?? [];
256
257    $styles = \array_unique(\array_merge($additional['ui_styles_wrapper'] ?? [], $additional['ui_styles'] ?? []));
258
259    if ($styles) {
260      $source['_third_party_settings']['ui_styles']['selected'] = $styles;
261    }
262
263    $extra = \trim(($additional['ui_styles_wrapper_extra'] ?? '') . ' ' . ($additional['ui_styles_extra'] ?? ''));
264
265    if ($extra) {
266      $source['_third_party_settings']['ui_styles']['extra'] = $extra;
267    }
268
269    return $source;
270  }
271
272  /**
273   * Convert layout builder component.
274   *
275   * @param \Drupal\layout_builder\SectionComponent $component
276   *   A single layout builder component.
277   *
278   * @return array
279   *   A single UI Patterns source.
280   */
281  protected function convertLayoutBuilderComponent(SectionComponent $component): array {
282    /** @var \Drupal\Core\Block\BlockPluginInterface $block */
283    $block = $component->getPlugin();
284    $definition = $block->getPluginDefinition();
285
286    $class = $definition instanceof PluginDefinitionInterface ? $definition->getClass() : $definition['class'];
287
288    if ($class === 'Drupal\layout_builder\Plugin\Block\FieldBlock') {
289      return $this->convertFieldBlock($block);
290    }
291
292    $provider = $definition instanceof PluginDefinitionInterface ? $definition->getProvider() : $definition['provider'];
293
294    if ($provider === 'ui_patterns_blocks') {
295      return $this->convertUiPatternsBlock($block);
296    }
297
298    $id = $definition instanceof PluginDefinitionInterface ? $definition->id() : $definition['id'];
299
300    if ($id === 'extra_field_block') {
301      return $this->convertExtraFieldBlock($block);
302    }
303
304    return $this->convertBlock($block);
305  }
306
307  /**
308   * Convert a Layout Builder field block.
309   *
310   * @param \Drupal\Core\Block\BlockPluginInterface $block
311   *   A block plugin.
312   *
313   * @return array
314   *   A single UI Patterns source.
315   */
316  protected function convertFieldBlock(BlockPluginInterface $block): array {
317    $config = $block->getConfiguration();
318    [, $entity_type, $bundle, $field_name] = \explode(':', $config['id']);
319
320    return $this->convertSingleField($entity_type, $bundle, $field_name, $config['formatter']);
321  }
322
323  /**
324   * Convert an extra field block.
325   *
326   * @param \Drupal\Core\Block\BlockPluginInterface $block
327   *   A block plugin.
328   *
329   * @return array
330   *   A single UI Patterns source.
331   */
332  protected function convertExtraFieldBlock(BlockPluginInterface $block): array {
333    [,,, $field_name] = \explode(':', $block->getPluginId());
334
335    return $this->convertExtraField($field_name);
336  }
337
338  /**
339   * Convert a UI Patterns block plugin.
340   *
341   * @param \Drupal\Core\Block\BlockPluginInterface $block
342   *   A block plugin.
343   *
344   * @return array
345   *   A single UI Patterns source.
346   */
347  protected function convertUiPatternsBlock(BlockPluginInterface $block): array {
348    $config = $block->getConfiguration();
349
350    // Sometimes, this value is null, so let's override it.
351    if (!isset($config['ui_patterns']['component_id'])) {
352      // See: \Drupal\ui_patterns_blocks\Plugin\Block\ComponentBlock.
353      $config['ui_patterns']['component_id'] = \str_replace('ui_patterns:', '', $config['id']);
354      // See: \Drupal\ui_patterns_blocks\Plugin\Block\EntityComponentBlock.
355      $config['ui_patterns']['component_id'] = \str_replace('ui_patterns_entity:', '', $config['id']);
356    }
357
358    return [
359      'source_id' => 'component',
360      'source' => [
361        'component' => $config['ui_patterns'],
362      ],
363    ];
364  }
365
366  /**
367   * Convert a generic block plugin.
368   *
369   * @param \Drupal\Core\Block\BlockPluginInterface $block
370   *   A block plugin.
371   *
372   * @return array
373   *   A single UI Patterns source.
374   */
375  protected function convertBlock(BlockPluginInterface $block): array {
376    $block_id = $block->getPluginId();
377
378    return [
379      'source_id' => 'block',
380      'source' => [
381        'plugin_id' => $block_id,
382        $block_id => $block->getConfiguration(),
383      ],
384    ];
385  }
386
387}