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
|
|
|
|
16
|
|
|
use Ocubom\Twig\Extension\Svg\Exception\ParseException; |
17
|
|
|
use Ocubom\Twig\Extension\Svg\Library\FontAwesome; |
18
|
|
|
use Ocubom\Twig\Extension\Svg\Processor\ClassProcessor; |
19
|
|
|
use Ocubom\Twig\Extension\Svg\Processor\RemoveAttributeProcessor; |
20
|
|
|
use Ocubom\Twig\Extension\Svg\Svg; |
21
|
|
|
use Ocubom\Twig\Extension\Svg\Util\DomHelper; |
22
|
|
|
use Symfony\Component\OptionsResolver\Options; |
23
|
|
|
use Symfony\Component\OptionsResolver\OptionsResolver; |
24
|
|
|
|
25
|
|
|
class Icon extends Svg |
26
|
|
|
{ |
27
|
|
|
/** |
28
|
|
|
* Full path to the icon. |
29
|
|
|
*/ |
30
|
|
|
protected \SplFileInfo $path; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* @param mixed $data The FontAwesome Icon data |
34
|
|
|
*/ |
35
|
2 |
|
public function __construct($data, iterable $options = null) |
36
|
|
|
{ |
37
|
|
|
try { |
38
|
|
|
switch (true) { |
39
|
2 |
|
case $data instanceof Icon: // "Copy" constructor |
40
|
|
|
$this->path = $data->path; |
41
|
|
|
break; |
42
|
|
|
|
43
|
2 |
|
case $data instanceof \SplFileInfo: |
44
|
2 |
|
$this->path = $data; |
45
|
2 |
|
break; |
46
|
|
|
|
47
|
|
|
case is_string($data): |
|
|
|
|
48
|
|
|
$this->path = new \SplFileInfo($data); |
49
|
|
|
break; |
50
|
|
|
|
51
|
|
|
default: |
52
|
|
|
throw new ParseException(sprintf('Unable to create "%s" from "%s"', __CLASS__, get_debug_type($data))); |
53
|
|
|
} |
54
|
|
|
|
55
|
|
|
/** @var array $options */ |
56
|
2 |
|
$options = iterable_to_array($options ?? []); |
57
|
|
|
|
58
|
|
|
// Construct from path |
59
|
2 |
|
parent::__construct($this->path, array_merge($options, [ |
60
|
2 |
|
'class_default' => array_merge($options['class_default'] ?? [], [ |
61
|
2 |
|
FontAwesome::INLINE_CLASS, // Add inlined class |
62
|
2 |
|
'fa-'.$this->getName(), // Add icon name class |
63
|
2 |
|
]), |
64
|
2 |
|
'class_block' => array_merge($options['class_block'] ?? [], [ |
65
|
2 |
|
$this->getStyle(), // Block style |
66
|
2 |
|
$this->getStyleClass(), // Block current classes |
67
|
2 |
|
$this->getStyleClass('5.0'), // Block pre-6.0 classes |
68
|
2 |
|
]), |
69
|
2 |
|
])); |
70
|
|
|
|
71
|
|
|
// Add Font Awesome data-* |
72
|
2 |
|
$this->svg->setAttribute('data-prefix', $this->getStyleClass('5.0')); |
73
|
2 |
|
$this->svg->setAttribute('data-icon', $this->getName()); |
74
|
|
|
} catch (ParseException $exc) { |
75
|
|
|
throw new ParseException(sprintf('Unable to create a FontAwesome Icon from "%s"', get_debug_type($data)), 0, $exc); |
76
|
|
|
} |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* @codeCoverageIgnore |
81
|
|
|
*/ |
82
|
|
|
public function getFaId(): string |
83
|
|
|
{ |
84
|
|
|
return sprintf( |
85
|
|
|
'fa-%s-%s', |
86
|
|
|
$this->getStyle(), |
87
|
|
|
$this->getName(), |
88
|
|
|
); |
89
|
|
|
} |
90
|
|
|
|
91
|
2 |
|
public function getName(): string |
92
|
|
|
{ |
93
|
2 |
|
return $this->path->getBasename('.svg'); |
94
|
|
|
} |
95
|
|
|
|
96
|
2 |
|
public function getStyle(): string |
97
|
|
|
{ |
98
|
2 |
|
$path = $this->path->getPathInfo(); |
99
|
|
|
assert($path instanceof \SplFileInfo); |
100
|
|
|
|
101
|
2 |
|
return $path->getBasename(); |
102
|
|
|
} |
103
|
|
|
|
104
|
2 |
|
public function getStyleClass(string $version = '6.0'): string |
105
|
|
|
{ |
106
|
2 |
|
return version_compare($version, '6.0', '<') |
107
|
2 |
|
? 'fa'.$this->getStyle()[0] |
108
|
2 |
|
: 'fa-'.$this->getStyle(); |
109
|
|
|
} |
110
|
|
|
|
111
|
1 |
|
public function getHtmlTag(iterable $options = null): \DOMElement |
112
|
|
|
{ |
113
|
|
|
// Create the HTML Tag node |
114
|
1 |
|
$node = DomHelper::createElement(FontAwesome::HTML_TAG); |
115
|
|
|
// Copy options as attributes |
116
|
1 |
|
foreach ($options ?? [] as $key => $val) { |
117
|
1 |
|
if (!empty($val)) { |
118
|
1 |
|
$val = is_iterable($val) ? implode(' ', iterable_to_array($val)) : (string) $val; |
119
|
|
|
|
120
|
1 |
|
$node->setAttribute($key, $val); |
121
|
|
|
} |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
// Process classes |
125
|
1 |
|
$processor = new ClassProcessor(); |
126
|
1 |
|
$processor($node, [ |
127
|
1 |
|
'class' => [ |
128
|
1 |
|
$this->getStyleClass(), |
129
|
1 |
|
'fa-'.$this->getName(), |
130
|
1 |
|
], |
131
|
1 |
|
'class_banned' => [ |
132
|
1 |
|
FontAwesome::INLINE_CLASS, |
133
|
1 |
|
], |
134
|
1 |
|
]); |
135
|
|
|
|
136
|
1 |
|
return $node; |
137
|
|
|
} |
138
|
|
|
|
139
|
|
|
/** |
140
|
|
|
* @return array<string, array<int, callable>|callable> |
141
|
|
|
* |
142
|
|
|
* @psalm-suppress InvalidScope |
143
|
|
|
*/ |
144
|
1 |
|
protected static function getProcessors(): array |
145
|
|
|
{ |
146
|
1 |
|
return array_merge(parent::getProcessors(), [ |
147
|
|
|
// Options will be ignored & removed |
148
|
1 |
|
'class_default' => new RemoveAttributeProcessor('class_default'), |
149
|
1 |
|
'class_block' => new RemoveAttributeProcessor('class_block'), |
150
|
1 |
|
'fill' => new RemoveAttributeProcessor('fill'), |
151
|
1 |
|
'opacity' => new RemoveAttributeProcessor('opacity'), |
152
|
1 |
|
'primary_fill' => new RemoveAttributeProcessor('primary_fill'), |
153
|
1 |
|
'primary_opacity' => new RemoveAttributeProcessor('primary_opacity'), |
154
|
1 |
|
'secondary_fill' => new RemoveAttributeProcessor('secondary_fill'), |
155
|
1 |
|
'secondary_opacity' => new RemoveAttributeProcessor('secondary_opacity'), |
156
|
|
|
|
157
|
|
|
// Remove special attributes |
158
|
1 |
|
'data-fa-title-id' => new RemoveAttributeProcessor('data-fa-title-id'), |
159
|
1 |
|
]); |
160
|
|
|
} |
161
|
|
|
|
162
|
2 |
|
public static function configureOptions(OptionsResolver $resolver = null): OptionsResolver |
163
|
|
|
{ |
164
|
2 |
|
$resolver = parent::configureOptions($resolver); |
165
|
|
|
|
166
|
2 |
|
$resolver->define('class_default') |
167
|
2 |
|
->default([]) |
168
|
2 |
|
->allowedTypes('string[]') |
169
|
2 |
|
->info('Default classes to add unless null'); |
170
|
|
|
|
171
|
2 |
|
$resolver->define('fill') |
172
|
2 |
|
->default('currentColor') |
173
|
2 |
|
->allowedTypes('null', 'string') |
174
|
2 |
|
->info('Default fill color for paths'); |
175
|
|
|
|
176
|
2 |
|
$resolver->define('opacity') |
177
|
2 |
|
->default(null) |
178
|
2 |
|
->allowedTypes('null', 'float') |
179
|
2 |
|
->info('Default opacity color for paths'); |
180
|
|
|
|
181
|
2 |
|
$resolver->define('primary_fill') |
182
|
2 |
|
->default(null) |
183
|
2 |
|
->allowedTypes('null', 'string') |
184
|
2 |
|
->info('Default fill color for primary paths (duotone)'); |
185
|
|
|
|
186
|
2 |
|
$resolver->define('primary_opacity') |
187
|
2 |
|
->default(null) |
188
|
2 |
|
->allowedTypes('null', 'float') |
189
|
2 |
|
->info('Default opacity color for primary paths (duotone)'); |
190
|
|
|
|
191
|
2 |
|
$resolver->define('secondary_fill') |
192
|
2 |
|
->default(null) |
193
|
2 |
|
->allowedTypes('null', 'string') |
194
|
2 |
|
->info('Default fill color for secondary paths (duotone)'); |
195
|
|
|
|
196
|
2 |
|
$resolver->define('secondary_opacity') |
197
|
2 |
|
->default(null) |
198
|
2 |
|
->allowedTypes('null', 'float') |
199
|
2 |
|
->info('Default opacity color for secondary paths (duotone)'); |
200
|
|
|
|
201
|
2 |
|
$resolver->define('data-fa-title-id') |
202
|
2 |
|
->default(null) |
203
|
2 |
|
->allowedTypes('null', 'string') |
204
|
2 |
|
->info('Set the icon title id instead of generate a new one'); |
205
|
|
|
|
206
|
|
|
// Uses data-fa-title-id as aria-labelledby if not defined |
207
|
2 |
|
$resolver->addNormalizer( |
208
|
2 |
|
'aria-labelledby', |
209
|
2 |
|
function (Options $options, ?string $value): ?string { |
210
|
2 |
|
if (empty($value) && !empty($options['data-fa-title-id'])) { |
211
|
1 |
|
return $options['data-fa-title-id']; |
212
|
|
|
} |
213
|
|
|
|
214
|
2 |
|
return $value; |
215
|
2 |
|
}, |
216
|
2 |
|
true |
217
|
2 |
|
); |
218
|
|
|
|
219
|
2 |
|
return $resolver; |
220
|
|
|
} |
221
|
|
|
} |
222
|
|
|
|
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
orexit
statements that have been added for debug purposes.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.