Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
79.79% |
75 / 94 |
|
72.22% |
13 / 18 |
CRAP | |
0.00% |
0 / 1 |
ResourceContainer | |
79.79% |
75 / 94 |
|
72.22% |
13 / 18 |
78.08 | |
0.00% |
0 / 1 |
addCss | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
5 | |||
addJs | |
87.50% |
7 / 8 |
|
0.00% |
0 / 1 |
7.10 | |||
addJsEntry | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
addJsStringEntry | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
2 | |||
addJsArrayEntry | |
68.42% |
13 / 19 |
|
0.00% |
0 / 1 |
10.02 | |||
removeEntry | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
insertEntry | |
76.47% |
13 / 17 |
|
0.00% |
0 / 1 |
11.30 | |||
getCss | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getJs | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
2 | |||
parseSetting | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
4 | |||
setEncoding | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getEncoding | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setFavicon | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getFavicon | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
setGenerator | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getGenerator | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
dynamicallyParsed | |
40.00% |
2 / 5 |
|
0.00% |
0 / 1 |
2.86 | |||
removeCSS | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 |
1 | <?php |
2 | |
3 | /** |
4 | * VuFind Theme Public Resource Handler (for CSS, JS, etc.) |
5 | * |
6 | * PHP version 8 |
7 | * |
8 | * Copyright (C) Villanova University 2010. |
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 Theme |
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 Main Site |
28 | */ |
29 | |
30 | namespace VuFindTheme; |
31 | |
32 | use function count; |
33 | use function in_array; |
34 | use function is_array; |
35 | |
36 | /** |
37 | * VuFind Theme Public Resource Handler (for CSS, JS, etc.) |
38 | * |
39 | * @category VuFind |
40 | * @package Theme |
41 | * @author Demian Katz <demian.katz@villanova.edu> |
42 | * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License |
43 | * @link https://vufind.org Main Site |
44 | */ |
45 | class ResourceContainer |
46 | { |
47 | use \VuFind\Log\VarDumperTrait; |
48 | |
49 | /** |
50 | * Less CSS files |
51 | * |
52 | * @var array |
53 | */ |
54 | protected $less = []; |
55 | |
56 | /** |
57 | * CSS files |
58 | * |
59 | * @var array |
60 | */ |
61 | protected $css = []; |
62 | |
63 | /** |
64 | * Javascript files |
65 | * |
66 | * @var array |
67 | */ |
68 | protected $js = []; |
69 | |
70 | /** |
71 | * Favicon |
72 | * |
73 | * @var string|array|null |
74 | */ |
75 | protected $favicon = null; |
76 | |
77 | /** |
78 | * Encoding type |
79 | * |
80 | * @var string |
81 | */ |
82 | protected $encoding = 'UTF-8'; |
83 | |
84 | /** |
85 | * Generator value for <meta> tag |
86 | * |
87 | * @var string |
88 | */ |
89 | protected $generator = ''; |
90 | |
91 | /** |
92 | * Add a CSS file. |
93 | * |
94 | * @param array|string $css CSS file (or array of CSS files) to add (possibly |
95 | * with extra settings from theme config appended to each filename string). |
96 | * |
97 | * @return void |
98 | */ |
99 | public function addCss($css) |
100 | { |
101 | if (!is_array($css) && !is_a($css, 'Traversable')) { |
102 | $css = [$css]; |
103 | } |
104 | foreach ($css as $current) { |
105 | if (!$this->dynamicallyParsed($current)) { |
106 | $this->css[] = $current; |
107 | } |
108 | } |
109 | } |
110 | |
111 | /** |
112 | * Add a Javascript file. |
113 | * |
114 | * @param array|string $js Javascript file (or array of files) to add (possibly |
115 | * with extra settings from theme config appended to each filename string). |
116 | * |
117 | * @return void |
118 | */ |
119 | public function addJs($js) |
120 | { |
121 | if ((!is_array($js) && !is_a($js, 'Traversable')) || isset($js['file'])) { |
122 | $this->addJsEntry($js); |
123 | } elseif (isset($js[0])) { |
124 | foreach ($js as $current) { |
125 | $this->addJsEntry($current); |
126 | } |
127 | } elseif ($js === []) { |
128 | return; |
129 | } else { |
130 | throw new \Exception('Invalid JS entry format: ' . $this->varDump($js)); |
131 | } |
132 | } |
133 | |
134 | /** |
135 | * Helper function for adding a Javascript file. |
136 | * |
137 | * @param string|array $jsEntry Entry to add, either as string with path |
138 | * or array with additional properties. |
139 | * |
140 | * @return void |
141 | */ |
142 | protected function addJsEntry($jsEntry) |
143 | { |
144 | if (!is_array($jsEntry)) { |
145 | $this->addJsStringEntry($jsEntry); |
146 | } else { |
147 | $this->addJsArrayEntry($jsEntry); |
148 | } |
149 | } |
150 | |
151 | /** |
152 | * Helper function for adding a Javascript file which is described as string. |
153 | * |
154 | * @param string $jsEntry Entry to add as string. |
155 | * |
156 | * @return void |
157 | */ |
158 | protected function addJsStringEntry($jsEntry) |
159 | { |
160 | $parts = $this->parseSetting($jsEntry); |
161 | if (count($parts) == 1) { |
162 | $jsEntry = ['file' => $jsEntry]; |
163 | } else { |
164 | $jsEntry = [ |
165 | 'file' => $parts[0], |
166 | 'attributes' => ['conditional' => trim($parts[1])], |
167 | ]; |
168 | } |
169 | $this->addJsArrayEntry($jsEntry); |
170 | } |
171 | |
172 | /** |
173 | * Helper function for adding a Javascript file which is described as array. |
174 | * |
175 | * @param string $jsEntry Entry to add as string. |
176 | * |
177 | * @return void |
178 | */ |
179 | protected function addJsArrayEntry($jsEntry) |
180 | { |
181 | if (!isset($jsEntry['position'])) { |
182 | $jsEntry['position'] = 'header'; |
183 | } |
184 | |
185 | if (isset($jsEntry['priority']) && isset($jsEntry['load_after'])) { |
186 | throw new \Exception( |
187 | 'Using "priority" as well as "load_after" in the same entry ' |
188 | . 'is not supported: "' . $jsEntry['file'] . '"' |
189 | ); |
190 | } |
191 | |
192 | // If we are disabling the dependency, remove it now. |
193 | if ($jsEntry['disabled'] ?? false) { |
194 | $this->removeEntry($jsEntry, $this->js); |
195 | return; |
196 | } |
197 | |
198 | foreach ($this->js as $existingEntry) { |
199 | if ($existingEntry['file'] == $jsEntry['file']) { |
200 | // If we have the same settings as before, just skip this entry. |
201 | if ($existingEntry == $jsEntry) { |
202 | return; |
203 | } |
204 | |
205 | throw new \Exception( |
206 | 'Overriding an existing dependency is not supported: ' |
207 | . '"' . $jsEntry['file'] . '"' |
208 | ); |
209 | } |
210 | } |
211 | |
212 | $this->insertEntry($jsEntry, $this->js); |
213 | } |
214 | |
215 | /** |
216 | * Helper function to remove an entry from an array based on filename. |
217 | * |
218 | * @param array $entry The entry to remove. |
219 | * @param array $array The array from which the entry shall be removed. |
220 | * |
221 | * @return void |
222 | */ |
223 | protected function removeEntry($entry, &$array) |
224 | { |
225 | foreach (array_keys($array) as $i) { |
226 | if (($array[$i]['file'] ?? '') === ($entry['file'] ?? null)) { |
227 | unset($array[$i]); |
228 | return; |
229 | } |
230 | } |
231 | } |
232 | |
233 | /** |
234 | * Helper function to insert an entry to an array, |
235 | * also considering priority and dependency, if existing. |
236 | * |
237 | * @param array $entry The entry to insert. |
238 | * @param array $array The array into which the entry shall be inserted. |
239 | * |
240 | * @return void |
241 | */ |
242 | protected function insertEntry($entry, &$array) |
243 | { |
244 | if (isset($entry['priority']) || isset($entry['load_after'])) { |
245 | foreach (array_keys($array) as $i) { |
246 | if (isset($entry['priority'])) { |
247 | $currentPriority = $array[$i]['priority'] ?? null; |
248 | if ( |
249 | !isset($currentPriority) |
250 | || $currentPriority > $entry['priority'] |
251 | ) { |
252 | array_splice($array, $i, 0, [$entry]); |
253 | return; |
254 | } |
255 | } elseif (isset($entry['load_after'])) { |
256 | if ($entry['load_after'] === $array[$i]['file']) { |
257 | array_splice($array, $i + 1, 0, [$entry]); |
258 | return; |
259 | } |
260 | } |
261 | } |
262 | |
263 | if (isset($entry['load_after'])) { |
264 | throw new \Exception( |
265 | 'Dependency not found: ' . $entry['load_after'] |
266 | ); |
267 | } |
268 | } |
269 | |
270 | // Insert at end if either no priority/dependency is given |
271 | // or no other element has been found |
272 | $array[] = $entry; |
273 | } |
274 | |
275 | /** |
276 | * Get CSS files. |
277 | * |
278 | * @return array |
279 | */ |
280 | public function getCss() |
281 | { |
282 | return array_unique($this->css); |
283 | } |
284 | |
285 | /** |
286 | * Get Javascript files. |
287 | * |
288 | * @param string $position Position where the files should be inserted |
289 | * (allowed values are 'header' or 'footer'). |
290 | * |
291 | * @return array |
292 | */ |
293 | public function getJs(string $position = null) |
294 | { |
295 | if (!isset($position)) { |
296 | return $this->js; |
297 | } else { |
298 | return array_filter( |
299 | $this->js, |
300 | function ($jsFile) use ($position) { |
301 | return $jsFile['position'] == $position; |
302 | } |
303 | ); |
304 | } |
305 | } |
306 | |
307 | /** |
308 | * Given a colon-delimited configuration string, break it apart, making sure |
309 | * that URLs in the first position are not inappropriately split. |
310 | * |
311 | * @param string $current Setting to parse |
312 | * |
313 | * @return array |
314 | */ |
315 | public function parseSetting($current) |
316 | { |
317 | // TODO: replace this method with a deprecation warning when all configs |
318 | // have been converted to arrays |
319 | $parts = explode(':', $current); |
320 | // Special case: don't explode URLs: |
321 | if ( |
322 | ($parts[0] === 'http' || $parts[0] === 'https') |
323 | && str_starts_with($parts[1], '//') |
324 | ) { |
325 | $protocol = array_shift($parts); |
326 | $parts[0] = $protocol . ':' . $parts[0]; |
327 | } |
328 | return $parts; |
329 | } |
330 | |
331 | /** |
332 | * Set the encoding. |
333 | * |
334 | * @param string $e New encoding |
335 | * |
336 | * @return void |
337 | */ |
338 | public function setEncoding($e) |
339 | { |
340 | $this->encoding = $e; |
341 | } |
342 | |
343 | /** |
344 | * Get the encoding. |
345 | * |
346 | * @return void |
347 | */ |
348 | public function getEncoding() |
349 | { |
350 | return $this->encoding; |
351 | } |
352 | |
353 | /** |
354 | * Set the favicon. |
355 | * |
356 | * @param string|array $favicon New favicon path. |
357 | * |
358 | * @return void |
359 | */ |
360 | public function setFavicon($favicon) |
361 | { |
362 | $this->favicon = $favicon; |
363 | } |
364 | |
365 | /** |
366 | * Get the favicon (null for none). |
367 | * |
368 | * @return string|array|null |
369 | */ |
370 | public function getFavicon() |
371 | { |
372 | return $this->favicon; |
373 | } |
374 | |
375 | /** |
376 | * Set the generator. |
377 | * |
378 | * @param string $generator New generator. |
379 | * |
380 | * @return void |
381 | */ |
382 | public function setGenerator($generator) |
383 | { |
384 | $this->generator = $generator; |
385 | } |
386 | |
387 | /** |
388 | * Get the generator. |
389 | * |
390 | * @return string |
391 | */ |
392 | public function getGenerator() |
393 | { |
394 | return $this->generator; |
395 | } |
396 | |
397 | /** |
398 | * Check if a CSS file is being dynamically compiled in LESS |
399 | * |
400 | * @param string $file Filename to check |
401 | * |
402 | * @return bool |
403 | */ |
404 | protected function dynamicallyParsed($file) |
405 | { |
406 | if (empty($this->less)) { |
407 | return false; |
408 | } |
409 | [$fileName, ] = explode('.', $file); |
410 | $lessFile = $fileName . '.less'; |
411 | return in_array($lessFile, $this->less, true); |
412 | } |
413 | |
414 | /** |
415 | * Remove a CSS file if it matches another file's name |
416 | * |
417 | * @param string $file Filename to remove |
418 | * |
419 | * @return void |
420 | */ |
421 | protected function removeCSS($file) |
422 | { |
423 | [$name, ] = explode('.', $file); |
424 | $name .= '.css'; |
425 | $index = array_search($name, $this->css); |
426 | if (false !== $index) { |
427 | unset($this->css[$index]); |
428 | } |
429 | } |
430 | } |