Passed
Push — main ( 7d39cf...4a07e7 )
by Oscar
03:49
created

Icon::getProcessors()   B

Complexity

Conditions 6
Paths 1

Size

Total Lines 39
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 25
CRAP Score 6.0146

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 25
c 1
b 0
f 0
dl 0
loc 39
ccs 25
cts 27
cp 0.9259
rs 8.8977
cc 6
nc 1
nop 0
crap 6.0146
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\Library\FontAwesome;
13
14
use function BenTools\IterableFunctions\iterable_to_array;
15
use function Ocubom\Twig\Extension\is_string;
16
17
use Ocubom\Twig\Extension\Svg\Exception\ParseException;
18
use Ocubom\Twig\Extension\Svg\Library\FontAwesome;
19
use Ocubom\Twig\Extension\Svg\Processor\ClassProcessor;
20
use Ocubom\Twig\Extension\Svg\Processor\RemoveAttributeProcessor;
21
use Ocubom\Twig\Extension\Svg\Svg;
22
use Ocubom\Twig\Extension\Svg\Util\DomHelper;
23
use Symfony\Component\OptionsResolver\Options;
24
use Symfony\Component\OptionsResolver\OptionsResolver;
25
26
class Icon extends Svg
27
{
28
    /**
29
     * Full path to the icon.
30
     */
31
    protected \SplFileInfo $path;
32
33
    /**
34
     * @param mixed $data The FontAwesome Icon data
35
     */
36 2
    public function __construct($data, iterable $options = null)
37
    {
38
        try {
39
            switch (true) {
40 2
                case $data instanceof Icon: // "Copy" constructor
41
                    $this->path = $data->path;
42
                    break;
43
44 2
                case $data instanceof \SplFileInfo:
45 2
                    $this->path = $data;
46 2
                    break;
47
48
                case is_string($data):
0 ignored issues
show
Unused Code introduced by
is_string($data) is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
49
                    $this->path = new \SplFileInfo($data);
50
                    break;
51
52
                default:
53
                    throw new ParseException(sprintf('Unable to create "%s" from "%s"', __CLASS__, get_debug_type($data)));
54
            }
55
56
            /** @var array $options */
57 2
            $options = iterable_to_array($options ?? []);
58
59
            // Construct from path
60 2
            parent::__construct($this->path, array_merge($options, [
61 2
                'class_default' => array_merge($options['class_default'] ?? [], [
62 2
                    FontAwesome::INLINE_CLASS, // Add inlined class
63 2
                    'fa-'.$this->getName(), // Add icon name class
64 2
                ]),
65 2
                'class_block' => array_merge($options['class_block'] ?? [], [
66 2
                    $this->getStyle(), // Block style
67 2
                    $this->getStyleClass(), // Block current classes
68 2
                    $this->getStyleClass('5.0'), // Block pre-6.0 classes
69 2
                ]),
70 2
            ]));
71
72
            // Add Font Awesome data-*
73 2
            $this->svg->setAttribute('data-prefix', $this->getStyleClass('5.0'));
74 2
            $this->svg->setAttribute('data-icon', $this->getName());
75
        } catch (ParseException $exc) {
76
            throw new ParseException(sprintf('Unable to create a FontAwesome Icon from "%s"', get_debug_type($data)), 0, $exc);
77
        }
78
    }
79
80
    /**
81
     * @codeCoverageIgnore
82
     */
83
    public function getFaId(): string
84
    {
85
        return sprintf(
86
            'fa-%s-%s',
87
            $this->getStyle(),
88
            $this->getName(),
89
        );
90
    }
91
92 2
    public function getName(): string
93
    {
94 2
        return $this->path->getBasename('.svg');
95
    }
96
97 2
    public function getStyle(): string
98
    {
99 2
        $path = $this->path->getPathInfo();
100
        assert($path instanceof \SplFileInfo);
101
102 2
        return $path->getBasename();
103
    }
104
105 2
    public function getStyleClass(string $version = '6.0'): string
106
    {
107 2
        return version_compare($version, '6.0', '<')
108 2
            ? 'fa'.$this->getStyle()[0]
109 2
            : 'fa-'.$this->getStyle();
110
    }
111
112 1
    public function getHtmlTag(iterable $options = null): \DOMElement
113
    {
114
        // Create the HTML Tag node
115 1
        $node = DomHelper::createElement(FontAwesome::HTML_TAG);
116
        // Copy options as attributes
117 1
        foreach ($options ?? [] as $key => $val) {
118 1
            if (!empty($val)) {
119 1
                $val = is_iterable($val) ? implode(' ', iterable_to_array($val)) : (string) $val;
120
121 1
                $node->setAttribute($key, $val);
122
            }
123
        }
124
125
        // Process classes
126 1
        $processor = new ClassProcessor();
127 1
        $processor($node, [
128 1
            'class' => [
129 1
                $this->getStyleClass(),
130 1
                'fa-'.$this->getName(),
131 1
            ],
132 1
            'class_banned' => [
133 1
                FontAwesome::INLINE_CLASS,
134 1
            ],
135 1
        ]);
136
137 1
        return $node;
138
    }
139
140
    /**
141
     * @return array<string, array<int, callable>|callable>
142
     *
143
     * @psalm-suppress InvalidScope
144
     */
145 2
    protected static function getProcessors(): array
146
    {
147 1
        return array_merge(parent::getProcessors(), [
148
            // Options will be ignored & removed
149 1
            'class_default' => new RemoveAttributeProcessor('class_default'),
150 1
            'class_block' => new RemoveAttributeProcessor('class_block'),
151 1
            'fill' => new RemoveAttributeProcessor('fill'),
152 1
            'opacity' => new RemoveAttributeProcessor('opacity'),
153 1
            'primary_fill' => new RemoveAttributeProcessor('primary_fill'),
154 1
            'primary_opacity' => new RemoveAttributeProcessor('primary_opacity'),
155 1
            'secondary_fill' => new RemoveAttributeProcessor('secondary_fill'),
156 1
            'secondary_opacity' => new RemoveAttributeProcessor('secondary_opacity'),
157
158
            // Remove special attributes
159 1
            'data-fa-title-id' => new RemoveAttributeProcessor('data-fa-title-id'),
160
161 1
            '' => function (\DOMElement $svg, array $options = []): \DOMElement {
162
                // Add FontAwesome fill and opacity values to each path
163
                /** @var \DOMElement $path */
164 2
                foreach ($svg->getElementsByTagName('path') as $path) {
165 2
                    $class = array_intersect(
166 2
                        ['fa-primary', 'fa-secondary'],
167 2
                        preg_split('@\s+@Uis', $path->getAttribute('class'))
168 2
                    );
169 2
                    $class = count($class) > 0
170
                        ? substr($class[0], 3)
171 2
                        : '';
172
173 2
                    foreach (['fill', 'opacity'] as $name) {
174 2
                        $key = $class.'_'.$name;
175 2
                        if (!empty($options[$key])) {
176
                            $path->setAttribute($name, $options[$key]);
177 2
                        } elseif (!empty($options[$name])) {
178 2
                            $path->setAttribute($name, $options[$name]);
179
                        }
180
                    }
181
                }
182
183 2
                return $svg;
184 1
            },
185 1
        ]);
186
    }
187
188 2
    public static function configureOptions(OptionsResolver $resolver = null): OptionsResolver
189
    {
190 2
        $resolver = parent::configureOptions($resolver);
191
192
        /** @psalm-suppress MissingClosureParamType */
193 2
        $normalizeFloat = function (Options $options, $value): ?float {
194 2
            return is_numeric((string) $value) ? floatval((string) $value) : null;
195 2
        };
196
197 2
        $resolver->define('class_default')
198 2
            ->default([])
199 2
            ->allowedTypes('string[]')
200 2
            ->info('Default classes to add unless null');
201
202 2
        $resolver->define('fill')
203 2
            ->default('currentColor')
204 2
            ->allowedTypes('null', 'string')
205 2
            ->info('Default fill color for paths');
206
207 2
        $resolver->define('opacity')
208 2
            ->default(null)
209 2
            ->allowedTypes('null', 'string', 'float')
210 2
            ->normalize($normalizeFloat)
211 2
            ->info('Default opacity color for paths');
212
213 2
        $resolver->define('primary_fill')
214 2
            ->default(null)
215 2
            ->allowedTypes('null', 'string')
216 2
            ->info('Default fill color for primary paths (duotone)');
217
218 2
        $resolver->define('primary_opacity')
219 2
            ->default(null)
220 2
            ->allowedTypes('null', 'string', 'float')
221 2
            ->normalize($normalizeFloat)
222 2
            ->info('Default opacity color for primary paths (duotone)');
223
224 2
        $resolver->define('secondary_fill')
225 2
            ->default(null)
226 2
            ->allowedTypes('null', 'string')
227 2
            ->info('Default fill color for secondary paths (duotone)');
228
229 2
        $resolver->define('secondary_opacity')
230 2
            ->default(null)
231 2
            ->allowedTypes('null', 'string', 'float')
232 2
            ->normalize($normalizeFloat)
233 2
            ->info('Default opacity color for secondary paths (duotone)');
234
235 2
        $resolver->define('data-fa-title-id')
236 2
            ->default(null)
237 2
            ->allowedTypes('null', 'string')
238 2
            ->info('Set the icon title id instead of generate a new one');
239
240
        // Uses data-fa-title-id as aria-labelledby if not defined
241 2
        $resolver->addNormalizer(
242 2
            'aria-labelledby',
243 2
            function (Options $options, ?string $value): ?string {
244 2
                if (empty($value) && !empty($options['data-fa-title-id'])) {
245 1
                    return $options['data-fa-title-id'];
246
                }
247
248 2
                return $value;
249 2
            },
250 2
            true
251 2
        );
252
253 2
        return $resolver;
254
    }
255
}
256