Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 36
0.00% covered (danger)
0.00%
0 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
HeadScript
0.00% covered (danger)
0.00%
0 / 36
0.00% covered (danger)
0.00%
0 / 10
462
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 getFileType
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 itemToString
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
30
 forcePrependFile
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
30
 isExcludedFromConcat
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 getResourceFilePath
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setResourceFilePath
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getMinifier
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getMinifiedData
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 addNonce
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3/**
4 * Head script view helper (extended for VuFind's theme system)
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  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 VuFindTheme\View\Helper;
31
32use VuFindTheme\ThemeInfo;
33
34use function array_key_exists;
35
36/**
37 * Head script view helper (extended for VuFind's theme system)
38 *
39 * @category VuFind
40 * @package  View_Helpers
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/wiki/development Wiki
44 *
45 * @method getWhitespace(string|int $indent)
46 * @method getIndent()
47 * @method getSeparator()
48 */
49class HeadScript extends \Laminas\View\Helper\HeadScript implements \Laminas\Log\LoggerAwareInterface
50{
51    use ConcatTrait {
52        getMinifiedData as getBaseMinifiedData;
53    }
54    use RelativePathTrait;
55    use \VuFind\Log\LoggerAwareTrait;
56
57    /**
58     * Theme information service
59     *
60     * @var ThemeInfo
61     */
62    protected $themeInfo;
63
64    /**
65     * CSP nonce
66     *
67     * @var string
68     */
69    protected $cspNonce;
70
71    /**
72     * Constructor
73     *
74     * @param ThemeInfo   $themeInfo Theme information service
75     * @param string|bool $plconfig  Config for current application environment
76     * @param string      $nonce     Nonce from nonce generator
77     */
78    public function __construct(ThemeInfo $themeInfo, $plconfig = false, $nonce = '')
79    {
80        parent::__construct();
81        $this->themeInfo = $themeInfo;
82        $this->usePipeline = $this->enabledInConfig($plconfig);
83        $this->cspNonce = $nonce;
84        $this->optionalAttributes[] = 'nonce';
85    }
86
87    /**
88     * Folder name and file extension for trait
89     *
90     * @return string
91     */
92    protected function getFileType()
93    {
94        return 'js';
95    }
96
97    /**
98     * Create script HTML
99     *
100     * @param mixed  $item        Item to convert
101     * @param string $indent      String to add before the item
102     * @param string $escapeStart Starting sequence
103     * @param string $escapeEnd   Ending sequence
104     *
105     * @return string
106     */
107    public function itemToString($item, $indent, $escapeStart, $escapeEnd)
108    {
109        // Normalize href to account for themes (if appropriate):
110        if (!empty($item->attributes['src']) && $this->isRelativePath($item->attributes['src'])) {
111            $relPath = 'js/' . $item->attributes['src'];
112            $details = $this->themeInfo
113                ->findContainingTheme($relPath, ThemeInfo::RETURN_ALL_DETAILS);
114
115            if (!empty($details)) {
116                $urlHelper = $this->getView()->plugin('url');
117                $url = $urlHelper('home') . "themes/{$details['theme']}/" . $relPath;
118                $url .= strstr($url, '?') ? '&_=' : '?_=';
119                $url .= filemtime($details['path']);
120                $item->attributes['src'] = $url;
121            }
122        }
123
124        $this->addNonce($item);
125        return parent::itemToString($item, $indent, $escapeStart, $escapeEnd);
126    }
127
128    /**
129     * Forcibly prepend a file removing it from any existing position
130     *
131     * @param string $src   Script src
132     * @param string $type  Script type
133     * @param array  $attrs Array of script attributes
134     *
135     * @return void
136     */
137    public function forcePrependFile(
138        $src = null,
139        $type = 'text/javascript',
140        array $attrs = []
141    ) {
142        // Look for existing entry and remove it if found. Comparison method
143        // copied from isDuplicate().
144        foreach ($this->getContainer() as $offset => $item) {
145            if (
146                ($item->source === null)
147                && array_key_exists('src', $item->attributes)
148                && ($src === $item->attributes['src'])
149            ) {
150                $this->offsetUnset($offset);
151                break;
152            }
153        }
154        parent::prependFile($src, $type, $attrs);
155    }
156
157    /**
158     * Returns true if file should not be included in the compressed concat file
159     * Required by ConcatTrait
160     *
161     * @param stdClass $item Script element object
162     *
163     * @return bool
164     */
165    protected function isExcludedFromConcat($item)
166    {
167        return empty($item->attributes['src'])
168            || isset($item->attributes['conditional'])
169            || strpos($item->attributes['src'], '://');
170    }
171
172    /**
173     * Get the file path from the script object
174     * Required by ConcatTrait
175     *
176     * @param stdClass $item Script element object
177     *
178     * @return string
179     */
180    protected function getResourceFilePath($item)
181    {
182        return $item->attributes['src'];
183    }
184
185    /**
186     * Set the file path of the script object
187     * Required by ConcatTrait
188     *
189     * @param stdClass $item Script element object
190     * @param string   $path New path string
191     *
192     * @return stdClass
193     */
194    protected function setResourceFilePath($item, $path)
195    {
196        $item->attributes['src'] = $path;
197        return $item;
198    }
199
200    /**
201     * Get the minifier that can handle these file types
202     * Required by ConcatTrait
203     *
204     * @return \MatthiasMullie\Minify\JS
205     */
206    protected function getMinifier()
207    {
208        return new \MatthiasMullie\Minify\JS();
209    }
210
211    /**
212     * Get minified data for a file
213     *
214     * @param array  $details    File details
215     * @param string $concatPath Target path for the resulting file (used in minifier
216     * for path mapping)
217     *
218     * @throws \Exception
219     * @return string
220     */
221    protected function getMinifiedData($details, $concatPath)
222    {
223        $data = $this->getBaseMinifiedData($details, $concatPath);
224        // Play it safe by terminating a script with a semicolon
225        if (!str_ends_with(trim($data), ';')) {
226            $data .= ';';
227        }
228        return $data;
229    }
230
231    /**
232     * Add a nonce to the item
233     *
234     * @param stdClass $item Item
235     *
236     * @return void
237     */
238    protected function addNonce($item)
239    {
240        $item->attributes['nonce'] = $this->cspNonce;
241    }
242}