Passed
Push — main ( 64d07c...38d056 )
by Oscar
02:39
created

FontAwesomeSvg::getStyleClass()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 3
c 0
b 0
f 0
dl 0
loc 5
ccs 4
cts 4
cp 1
rs 10
cc 2
nc 2
nop 1
crap 2
1
<?php
2
3
/*
4
 * This file is part of ocubom/twig-svg-extension
5
 *
6
 * © Oscar Cubo Medina <https://ocubom.github.io>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Ocubom\Twig\Extension\Svg\Provider\FontAwesome;
13
14
use Ocubom\Twig\Extension\Svg\Exception\ParseException;
15
use Ocubom\Twig\Extension\Svg\Processor\ClassProcessor;
16
use Ocubom\Twig\Extension\Svg\Processor\RemoveAttributeProcessor;
17
use Ocubom\Twig\Extension\Svg\Svg;
18
use Ocubom\Twig\Extension\Svg\Util\DomUtil;
19
use Symfony\Component\OptionsResolver\Options;
20
use Symfony\Component\OptionsResolver\OptionsResolver;
21
22
use function BenTools\IterableFunctions\iterable_to_array;
23
24
class FontAwesomeSvg extends Svg
25
{
26
    /**
27
     * Full path to the icon.
28
     */
29
    protected \SplFileInfo $path;
30
31
    /**
32
     * @param mixed $data The FontAwesome Icon data
33
     */
34 3
    public function __construct($data, iterable $options = null)
35
    {
36
        try {
37
            switch (true) {
38 3
                case $data instanceof FontAwesomeSvg: // "Copy" constructor
39
                    $this->path = $data->path;
40
                    break;
41
42 3
                case $data instanceof \SplFileInfo:
43 3
                    $this->path = $data;
44 3
                    break;
45
46
                default:
47
                    throw new ParseException(sprintf('Unable to create "%s" from "%s"', __CLASS__, get_debug_type($data)));
48
            }
49
50
            /** @var array $options */
51 3
            $options = iterable_to_array($options ?? []);
52
53
            // Construct from path
54 3
            parent::__construct($this->path, array_merge($options, [
55 3
                'class_default' => array_merge($options['class_default'] ?? [], [
56 3
                    FontAwesome::INLINE_CLASS, // Add inlined class
57 3
                    'fa-'.$this->getName(), // Add icon name class
58 3
                ]),
59 3
                'class_block' => array_merge($options['class_block'] ?? [], [
60 3
                    $this->getStyle(), // Block style
61 3
                    $this->getStyleClass(), // Block current classes
62 3
                    $this->getStyleClass('5.0'), // Block pre-6.0 classes
63 3
                ]),
64 3
            ]));
65
66
            // Add Font Awesome data-*
67 3
            $this->svg->setAttribute('data-prefix', $this->getStyleClass('5.0'));
68 3
            $this->svg->setAttribute('data-icon', $this->getName());
69
        } catch (ParseException $exc) {
70
            throw new ParseException(sprintf('Unable to create a FontAwesome Icon from "%s"', get_debug_type($data)), 0, $exc);
71
        }
72
    }
73
74
    /**
75
     * @codeCoverageIgnore
76
     */
77
    public function getFaId(): string
78
    {
79
        return sprintf(
80
            'fa-%s-%s',
81
            $this->getStyle(),
82
            $this->getName(),
83
        );
84
    }
85
86 3
    public function getName(): string
87
    {
88 3
        return $this->path->getBasename('.svg');
89
    }
90
91 3
    public function getStyle(): string
92
    {
93 3
        $path = $this->path->getPathInfo();
94
        assert($path instanceof \SplFileInfo);
95
96 3
        return $path->getBasename();
97
    }
98
99 3
    public function getStyleClass(string $version = '6.0'): string
100
    {
101 3
        return version_compare($version, '6.0', '<')
102 3
            ? 'fa'.$this->getStyle()[0]
103 3
            : 'fa-'.$this->getStyle();
104
    }
105
106 1
    public function getHtmlTag(iterable $options = null): \DOMElement
107
    {
108
        // Create the HTML Tag node
109 1
        $node = DomUtil::createElement(FontAwesome::HTML_TAG);
110
        // Copy options as attributes
111 1
        foreach ($options ?? [] as $key => $val) {
112 1
            if (!empty($val)) {
113 1
                $val = is_iterable($val) ? implode(' ', iterable_to_array($val)) : (string) $val;
114
115 1
                $node->setAttribute($key, $val);
116
            }
117
        }
118
119
        // Process classes
120 1
        $processor = new ClassProcessor();
121 1
        $processor($node, [
122 1
            'class' => [
123 1
                $this->getStyleClass(),
124 1
                'fa-'.$this->getName(),
125 1
            ],
126 1
            'class_banned' => [
127 1
                FontAwesome::INLINE_CLASS,
128 1
            ],
129 1
        ]);
130
131 1
        return $node;
132
    }
133
134
    /**
135
     * @return array<string, array<int, callable>|callable>
136
     *
137
     * @psalm-suppress InvalidScope
138
     */
139 3
    protected static function getProcessors(): array
140
    {
141 1
        return array_merge(parent::getProcessors(), [
142
            // Options will be ignored & removed
143 1
            'class_default' => new RemoveAttributeProcessor('class_default'),
144 1
            'class_block' => new RemoveAttributeProcessor('class_block'),
145 1
            'fill' => new RemoveAttributeProcessor('fill'),
146 1
            'opacity' => new RemoveAttributeProcessor('opacity'),
147 1
            'primary_fill' => new RemoveAttributeProcessor('primary_fill'),
148 1
            'primary_opacity' => new RemoveAttributeProcessor('primary_opacity'),
149 1
            'secondary_fill' => new RemoveAttributeProcessor('secondary_fill'),
150 1
            'secondary_opacity' => new RemoveAttributeProcessor('secondary_opacity'),
151
152
            // Remove special attributes
153 1
            'data-fa-title-id' => new RemoveAttributeProcessor('data-fa-title-id'),
154
155
            // Global changes
156 1
            '' => function (\DOMElement $svg, array $options = []): \DOMElement {
157
                // Add FontAwesome fill and opacity values to each path
158
                /** @var \DOMElement $path */
159 3
                foreach ($svg->getElementsByTagName('path') as $path) {
160 3
                    $class = array_intersect(
161 3
                        ['fa-primary', 'fa-secondary'],
162 3
                        preg_split('@\s+@Uis', $path->getAttribute('class'))
163 3
                    );
164 3
                    $class = count($class) > 0
165
                        ? substr($class[0], 3)
166 3
                        : '';
167
168 3
                    foreach (['fill', 'opacity'] as $name) {
169 3
                        $key = $class.'_'.$name;
170 3
                        if (!empty($options[$key])) {
171
                            $path->setAttribute($name, $options[$key]);
172 3
                        } elseif (!empty($options[$name])) {
173 3
                            $path->setAttribute($name, $options[$name]);
174
                        }
175
                    }
176
                }
177
178 3
                return $svg;
179 1
            },
180 1
        ]);
181
    }
182
183 3
    public static function configureOptions(OptionsResolver $resolver = null): OptionsResolver
184
    {
185 3
        $resolver = parent::configureOptions($resolver);
186
187
        /** @psalm-suppress MissingClosureParamType */
188 3
        $normalizeFloat = function (Options $options, $value): ?float {
189 3
            return is_numeric((string) $value) ? floatval((string) $value) : null;
190 3
        };
191
192 3
        $resolver->define('class_default')
193 3
            ->default([])
194 3
            ->allowedTypes('string[]')
195 3
            ->info('Default classes to add unless null');
196
197 3
        $resolver->define('fill')
198 3
            ->default('currentColor')
199 3
            ->allowedTypes('null', 'string')
200 3
            ->info('Default fill color for paths');
201
202 3
        $resolver->define('opacity')
203 3
            ->default(null)
204 3
            ->allowedTypes('null', 'string', 'float')
205 3
            ->normalize($normalizeFloat)
206 3
            ->info('Default opacity color for paths');
207
208 3
        $resolver->define('primary_fill')
209 3
            ->default(null)
210 3
            ->allowedTypes('null', 'string')
211 3
            ->info('Default fill color for primary paths (duotone)');
212
213 3
        $resolver->define('primary_opacity')
214 3
            ->default(null)
215 3
            ->allowedTypes('null', 'string', 'float')
216 3
            ->normalize($normalizeFloat)
217 3
            ->info('Default opacity color for primary paths (duotone)');
218
219 3
        $resolver->define('secondary_fill')
220 3
            ->default(null)
221 3
            ->allowedTypes('null', 'string')
222 3
            ->info('Default fill color for secondary paths (duotone)');
223
224 3
        $resolver->define('secondary_opacity')
225 3
            ->default(null)
226 3
            ->allowedTypes('null', 'string', 'float')
227 3
            ->normalize($normalizeFloat)
228 3
            ->info('Default opacity color for secondary paths (duotone)');
229
230 3
        $resolver->define('data-fa-title-id')
231 3
            ->default(null)
232 3
            ->allowedTypes('null', 'string')
233 3
            ->info('Set the icon title id instead of generate a new one');
234
235
        // Uses data-fa-title-id as aria-labelledby if not defined
236 3
        $resolver->addNormalizer(
237 3
            'aria-labelledby',
238 3
            function (Options $options, ?string $value): ?string {
239 3
                if (empty($value) && !empty($options['data-fa-title-id'])) {
240 1
                    return $options['data-fa-title-id'];
241
                }
242
243 3
                return $value;
244 3
            },
245 3
            true
246 3
        );
247
248 3
        return $resolver;
249
    }
250
}
251