Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
96.61% covered (success)
96.61%
57 / 59
50.00% covered (danger)
50.00%
1 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
BlockChoices
96.61% covered (success)
96.61%
57 / 59
50.00% covered (danger)
50.00%
1 / 2
30
0.00% covered (danger)
0.00%
0 / 1
 __construct
96.43% covered (success)
96.43%
54 / 56
0.00% covered (danger)
0.00%
0 / 1
25
 describe
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
5
1<?php
2
3declare(strict_types=1);
4
5namespace Cosray\Panel;
6
7use Cosray\Block;
8use Cosray\Exception\RuntimeException;
9use Cosray\Field\Blocks;
10
11final class BlockChoices
12{
13    /** @var array<string, array<array-key, mixed>> */
14    public readonly array $types;
15    /** @var array<string, array{type: string, handle: string, label: string, icon: string}> */
16    public readonly array $all;
17    /** @var list<array{type: string, handle: string, label: string, icon: string}> */
18    public readonly array $common;
19
20    /** @param array<string, mixed> $props */
21    public function __construct(string $field, array $props, ?callable $renderIcon = null)
22    {
23        $descriptors = $props['blockTypes'] ?? [];
24        if (!is_array($descriptors)) {
25            throw new RuntimeException(
26                "Blocks field '{$field}' blockTypes must be a list of block type descriptors, "
27                    . self::describe($descriptors)
28                    . ' given',
29            );
30        }
31        $types = [];
32        foreach ($descriptors as $index => $type) {
33            if (!is_array($type) || !is_string($type['type'] ?? null) || $type['type'] === '') {
34                throw new RuntimeException(
35                    "Blocks field '{$field}' block type descriptor {$index} needs a non-empty string 'type', "
36                        . self::describe(is_array($type) ? $type['type'] ?? null : $type)
37                        . ' given',
38                );
39            }
40            $types[$type['type']] = $type;
41        }
42        $this->types = $types;
43        $common = array_key_exists('commonTypes', $props) ? $props['commonTypes'] : [];
44        if (!is_array($common) || !array_is_list($common)) {
45            throw new RuntimeException(
46                "Blocks field '{$field}' commonTypes must be a list of block type IDs, "
47                    . self::describe($common)
48                    . ' given',
49            );
50        }
51        foreach ($common as $index => $type) {
52            if (!is_string($type)) {
53                throw new RuntimeException(
54                    "Blocks field '{$field}' common type {$index} must be a block type ID, "
55                        . self::describe($type)
56                        . ' given',
57                );
58            }
59        }
60        $common = Blocks::commonSelection($common, array_keys($types), $field);
61
62        $choices = [];
63        foreach ($types as $id => $type) {
64            $custom = $type['icon'] ?? null;
65            $svg = is_array($custom) && $renderIcon !== null ? $renderIcon($custom) : '';
66            $panelIcon = $custom !== null
67                ? 'square'
68                : match ($id) {
69                    Block\RichText::class => 'body-text',
70                    Block\Heading::class => 'heading',
71                    Block\Image::class => 'image',
72                    Block\Images::class => 'images',
73                    Block\Video::class => 'film',
74                    Block\Youtube::class => 'play-btn',
75                    Block\Iframe::class => 'window',
76                    default => 'square',
77                };
78            $choices[$id] = [
79                'type' => $id,
80                'handle' => (string) ($type['handle'] ?? $id),
81                'label' => (string) ($type['label'] ?? __('field:block')),
82                // Debug providers can return only a diagnostic HTML comment.
83                'icon' => preg_match('/<svg\b/i', $svg) === 1 ? $svg : Icon::render($panelIcon),
84            ];
85        }
86        $this->all = $choices;
87        $this->common = array_map(static fn(string $type): array => $choices[$type], $common);
88    }
89
90    private static function describe(mixed $value): string
91    {
92        return match (true) {
93            is_scalar($value) => var_export($value, true),
94            is_array($value) && !array_is_list($value) => 'keyed array',
95            default => get_debug_type($value),
96        };
97    }
98}