Code Coverage |
||||||||||||||||
Lines |
Branches |
Paths |
Functions and Methods |
Classes and Traits |
||||||||||||
| Total | |
0.00% |
0 / 73 |
|
0.00% |
0 / 50 |
|
0.00% |
0 / 119 |
|
0.00% |
0 / 10 |
CRAP | |
0.00% |
0 / 1 |
| ApiPreviewController | |
0.00% |
0 / 73 |
|
0.00% |
0 / 50 |
|
0.00% |
0 / 119 |
|
0.00% |
0 / 10 |
756 | |
0.00% |
0 / 1 |
| __construct | |
0.00% |
0 / 1 |
n/a |
0 / 0 |
n/a |
0 / 0 |
|
0.00% |
0 / 1 |
2 | |||||
| getBlockPreview | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| getPresetPreview | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| getComponentPreview | |
0.00% |
0 / 12 |
|
0.00% |
0 / 6 |
|
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
12 | |||
| generateBlock | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| buildResponse | |
0.00% |
0 / 16 |
|
0.00% |
0 / 5 |
|
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 | |||
| getComponentDescription | |
0.00% |
0 / 5 |
|
0.00% |
0 / 7 |
|
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
12 | |||
| generateStory | |
0.00% |
0 / 9 |
|
0.00% |
0 / 4 |
|
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
| generateComponent | |
0.00% |
0 / 14 |
|
0.00% |
0 / 22 |
|
0.00% |
0 / 101 |
|
0.00% |
0 / 1 |
110 | |||
| renderSource | |
0.00% |
0 / 5 |
|
0.00% |
0 / 3 |
|
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace Drupal\display_builder\Controller; |
| 6 | |
| 7 | use Drupal\Core\Controller\ControllerBase; |
| 8 | use Drupal\Core\Render\HtmlResponse; |
| 9 | use Drupal\Core\Render\RendererInterface; |
| 10 | use Drupal\Core\Theme\ComponentPluginManager; |
| 11 | use Drupal\display_builder\RenderableBuilderTrait; |
| 12 | use Drupal\ui_patterns_library\StoryPluginManager; |
| 13 | use Symfony\Component\DependencyInjection\Attribute\Autowire; |
| 14 | |
| 15 | /** |
| 16 | * Returns preview responses for Display builder routes. |
| 17 | */ |
| 18 | class 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 | } |
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.
| 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 | } |
| 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 | } |
| 218 | private function generateComponent(string $component_id): array { |
| 219 | try { |
| 220 | $definition = $this->componentManager->getDefinition($component_id); |
| 222 | catch (\Throwable $th) { |
| 223 | return []; |
| 227 | '#type' => 'component', |
| 228 | '#component' => $component_id, |
| 229 | ]; |
| 230 | |
| 231 | foreach ($definition['slots'] ?? [] as $slot_id => $slot) { |
| 231 | foreach ($definition['slots'] ?? [] as $slot_id => $slot) { |
| 231 | foreach ($definition['slots'] ?? [] as $slot_id => $slot) { |
| 232 | if (isset($slot['examples']) && \is_array($slot['examples']) && !empty($slot['examples'])) { |
| 232 | if (isset($slot['examples']) && \is_array($slot['examples']) && !empty($slot['examples'])) { |
| 232 | if (isset($slot['examples']) && \is_array($slot['examples']) && !empty($slot['examples'])) { |
| 232 | if (isset($slot['examples']) && \is_array($slot['examples']) && !empty($slot['examples'])) { |
| 232 | if (isset($slot['examples']) && \is_array($slot['examples']) && !empty($slot['examples'])) { |
| 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]; |
| 231 | foreach ($definition['slots'] ?? [] as $slot_id => $slot) { |
| 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) { |
| 237 | foreach ($definition['props']['properties'] ?? [] as $prop_id => $prop) { |
| 237 | foreach ($definition['props']['properties'] ?? [] as $prop_id => $prop) { |
| 238 | if (isset($prop['examples']) && \is_array($prop['examples']) && !empty($prop['examples'])) { |
| 238 | if (isset($prop['examples']) && \is_array($prop['examples']) && !empty($prop['examples'])) { |
| 238 | if (isset($prop['examples']) && \is_array($prop['examples']) && !empty($prop['examples'])) { |
| 238 | if (isset($prop['examples']) && \is_array($prop['examples']) && !empty($prop['examples'])) { |
| 238 | if (isset($prop['examples']) && \is_array($prop['examples']) && !empty($prop['examples'])) { |
| 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]; |
| 237 | foreach ($definition['props']['properties'] ?? [] as $prop_id => $prop) { |
| 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 | } |
| 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) { |
| 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 | } |
| 39 | public function getBlockPreview(string $block_id): HtmlResponse { |
| 40 | return $this->buildResponse($this->generateBlock($block_id)); |
| 41 | } |
| 166 | private function getComponentDescription(string $component_id): ?string { |
| 167 | try { |
| 168 | $definition = $this->componentManager->getDefinition($component_id); |
| 170 | catch (\Throwable $th) { |
| 171 | return NULL; |
| 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; |
| 176 | return $description === NULL ? NULL : (string) $description; |
| 177 | } |
| 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); |
| 80 | $stories = $this->storyPluginManager->getComponentStories($component_id); |
| 81 | |
| 82 | if (empty($stories)) { |
| 82 | if (empty($stories)) { |
| 83 | $build = $this->generateComponent($component_id); |
| 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 | } |
| 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 | } |
| 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 | } |