Code Coverage
 
Lines
Branches
Paths
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 73
0.00% covered (danger)
0.00%
0 / 50
0.00% covered (danger)
0.00%
0 / 119
0.00% covered (danger)
0.00%
0 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
ApiPreviewController
0.00% covered (danger)
0.00%
0 / 73
0.00% covered (danger)
0.00%
0 / 50
0.00% covered (danger)
0.00%
0 / 119
0.00% covered (danger)
0.00%
0 / 10
756
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
n/a
0 / 0
n/a
0 / 0
0.00% covered (danger)
0.00%
0 / 1
2
 getBlockPreview
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
 getPresetPreview
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
 getComponentPreview
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 generateBlock
0.00% covered (danger)
0.00%
0 / 7
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
 buildResponse
0.00% covered (danger)
0.00%
0 / 16
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
 getComponentDescription
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 generateStory
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 generateComponent
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 101
0.00% covered (danger)
0.00%
0 / 1
110
 renderSource
0.00% covered (danger)
0.00%
0 / 5
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
1<?php
2
3declare(strict_types=1);
4
5namespace Drupal\display_builder\Controller;
6
7use Drupal\Core\Controller\ControllerBase;
8use Drupal\Core\Render\HtmlResponse;
9use Drupal\Core\Render\RendererInterface;
10use Drupal\Core\Theme\ComponentPluginManager;
11use Drupal\display_builder\RenderableBuilderTrait;
12use Drupal\ui_patterns_library\StoryPluginManager;
13use Symfony\Component\DependencyInjection\Attribute\Autowire;
14
15/**
16 * Returns preview responses for Display builder routes.
17 */
18class ApiPreviewController extends ControllerBase {
19
20  use RenderableBuilderTrait;
21
22  public function __construct(
23    #[Autowire(service: 'plugin.manager.component_story')]
24    private StoryPluginManager $storyPluginManager,
25    #[Autowire(service: 'plugin.manager.sdc')]
26    private ComponentPluginManager $componentManager,
27    private RendererInterface $renderer,
28  ) {}
29
30  /**
31   * Get block preview.
32   *
33   * @param string $block_id
34   *   Block ID.
35   *
36   * @return \Drupal\Core\Render\HtmlResponse
37   *   The HTML response.
38   */
39  public function getBlockPreview(string $block_id): HtmlResponse {
40    return $this->buildResponse($this->generateBlock($block_id));
41  }
42
43  /**
44   * Get preset preview.
45   *
46   * @param string $preset_id
47   *   Preset ID.
48   *
49   * @return \Drupal\Core\Render\HtmlResponse
50   *   The HTML response.
51   */
52  public function getPresetPreview(string $preset_id): HtmlResponse {
53    /** @var \Drupal\display_builder\Entity\PatternPresetInterface $preset */
54    $preset = $this->entityTypeManager()->getStorage('pattern_preset')->load($preset_id);
55    $data = $preset->getSources([], FALSE);
56
57    return $this->buildResponse($this->renderSource($data), $preset->get('description'));
58  }
59
60  /**
61   * Get component preview, only HTML without specific css or js.
62   *
63   * @param string $component_id
64   *   Component ID.
65   * @param string $variant_id
66   *   Component variant ID.
67   *
68   * @return \Drupal\Core\Render\HtmlResponse
69   *   The HTML response.
70   */
71  public function getComponentPreview(string $component_id, string $variant_id): HtmlResponse {
72    $ui_patterns_library = $this->moduleHandler()->moduleExists('ui_patterns_library');
73
74    $build = [];
75
76    if (!$ui_patterns_library) {
77      $build = $this->generateComponent($component_id);
78    }
79    else {
80      $stories = $this->storyPluginManager->getComponentStories($component_id);
81
82      if (empty($stories)) {
83        $build = $this->generateComponent($component_id);
84      }
85      else {
86        $story = [];
87        $first_story = \reset($stories);
88        $story[$first_story['machineName'] ?? 'default'] = $first_story;
89        $build = $this->generateStory($component_id, $variant_id, $story);
90      }
91    }
92
93    return $this->buildResponse($build, $this->getComponentDescription($component_id));
94  }
95
96  /**
97   * Build renderable block.
98   *
99   * @param string $block_id
100   *   The block id to preview.
101   *
102   * @return array
103   *   A renderable array.
104   */
105  protected function generateBlock(string $block_id): array {
106    $data = [
107      'source_id' => 'block',
108      'source' => [
109        'plugin_id' => $block_id,
110      ],
111    ];
112
113    return $this->renderSource($data);
114  }
115
116  /**
117   * Build the preview response, with an optional description footer.
118   *
119   * The rendered thing and its description are wrapped separately so the
120   * description can stay pinned at the bottom of the popup while the render
121   * above it absorbs the clipping, whenever the popup had to be capped to
122   * the room left on screen.
123   *
124   * @param array $build
125   *   The renderable to preview.
126   * @param string|null $description
127   *   (Optional) Description to show below it, when there is one.
128   *
129   * @return \Drupal\Core\Render\HtmlResponse
130   *   The HTML response.
131   *
132   * @see components/display_builder/css/preview.css
133   */
134  private function buildResponse(array $build, ?string $description = NULL): HtmlResponse {
135    $wrapper = [
136      'content' => [
137        '#type' => 'container',
138        '#attributes' => ['class' => ['db-preview__content']],
139        'content' => $build,
140      ],
141    ];
142
143    if ($description !== NULL && \trim($description) !== '') {
144      $wrapper['description'] = [
145        '#type' => 'container',
146        '#attributes' => ['class' => ['db-preview__description']],
147        'text' => ['#plain_text' => $description],
148      ];
149    }
150
151    $response = new HtmlResponse();
152    $response->setContent($this->renderer->renderRoot($wrapper));
153
154    return $response;
155  }
156
157  /**
158   * Get a component description, if it declares one.
159   *
160   * @param string $component_id
161   *   The component id.
162   *
163   * @return string|null
164   *   The description, or NULL when the component has none or is unknown.
165   */
166  private function getComponentDescription(string $component_id): ?string {
167    try {
168      $definition = $this->componentManager->getDefinition($component_id);
169    }
170    catch (\Throwable $th) {
171      return NULL;
172    }
173
174    $description = $definition['description'] ?? NULL;
175
176    return $description === NULL ? NULL : (string) $description;
177  }
178
179  /**
180   * Generate a story.
181   *
182   * @param string $component_id
183   *   The component id.
184   * @param string $variant_id
185   *   The component variant id.
186   * @param array $stories
187   *   The stories.
188   *
189   * @return array
190   *   The render array
191   */
192  private function generateStory(string $component_id, string $variant_id, array $stories): array {
193    $html = [];
194
195    foreach (\array_keys($stories) as $story_id) {
196      $html[$story_id] = [
197        '#type' => 'component',
198        '#component' => $component_id,
199        '#story' => $story_id,
200        '#props' => ['variant' => $variant_id],
201      ];
202    }
203
204    return $html;
205  }
206
207  /**
208   * Generate a content even with empty story or no library module.
209   *
210   * @param string $component_id
211   *   The component id.
212   *
213   * @return array
214   *   The render array
215   *
216   * @todo Remove when https://www.drupal.org/project/ui_patterns/issues/3414774 is merged
217   */
218  private function generateComponent(string $component_id): array {
219    try {
220      $definition = $this->componentManager->getDefinition($component_id);
221    }
222    catch (\Throwable $th) {
223      return [];
224    }
225
226    $html = [
227      '#type' => 'component',
228      '#component' => $component_id,
229    ];
230
231    foreach ($definition['slots'] ?? [] as $slot_id => $slot) {
232      if (isset($slot['examples']) && \is_array($slot['examples']) && !empty($slot['examples'])) {
233        $html['#slots'][$slot_id] = $slot['examples'][0];
234      }
235    }
236
237    foreach ($definition['props']['properties'] ?? [] as $prop_id => $prop) {
238      if (isset($prop['examples']) && \is_array($prop['examples']) && !empty($prop['examples'])) {
239        $html['#props'][$prop_id] = $prop['examples'][0];
240      }
241    }
242
243    return $html;
244  }
245
246  /**
247   * Get renderable array for a slot source.
248   *
249   * @param array $data
250   *   The slot source data array containing:
251   *   - source_id: The source ID
252   *   - source: Array of source configuration.
253   *
254   * @return array
255   *   The renderable array for this slot source.
256   */
257  private function renderSource(array $data): array {
258    /** @var \Drupal\ui_patterns\Element\ComponentElementBuilder $builder */
259    $builder = \Drupal::service('ui_patterns.component_element_builder'); // @phpcs:ignore
260
261    try {
262      $build = $builder->buildSource([], 'content', [], $data, []) ?? [];
263
264      return $build['#slots']['content'][0] ?? [];
265    }
266    catch (\Throwable $th) {
267      return [];
268    }
269  }
270
271}

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.

ApiPreviewController->buildResponse
134  private function buildResponse(array $build, ?string $description = NULL): HtmlResponse {
135    $wrapper = [
136      'content' => [
137        '#type' => 'container',
138        '#attributes' => ['class' => ['db-preview__content']],
139        'content' => $build,
140      ],
141    ];
142
143    if ($description !== NULL && \trim($description) !== '') {
 
143    if ($description !== NULL && \trim($description) !== '') {
 
143    if ($description !== NULL && \trim($description) !== '') {
 
145        '#type' => 'container',
146        '#attributes' => ['class' => ['db-preview__description']],
147        'text' => ['#plain_text' => $description],
148      ];
149    }
150
151    $response = new HtmlResponse();
 
151    $response = new HtmlResponse();
152    $response->setContent($this->renderer->renderRoot($wrapper));
153
154    return $response;
155  }
134  private function buildResponse(array $build, ?string $description = NULL): HtmlResponse {
135    $wrapper = [
136      'content' => [
137        '#type' => 'container',
138        '#attributes' => ['class' => ['db-preview__content']],
139        'content' => $build,
140      ],
141    ];
142
143    if ($description !== NULL && \trim($description) !== '') {
 
143    if ($description !== NULL && \trim($description) !== '') {
 
143    if ($description !== NULL && \trim($description) !== '') {
 
151    $response = new HtmlResponse();
152    $response->setContent($this->renderer->renderRoot($wrapper));
153
154    return $response;
155  }
134  private function buildResponse(array $build, ?string $description = NULL): HtmlResponse {
135    $wrapper = [
136      'content' => [
137        '#type' => 'container',
138        '#attributes' => ['class' => ['db-preview__content']],
139        'content' => $build,
140      ],
141    ];
142
143    if ($description !== NULL && \trim($description) !== '') {
 
143    if ($description !== NULL && \trim($description) !== '') {
 
145        '#type' => 'container',
146        '#attributes' => ['class' => ['db-preview__description']],
147        'text' => ['#plain_text' => $description],
148      ];
149    }
150
151    $response = new HtmlResponse();
 
151    $response = new HtmlResponse();
152    $response->setContent($this->renderer->renderRoot($wrapper));
153
154    return $response;
155  }
134  private function buildResponse(array $build, ?string $description = NULL): HtmlResponse {
135    $wrapper = [
136      'content' => [
137        '#type' => 'container',
138        '#attributes' => ['class' => ['db-preview__content']],
139        'content' => $build,
140      ],
141    ];
142
143    if ($description !== NULL && \trim($description) !== '') {
 
143    if ($description !== NULL && \trim($description) !== '') {
 
151    $response = new HtmlResponse();
152    $response->setContent($this->renderer->renderRoot($wrapper));
153
154    return $response;
155  }
ApiPreviewController->generateBlock
105  protected function generateBlock(string $block_id): array {
106    $data = [
107      'source_id' => 'block',
108      'source' => [
109        'plugin_id' => $block_id,
110      ],
111    ];
112
113    return $this->renderSource($data);
114  }
ApiPreviewController->generateStory
192  private function generateStory(string $component_id, string $variant_id, array $stories): array {
193    $html = [];
194
195    foreach (\array_keys($stories) as $story_id) {
 
195    foreach (\array_keys($stories) as $story_id) {
 
195    foreach (\array_keys($stories) as $story_id) {
196      $html[$story_id] = [
197        '#type' => 'component',
 
195    foreach (\array_keys($stories) as $story_id) {
 
195    foreach (\array_keys($stories) as $story_id) {
196      $html[$story_id] = [
197        '#type' => 'component',
198        '#component' => $component_id,
199        '#story' => $story_id,
200        '#props' => ['variant' => $variant_id],
201      ];
202    }
203
204    return $html;
205  }
192  private function generateStory(string $component_id, string $variant_id, array $stories): array {
193    $html = [];
194
195    foreach (\array_keys($stories) as $story_id) {
 
195    foreach (\array_keys($stories) as $story_id) {
 
195    foreach (\array_keys($stories) as $story_id) {
196      $html[$story_id] = [
197        '#type' => 'component',
198        '#component' => $component_id,
199        '#story' => $story_id,
200        '#props' => ['variant' => $variant_id],
201      ];
202    }
203
204    return $html;
205  }
192  private function generateStory(string $component_id, string $variant_id, array $stories): array {
193    $html = [];
194
195    foreach (\array_keys($stories) as $story_id) {
 
195    foreach (\array_keys($stories) as $story_id) {
196      $html[$story_id] = [
197        '#type' => 'component',
198        '#component' => $component_id,
199        '#story' => $story_id,
200        '#props' => ['variant' => $variant_id],
201      ];
202    }
203
204    return $html;
205  }
ApiPreviewController->getBlockPreview
39  public function getBlockPreview(string $block_id): HtmlResponse {
40    return $this->buildResponse($this->generateBlock($block_id));
41  }
ApiPreviewController->getComponentDescription
166  private function getComponentDescription(string $component_id): ?string {
167    try {
168      $definition = $this->componentManager->getDefinition($component_id);
 
174    $description = $definition['description'] ?? NULL;
175
176    return $description === NULL ? NULL : (string) $description;
 
176    return $description === NULL ? NULL : (string) $description;
 
176    return $description === NULL ? NULL : (string) $description;
177  }
166  private function getComponentDescription(string $component_id): ?string {
167    try {
168      $definition = $this->componentManager->getDefinition($component_id);
 
174    $description = $definition['description'] ?? NULL;
175
176    return $description === NULL ? NULL : (string) $description;
 
176    return $description === NULL ? NULL : (string) $description;
 
176    return $description === NULL ? NULL : (string) $description;
177  }
170    catch (\Throwable $th) {
 
171      return NULL;
ApiPreviewController->getComponentPreview
71  public function getComponentPreview(string $component_id, string $variant_id): HtmlResponse {
72    $ui_patterns_library = $this->moduleHandler()->moduleExists('ui_patterns_library');
73
74    $build = [];
75
76    if (!$ui_patterns_library) {
 
76    if (!$ui_patterns_library) {
77      $build = $this->generateComponent($component_id);
 
93    return $this->buildResponse($build, $this->getComponentDescription($component_id));
94  }
71  public function getComponentPreview(string $component_id, string $variant_id): HtmlResponse {
72    $ui_patterns_library = $this->moduleHandler()->moduleExists('ui_patterns_library');
73
74    $build = [];
75
76    if (!$ui_patterns_library) {
 
80      $stories = $this->storyPluginManager->getComponentStories($component_id);
81
82      if (empty($stories)) {
 
82      if (empty($stories)) {
83        $build = $this->generateComponent($component_id);
 
93    return $this->buildResponse($build, $this->getComponentDescription($component_id));
94  }
71  public function getComponentPreview(string $component_id, string $variant_id): HtmlResponse {
72    $ui_patterns_library = $this->moduleHandler()->moduleExists('ui_patterns_library');
73
74    $build = [];
75
76    if (!$ui_patterns_library) {
 
80      $stories = $this->storyPluginManager->getComponentStories($component_id);
81
82      if (empty($stories)) {
 
86        $story = [];
87        $first_story = \reset($stories);
88        $story[$first_story['machineName'] ?? 'default'] = $first_story;
89        $build = $this->generateStory($component_id, $variant_id, $story);
90      }
91    }
92
93    return $this->buildResponse($build, $this->getComponentDescription($component_id));
 
93    return $this->buildResponse($build, $this->getComponentDescription($component_id));
94  }
ApiPreviewController->getPresetPreview
52  public function getPresetPreview(string $preset_id): HtmlResponse {
53    /** @var \Drupal\display_builder\Entity\PatternPresetInterface $preset */
54    $preset = $this->entityTypeManager()->getStorage('pattern_preset')->load($preset_id);
55    $data = $preset->getSources([], FALSE);
56
57    return $this->buildResponse($this->renderSource($data), $preset->get('description'));
58  }
ApiPreviewController->renderSource
257  private function renderSource(array $data): array {
258    /** @var \Drupal\ui_patterns\Element\ComponentElementBuilder $builder */
259    $builder = \Drupal::service('ui_patterns.component_element_builder'); // @phpcs:ignore
260
261    try {
262      $build = $builder->buildSource([], 'content', [], $data, []) ?? [];
263
264      return $build['#slots']['content'][0] ?? [];
266    catch (\Throwable $th) {
 
267      return [];
268    }
269  }