Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
57 / 57
100.00% covered (success)
100.00%
5 / 5
CRAP
100.00% covered (success)
100.00%
1 / 1
Icon
100.00% covered (success)
100.00%
57 / 57
100.00% covered (success)
100.00%
5 / 5
14
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 mapIcon
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
5
 compileAttrs
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 cacheKey
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 __invoke
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3/**
4 * Icon view helper
5 *
6 * PHP version 8
7 *
8 * Copyright (C) Villanova University 2020.
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License version 2,
12 * as published by the Free Software Foundation.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
22 *
23 * @category VuFind
24 * @package  View_Helpers
25 * @author   Demian Katz <demian.katz@villanova.edu>
26 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
27 * @link     https://vufind.org/wiki/development Wiki
28 */
29
30namespace VuFind\View\Helper\Root;
31
32use Laminas\Cache\Storage\StorageInterface;
33use Laminas\View\Helper\AbstractHelper;
34use Laminas\View\Helper\EscapeHtmlAttr;
35
36use function in_array;
37use function is_string;
38
39/**
40 * Icon view helper
41 *
42 * @category VuFind
43 * @package  View_Helpers
44 * @author   Demian Katz <demian.katz@villanova.edu>
45 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
46 * @link     https://vufind.org/wiki/development Wiki
47 */
48class Icon extends AbstractHelper
49{
50    /**
51     * Icon config from theme.config.php
52     *
53     * @var array
54     */
55    protected $config;
56
57    /**
58     * Default icon set
59     *
60     * @var string
61     */
62    protected $defaultSet;
63
64    /**
65     * Default icon template
66     *
67     * @var string
68     */
69    protected $defaultTemplate;
70
71    /**
72     * Transforming map
73     *
74     * @var array
75     */
76    protected $iconMap;
77
78    /**
79     * Cache for icons
80     *
81     * @var StorageInterface
82     */
83    protected $cache;
84
85    /**
86     * Escape helper
87     *
88     * @var EscapeHtmlAttr
89     */
90    protected $esc;
91
92    /**
93     * Are we in right to left text mode?
94     *
95     * @var boolean
96     */
97    protected $rtl;
98
99    /**
100     * Prevent extra work by only appending the stylesheet once
101     *
102     * @var boolean
103     */
104    protected $styleAppended = false;
105
106    /**
107     * Constructor
108     *
109     * @param array            $config  Icon configuration
110     * @param StorageInterface $cache   Cache instance
111     * @param EscapeHtmlAttr   $escAttr EscapeHtmlAttr view helper
112     * @param bool             $rtl     Are we in right to left text mode?
113     */
114    public function __construct(
115        array $config,
116        StorageInterface $cache,
117        EscapeHtmlAttr $escAttr,
118        bool $rtl = false
119    ) {
120        $this->config = $config;
121        $this->defaultSet = $this->config['defaultSet'] ?? 'FontAwesome';
122        $this->defaultTemplate = $this->config['defaultTemplate'] ?? 'font';
123        $this->iconMap = $this->config['aliases'] ?? [];
124        $this->cache = $cache;
125        $this->esc = $escAttr;
126        $this->rtl = $rtl;
127    }
128
129    /**
130     * Map icon to set. Add prefix, return with set and template.
131     * Broken out for easier customization.
132     *
133     * @param string $name       Icon name or key from theme.config.php
134     * @param array  $aliasTrail Safety mechanism to prevent circular aliases
135     *
136     * @return array
137     */
138    protected function mapIcon(string $name, $aliasTrail = []): array
139    {
140        $rtl = $this->rtl ? '-rtl' : '';
141        $icon = $this->iconMap[$name . $rtl] ?? $this->iconMap[$name] ?? $name;
142        $set = $this->defaultSet;
143        $class = null;
144
145        // Override set from config (ie. FontAwesome:icon)
146        if (str_contains($icon, ':')) {
147            $parts = explode(':', $icon, 3);
148            $set = $parts[0];
149            $icon = $parts[1];
150            $class = $parts[2] ?? null;
151        }
152
153        // Special case: aliases:
154        if ($set === 'Alias') {
155            $aliasTrail[] = $name;
156            if (in_array($icon, $aliasTrail)) {
157                throw new \Exception("Circular icon alias detected: $icon!");
158            }
159            return $this->mapIcon($icon, $aliasTrail);
160        }
161
162        // Find set in theme.config.php
163        $setConfig = $this->config['sets'][$set] ?? [];
164        $template = $setConfig['template'] ?? $this->defaultTemplate;
165        $prefix = $setConfig['prefix'] ?? '';
166
167        return [$prefix . $icon, $set, $template, $class];
168    }
169
170    /**
171     * Reduce extra parameters to one attribute string.
172     * Broken out for easier customization.
173     *
174     * @param array $attrs Additional HTML attributes for the HTML tag
175     *
176     * @return string
177     */
178    protected function compileAttrs(array $attrs): string
179    {
180        $attrStr = '';
181        foreach ($attrs as $key => $val) {
182            // class gets special handling in the template; don't use it now:
183            if ($key == 'class') {
184                continue;
185            }
186            $attrStr .= ' ' . $key . '="' . ($this->esc)($val) . '"';
187        }
188        return $attrStr;
189    }
190
191    /**
192     * Create a unique key for icon names and extra attributes
193     *
194     * @param string $name  Icon name or key from theme.config.php
195     * @param array  $attrs Additional HTML attributes for the HTML tag
196     *
197     * @return string
198     */
199    protected function cacheKey(string $name, $attrs = []): string
200    {
201        if (empty($attrs)) {
202            return $name;
203        }
204        ksort($attrs);
205        return $name . '+' . md5(json_encode($attrs));
206    }
207
208    /**
209     * Returns inline HTML for icon
210     *
211     * @param string       $name  Which icon?
212     * @param array|string $attrs Additional HTML attributes
213     *
214     * @return string
215     */
216    public function __invoke(string $name, $attrs = []): string
217    {
218        // Class name shortcut
219        if (is_string($attrs)) {
220            $attrs = ['class' => $attrs];
221        }
222
223        $cacheKey = $this->cacheKey($name, $attrs);
224        $cached = $this->cache->getItem($cacheKey);
225
226        if ($cached == null) {
227            [$icon, $set, $template, $class] = $this->mapIcon($name);
228            $attrs['class'] = trim(($attrs['class'] ?? '') . ' ' . $class);
229
230            // Surface set config and add icon and attrs
231            $cached = trim(
232                $this->getView()->render(
233                    'Helpers/icons/' . $template,
234                    array_merge(
235                        $this->config['sets'][$set] ?? [],
236                        [
237                            'icon' => $icon,
238                            'attrs' => $this->compileAttrs($attrs),
239                            'extra' => $attrs,
240                        ]
241                    )
242                )
243            );
244
245            $this->cache->setItem($cacheKey, $cached);
246        }
247
248        return $cached;
249    }
250}