diff --git a/composer.json b/composer.json index f4f11f1455c..c7a4383ec50 100644 --- a/composer.json +++ b/composer.json @@ -84,7 +84,7 @@ "laminas/laminas-session": "2.21.0", "laminas/laminas-stdlib": "3.19.0", "laminas/laminas-validator": "2.64.2", - "laminas/laminas-view": "2.27.0", + "laminas/laminas-view": "2.36.0", "laminas/laminas-translator": "^1", "league/commonmark": "2.6.0", "league/oauth2-client": "^2.7", diff --git a/composer.lock b/composer.lock index a6653927b63..72caf38e931 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d1765a7b30d0a5f737a778342035f65a", + "content-hash": "f92582e109e229436cb116a0b635cde6", "packages": [ { "name": "ahand/mobileesp", @@ -4482,16 +4482,16 @@ }, { "name": "laminas/laminas-view", - "version": "2.27.0", + "version": "2.36.0", "source": { "type": "git", "url": "https://github.com/laminas/laminas-view.git", - "reference": "b7e66e148ccd55c815b9626ee0cfd358dbb28be4" + "reference": "ddc9207725cb50508ea48fcf1210dc8480264196" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-view/zipball/b7e66e148ccd55c815b9626ee0cfd358dbb28be4", - "reference": "b7e66e148ccd55c815b9626ee0cfd358dbb28be4", + "url": "https://api.github.com/repos/laminas/laminas-view/zipball/ddc9207725cb50508ea48fcf1210dc8480264196", + "reference": "ddc9207725cb50508ea48fcf1210dc8480264196", "shasum": "" }, "require": { @@ -4501,9 +4501,9 @@ "laminas/laminas-escaper": "^2.5", "laminas/laminas-eventmanager": "^3.4", "laminas/laminas-json": "^3.3", - "laminas/laminas-servicemanager": "^3.14.0", + "laminas/laminas-servicemanager": "^3.21.0", "laminas/laminas-stdlib": "^3.10.1", - "php": "~8.0.0 || ~8.1.0 || ~8.2.0", + "php": "~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0", "psr/container": "^1 || ^2" }, "conflict": { @@ -4513,24 +4513,24 @@ "zendframework/zend-view": "*" }, "require-dev": { - "laminas/laminas-authentication": "^2.13", + "laminas/laminas-authentication": "^2.18", "laminas/laminas-coding-standard": "~2.5.0", - "laminas/laminas-feed": "^2.20", - "laminas/laminas-filter": "^2.31", - "laminas/laminas-http": "^2.18", - "laminas/laminas-i18n": "^2.21", - "laminas/laminas-modulemanager": "^2.14", - "laminas/laminas-mvc": "^3.6", - "laminas/laminas-mvc-i18n": "^1.7", - "laminas/laminas-mvc-plugin-flashmessenger": "^1.9", - "laminas/laminas-navigation": "^2.18.1", - "laminas/laminas-paginator": "^2.17", - "laminas/laminas-permissions-acl": "^2.13", - "laminas/laminas-router": "^3.11.1", - "laminas/laminas-uri": "^2.10", - "phpunit/phpunit": "^9.5.28", - "psalm/plugin-phpunit": "^0.18.4", - "vimeo/psalm": "^5.4" + "laminas/laminas-feed": "^2.23", + "laminas/laminas-filter": "^2.39", + "laminas/laminas-http": "^2.20", + "laminas/laminas-i18n": "^2.29.0", + "laminas/laminas-modulemanager": "^2.17", + "laminas/laminas-mvc": "^3.8.0", + "laminas/laminas-mvc-i18n": "^1.9", + "laminas/laminas-mvc-plugin-flashmessenger": "^1.10.1", + "laminas/laminas-navigation": "^2.20.0", + "laminas/laminas-paginator": "^2.19.0", + "laminas/laminas-permissions-acl": "^2.16", + "laminas/laminas-router": "^3.14.0", + "laminas/laminas-uri": "^2.12", + "phpunit/phpunit": "^10.5.38", + "psalm/plugin-phpunit": "^0.19.0", + "vimeo/psalm": "^5.26.1" }, "suggest": { "laminas/laminas-authentication": "Laminas\\Authentication component", @@ -4578,7 +4578,7 @@ "type": "community_bridge" } ], - "time": "2023-02-09T16:07:15+00:00" + "time": "2024-11-21T17:42:20+00:00" }, { "name": "lcobucci/clock", @@ -14409,7 +14409,7 @@ "platform": { "php": ">=8.1" }, - "platform-dev": {}, + "platform-dev": [], "platform-overrides": { "php": "8.1" }, diff --git a/config/vufind/config.ini b/config/vufind/config.ini index 6b0e1560bfa..72043cb68cb 100644 --- a/config/vufind/config.ini +++ b/config/vufind/config.ini @@ -55,9 +55,9 @@ theme = sandal5 ; Uncomment the following line to use a different theme for Admin module. ;admin_theme = sandal -; Automatic asset minification and concatenation setting. When active, HeadScript -; and HeadLink will concatenate and minify all viable files to reduce requests and -; load times. This setting is off by default. +; Automatic asset minification and concatenation setting. When active, the asset +; manager helper will concatenate and minify all viable files to reduce requests +; and load times. This setting is off by default. ; ; This configuration takes the form of a semi-colon separated list of ; environment:configuration pairs where "environment" is a possible APPLICATION_ENV diff --git a/module/VuFind/src/VuFind/View/Helper/Root/GoogleAnalytics.php b/module/VuFind/src/VuFind/View/Helper/Root/GoogleAnalytics.php index 4a0b1dc959e..e7682dc8018 100644 --- a/module/VuFind/src/VuFind/View/Helper/Root/GoogleAnalytics.php +++ b/module/VuFind/src/VuFind/View/Helper/Root/GoogleAnalytics.php @@ -29,8 +29,6 @@ namespace VuFind\View\Helper\Root; -use Laminas\View\Helper\HeadScript; - use function is_array; /** @@ -107,11 +105,11 @@ public function __invoke($customUrl = false) if (!$this->key) { return ''; } - $inlineScript = $this->getView()->plugin('inlinescript'); + $assetManager = $this->getView()->plugin('assetManager'); $url = 'https://www.googletagmanager.com/gtag/js?id=' . urlencode($this->key); $code = $this->getRawJavascript($customUrl); return - $inlineScript(HeadScript::FILE, $url, 'SET', ['async' => true]) . "\n" - . $inlineScript(HeadScript::SCRIPT, $code, 'SET'); + $assetManager->outputInlineScriptFile($url, attrs: ['async' => true]) . "\n" + . $assetManager->outputInlineScript($code); } } diff --git a/module/VuFind/src/VuFind/View/Helper/Root/GoogleTagManager.php b/module/VuFind/src/VuFind/View/Helper/Root/GoogleTagManager.php index 26cc29dfa22..2c9582b3cd0 100644 --- a/module/VuFind/src/VuFind/View/Helper/Root/GoogleTagManager.php +++ b/module/VuFind/src/VuFind/View/Helper/Root/GoogleTagManager.php @@ -68,7 +68,6 @@ public function getHeadCode() return ''; } - // phpcs:disable -- line length should be kept for this vendor snippet $js = <<gtmContainerId}'); END; - // phpcs:enable - $inlineScript = $this->getView()->plugin('inlinescript'); - $js = $inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $js, 'SET'); - return $js; + return $this->getView()->plugin('assetManager')->outputInlineScript($js); } /** @@ -94,14 +90,12 @@ public function getBodyCode() return ''; } - // phpcs:disable -- line length should be kept for this vendor snippet $js = << END; - // phpcs:enable return $js; } } diff --git a/module/VuFind/src/VuFind/View/Helper/Root/Matomo.php b/module/VuFind/src/VuFind/View/Helper/Root/Matomo.php index ac729669195..f8467e0d7e3 100644 --- a/module/VuFind/src/VuFind/View/Helper/Root/Matomo.php +++ b/module/VuFind/src/VuFind/View/Helper/Root/Matomo.php @@ -177,9 +177,7 @@ public function __invoke(array $params = []): string } else { $code = $this->trackPageView(); } - - $inlineScript = $this->getView()->plugin('inlinescript'); - return $inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $code, 'SET'); + return $this->getView()->plugin('assetManager')->outputInlineScript($code); } /** diff --git a/module/VuFind/src/VuFind/View/Helper/Root/Piwik.php b/module/VuFind/src/VuFind/View/Helper/Root/Piwik.php index ac760b65131..eaa856053c1 100644 --- a/module/VuFind/src/VuFind/View/Helper/Root/Piwik.php +++ b/module/VuFind/src/VuFind/View/Helper/Root/Piwik.php @@ -174,8 +174,7 @@ public function __invoke($params = null) $code = $this->trackPageView(); } - $inlineScript = $this->getView()->plugin('inlinescript'); - return $inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $code, 'SET'); + return $this->getView()->plugin('assetManager')->outputInlineScript($code); } /** diff --git a/module/VuFind/src/VuFindTest/Feature/ViewTrait.php b/module/VuFind/src/VuFindTest/Feature/ViewTrait.php index 2358f13148c..1f52c71094e 100644 --- a/module/VuFind/src/VuFindTest/Feature/ViewTrait.php +++ b/module/VuFind/src/VuFindTest/Feature/ViewTrait.php @@ -29,7 +29,11 @@ namespace VuFindTest\Feature; +use Laminas\View\Renderer\PhpRenderer; use VuFind\View\Helper\Root\SearchMemory; +use VuFindTest\Container\MockContainer; +use VuFindTheme\View\Helper\AssetManager; +use VuFindTheme\View\Helper\AssetManagerFactory; /** * Trait for tests involving Laminas Views. @@ -42,13 +46,29 @@ */ trait ViewTrait { + /** + * Get a working AssetManager helper. + * + * @param PhpRenderer $renderer View for helper + * + * @return AssetManager + */ + protected function getAssetManager(PhpRenderer $renderer): AssetManager + { + $container = new MockContainer($this); + $factory = new AssetManagerFactory(); + $helper = $factory($container, AssetManager::class); + $helper->setView($renderer); + return $helper; + } + /** * Get a working renderer. * * @param array $plugins Custom VuFind plug-ins to register * @param string $theme Theme directory to load from * - * @return \Laminas\View\Renderer\PhpRenderer + * @return PhpRenderer */ protected function getPhpRenderer($plugins = [], $theme = 'bootstrap5') { @@ -63,13 +83,14 @@ protected function getPhpRenderer($plugins = [], $theme = 'bootstrap5') $this->getPathForTheme($theme), ] ); - $renderer = new \Laminas\View\Renderer\PhpRenderer(); + $renderer = new PhpRenderer(); $renderer->setResolver($resolver); - if (!empty($plugins)) { - $pluginManager = $renderer->getHelperPluginManager(); - foreach ($plugins as $key => $value) { - $pluginManager->setService($key, $value); - } + $pluginManager = $renderer->getHelperPluginManager(); + if (!isset($plugins['assetManager'])) { + $plugins['assetManager'] = $this->getAssetManager($renderer); + } + foreach ($plugins as $key => $value) { + $pluginManager->setService($key, $value); } return $renderer; } diff --git a/module/VuFindTheme/Module.php b/module/VuFindTheme/Module.php index 5f9edd01279..44220fccc65 100644 --- a/module/VuFindTheme/Module.php +++ b/module/VuFindTheme/Module.php @@ -87,8 +87,8 @@ public function getServiceConfig() ParentInjectTemplateListener::class => InjectTemplateListener::class, ], 'factories' => [ - InjectTemplateListener::class => - InjectTemplateListenerFactory::class, + AssetPipeline::class => AssetPipelineFactory::class, + InjectTemplateListener::class => InjectTemplateListenerFactory::class, MixinGenerator::class => ThemeInfoInjectorFactory::class, Mobile::class => InvokableFactory::class, ResourceContainer::class => InvokableFactory::class, @@ -108,34 +108,18 @@ public function getViewHelperConfig() { return [ 'factories' => [ - View\Helper\FootScript::class => - View\Helper\PipelineInjectorFactory::class, + View\Helper\AssetManager::class => View\Helper\AssetManagerFactory::class, View\Helper\ImageLink::class => View\Helper\ImageLinkFactory::class, - View\Helper\HeadLink::class => - View\Helper\PipelineInjectorFactory::class, - View\Helper\HeadScript::class => - View\Helper\PipelineInjectorFactory::class, - View\Helper\ParentTemplate::class => - View\Helper\ParentTemplateFactory::class, - View\Helper\InlineScript::class => - View\Helper\PipelineInjectorFactory::class, - View\Helper\Slot::class => - View\Helper\PipelineInjectorFactory::class, - View\Helper\TemplatePath::class => - View\Helper\TemplatePathFactory::class, - View\Helper\SetupThemeResources::class => - View\Helper\SetupThemeResourcesFactory::class, + View\Helper\ParentTemplate::class => View\Helper\ParentTemplateFactory::class, + View\Helper\Slot::class => InvokableFactory::class, + View\Helper\TemplatePath::class => View\Helper\TemplatePathFactory::class, + View\Helper\SetupThemeResources::class => View\Helper\SetupThemeResourcesFactory::class, ], 'aliases' => [ - 'footScript' => View\Helper\FootScript::class, + 'assetManager' => View\Helper\AssetManager::class, // Legacy alias for compatibility with pre-8.0 templates: 'headThemeResources' => View\Helper\SetupThemeResources::class, 'imageLink' => View\Helper\ImageLink::class, - \Laminas\View\Helper\HeadLink::class => View\Helper\HeadLink::class, - \Laminas\View\Helper\HeadScript::class => - View\Helper\HeadScript::class, - \Laminas\View\Helper\InlineScript::class => - View\Helper\InlineScript::class, 'parentTemplate' => View\Helper\ParentTemplate::class, 'slot' => View\Helper\Slot::class, 'templatePath' => View\Helper\TemplatePath::class, diff --git a/module/VuFindTheme/src/VuFindTheme/AssetPipeline.php b/module/VuFindTheme/src/VuFindTheme/AssetPipeline.php new file mode 100644 index 00000000000..f57b6cd07cf --- /dev/null +++ b/module/VuFindTheme/src/VuFindTheme/AssetPipeline.php @@ -0,0 +1,471 @@ + + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org Main Site + */ + +namespace VuFindTheme; + +use Exception; +use Laminas\Log\LoggerAwareInterface; +use Laminas\View\Helper\Url; +use MatthiasMullie\Minify\Minify; +use VuFind\Log\LoggerAwareTrait; +use VuFindTheme\View\Helper\RelativePathTrait; + +use function count; +use function defined; +use function in_array; +use function is_resource; + +/** + * Class to handle asset pipeline functionality. + * + * @category VuFind + * @package Theme + * @author Demian Katz + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org Main Site + */ +class AssetPipeline implements LoggerAwareInterface +{ + use LoggerAwareTrait; + use RelativePathTrait; + + /** + * Map of asset types to minifier classes. + * + * @var array + */ + protected array $minifiers = [ + 'css' => \VuFindTheme\Minify\CSS::class, + 'js' => \MatthiasMullie\Minify\JS::class, + ]; + + /** + * Constructor + * + * @param ThemeInfo $themeInfo Theme information service + * @param Url $urlHelper URL view helper + * @param string|bool $pipelineConfig Config for current application environment + * @param ?int $maxImportSize Maximum imported (inlined) file size + */ + public function __construct( + protected ThemeInfo $themeInfo, + protected Url $urlHelper, + protected string|bool $pipelineConfig, + protected ?int $maxImportSize = null + ) { + } + + /** + * Check if the pipeline is functional. + * + * @return bool + */ + protected function isPipelineAvailable(): bool + { + try { + $cacheDir = $this->getResourceCacheDir(); + } catch (\Exception $e) { + $this->logError($e->getMessage()); + return false; + } + if ($cacheDir && !is_writable($cacheDir)) { + $this->logError("Cannot write to $cacheDir; disabling asset pipeline."); + return false; + } + return true; + } + + /** + * Check if config is enabled for the specified file type + * + * @param string $fileType File type to check for pipeline config + * + * @return bool + */ + protected function isPipelineEnabledForType(string $fileType): bool + { + $config = $this->pipelineConfig; + if ($config === false || $config == 'off' || $config == 'false' || $config === '0') { + return false; + } + if ($config == '*' || $config == 'on' || $config == 'true' || $config === true || $config === '1') { + return true; + } + $settings = array_map('trim', explode(',', $config)); + return in_array($fileType, $settings); + } + + /** + * Get the path to the directory where we can cache files generated by + * this trait. The directory will be created if it does not already exist. + * + * @return string + */ + protected function getResourceCacheDir(): string + { + if (!defined('LOCAL_CACHE_DIR')) { + throw new \Exception( + 'Asset pipeline feature depends on the LOCAL_CACHE_DIR constant.' + ); + } + // TODO: it might be better to use \VuFind\Cache\Manager here. + $cacheDir = LOCAL_CACHE_DIR . '/public/'; + if (!is_dir($cacheDir) && !file_exists($cacheDir)) { + if (!mkdir($cacheDir)) { + throw new \Exception("Unexpected problem creating cache directory: $cacheDir"); + } + } + return $cacheDir; + } + + /** + * Determine whether the asset is exempt from concatenation. + * + * @param array $item Asset + * @param string $type Type of asset (css or js) + * + * @return bool + * @throws Exception + */ + protected function isExcludedFromConcat(array $item, string $type): bool + { + if ($type === 'css') { + return !$this->isRelativePath($item['href']); + } elseif ($type === 'js') { + return empty($item['src']) + || !empty($item['attrs']['conditional']) + || !$this->isRelativePath($item['src']); + } + throw new Exception("Unknown type: $type"); + } + + /** + * Extract the file path from an asset. + * + * @param array $item Asset + * @param string $type Type of asset (css or js) + * + * @return string + * @throws Exception + */ + protected function getResourceFilePath(array $item, string $type): string + { + $key = $this->getFileKeyByType($type); + if (!isset($item[$key])) { + throw new Exception("Unexpected missing $key key in $type item."); + } + return $item[$key]; + } + + /** + * Get the group identification key for a specific asset. + * + * @param array $item Asset + * @param string $type Type of asset (css or js) + * + * @return string + * @throws Exception + */ + protected function getGroupType(array $item, string $type): string + { + if ($type === 'css') { + $groupType = $item['media'] ?? 'all'; + if (isset($item['conditionalStylesheet'])) { + $type .= '_' . $item['conditionalStylesheet']; + } + return $groupType; + } + return 'default'; + } + + /** + * Sort assets into groups that can be collapsed using a minifier. + * + * @param array $assets Assets to group + * @param string $type Type of assets (css or js) + * + * @return array + * @throws Exception + */ + protected function groupAssets(array $assets, string $type): array + { + $groups = []; + $groupTypeIndex = []; + + foreach ($assets as $item) { + if ($this->isExcludedFromConcat($item, $type)) { + $groups[] = [ + 'other' => true, + 'item' => $item, + ]; + continue; + } + + $path = $type . '/' . $this->getResourceFilePath($item, $type); + $details = $this->themeInfo->findContainingTheme( + $path, + ThemeInfo::RETURN_ALL_DETAILS + ); + // Deal with special case: $path was not found in any theme. + if (null === $details) { + $errorMsg = "Could not find file '$path' in theme files"; + $this->logError($errorMsg); + $groups[] = [ + 'other' => true, + 'item' => $item, + ]; + continue; + } + + $groupType = $this->getGroupType($item, $type); + $index = $groupTypeIndex[$groupType] ?? false; + if ($index === false) { + $groupTypeIndex[$groupType] = count($groups); + $groups[] = [ + 'items' => [$item], + 'key' => $details['path'] . filemtime($details['path']), + ]; + } elseif (!in_array($item, $groups[$index]['items'])) { + $groups[$index]['items'][] = $item; + $groups[$index]['key'] .= $details['path'] . filemtime($details['path']); + } + } + + return $groups; + } + + /** + * Check if a file is minifiable i.e. does not have a pattern that denotes it's + * already minified + * + * @param string $filename File name + * + * @return bool + */ + protected function isMinifiable(string $filename): bool + { + $basename = basename($filename); + return preg_match('/\.min\.(js|css)/', $basename) === 0; + } + + /** + * Get the minifier object for the specified file type. + * + * @param string $type Type of assets (css or js) + * + * @return Minify + * @throws Exception + */ + protected function getMinifier(string $type): Minify + { + $minifierClass = $this->minifiers[$type] ?? null; + if (!$minifierClass) { + throw new Exception("Unsupported type: $type"); + } + $minifier = new $minifierClass(); + if ($type === 'css' && null !== $this->maxImportSize) { + $minifier->setMaxImportSize($this->maxImportSize); + } + return $minifier; + } + + /** + * Get minified data for a file + * + * @param array $details File details + * @param string $concatPath Target path for the resulting file (used in minifier + * for path mapping) + * @param string $type Type of assets (css or js) + * + * @throws \Exception + * @return string + */ + protected function getMinifiedData(array $details, string $concatPath, string $type): string + { + if ($this->isMinifiable($details['path'])) { + $minifier = $this->getMinifier($type); + $minifier->add($details['path']); + $data = $minifier->execute($concatPath); + } else { + $data = file_get_contents($details['path']); + if (false === $data) { + throw new \Exception( + "Could not read file {$details['path']}" + ); + } + } + // Play it safe by terminating Javascript code with a semicolon + if ($type === 'js' && !str_ends_with(trim($data), ';')) { + $data .= ';'; + } + return $data; + } + + /** + * Create a concatenated file from the given group of files + * + * @param string $concatPath Resulting file path + * @param array $group Object containing 'key' and stdobj file 'items' + * @param string $type Type of assets (css or js) + * + * @throws \Exception + * @return void + */ + protected function createConcatenatedFile(string $concatPath, array $group, string $type): void + { + $data = []; + foreach ($group['items'] as $item) { + $details = $this->themeInfo->findContainingTheme( + $type . '/' . $this->getResourceFilePath($item, $type), + ThemeInfo::RETURN_ALL_DETAILS + ); + $details['path'] = realpath($details['path']); + $data[] = $this->getMinifiedData($details, $concatPath, $type); + } + // Separate each file's data with a new line so that e.g. a file + // ending in a comment doesn't cause the next one to also get commented out. + file_put_contents($concatPath, implode("\n", $data)); + } + + /** + * Using the concatKey, return the path of the concatenated file. + * Generate if it does not yet exist. + * + * @param array $group Grouped assets + * @param string $type Type of assets (css or js) + * + * @return string + */ + protected function getConcatenatedFilePath(array $group, string $type): string + { + // Don't recompress individual files + if (count($group['items']) === 1) { + $path = $this->getResourceFilePath($group['items'][0], $type); + $details = $this->themeInfo->findContainingTheme( + $type . '/' . $path, + ThemeInfo::RETURN_ALL_DETAILS + ); + return ($this->urlHelper)('home') . 'themes/' . $details['theme'] + . '/' . $type . '/' . $path; + } + // Locate/create concatenated asset file + $filename = md5($group['key']) . '.min.' . $type; + // Minifier uses realpath, so do that here too to make sure we're not + // pointing to a symlink. Otherwise the path converter won't find the correct + // shared directory part. + $concatPath = realpath($this->getResourceCacheDir()) . '/' . $filename; + if (!file_exists($concatPath)) { + $lockfile = "$concatPath.lock"; + $handle = fopen($lockfile, 'c+'); + if (!is_resource($handle)) { + throw new \Exception("Could not open lock file $lockfile"); + } + if (!flock($handle, LOCK_EX)) { + fclose($handle); + throw new \Exception("Could not lock file $lockfile"); + } + // Check again if file exists after acquiring the lock + if (!file_exists($concatPath)) { + try { + $this->createConcatenatedFile($concatPath, $group, $type); + } catch (\Exception $e) { + flock($handle, LOCK_UN); + fclose($handle); + throw $e; + } + } + flock($handle, LOCK_UN); + fclose($handle); + } + + return ($this->urlHelper)('home') . 'cache/' . $filename; + } + + /** + * Get the key name from the asset array where a filename/path can be set. + * + * @param string $type Type of assets (css or js) + * + * @return string + * @throws Exception + */ + protected function getFileKeyByType(string $type): string + { + $keys = ['css' => 'href', 'js' => 'src']; + if (isset($keys[$type])) { + return $keys[$type]; + } + throw new Exception("Unexpected type: $type"); + } + + /** + * Turn the output of groupAssets() into an array suitable for input to the view helpers. + * + * @param array $groups Grouped assets returned by groupAssets() + * @param string $type Type of assets (css or js) + * + * @return array + * @throws Exception + */ + protected function processGroupedAssets(array $groups, string $type): array + { + $assets = []; + + foreach ($groups as $group) { + if (isset($group['other'])) { + $assets[] = $group['item']; + } else { + $item = $group['items'][0]; + $item[$this->getFileKeyByType($type)] = $this->getConcatenatedFilePath($group, $type); + $assets[] = $item; + } + } + + return $assets; + } + + /** + * Process an array of assets through the pipeline. + * + * @param array $assets Assets to process + * @param string $type Type of assets (css or js) + * + * @return array + * @throws Exception + */ + public function process(array $assets, string $type): array + { + if (!$this->isPipelineEnabledForType($type) || !$this->isPipelineAvailable()) { + return $assets; + } + + $groupedAssets = $this->groupAssets($assets, $type); + return $this->processGroupedAssets($groupedAssets, $type); + } +} diff --git a/module/VuFindTheme/src/VuFindTheme/View/Helper/PipelineInjectorFactory.php b/module/VuFindTheme/src/VuFindTheme/AssetPipelineFactory.php similarity index 84% rename from module/VuFindTheme/src/VuFindTheme/View/Helper/PipelineInjectorFactory.php rename to module/VuFindTheme/src/VuFindTheme/AssetPipelineFactory.php index ebcc385a40a..ca53b35e6cb 100644 --- a/module/VuFindTheme/src/VuFindTheme/View/Helper/PipelineInjectorFactory.php +++ b/module/VuFindTheme/src/VuFindTheme/AssetPipelineFactory.php @@ -1,11 +1,11 @@ get(\VuFind\Config\PluginManager::class); - $nonceGenerator = $container->get(\VuFind\Security\NonceGenerator::class); - $nonce = $nonceGenerator->getNonce(); - $config = $configManager->get('config'); + $config = $configManager->get('config')?->toArray() ?? []; return new $requestedName( $container->get(\VuFindTheme\ThemeInfo::class), + $container->get('ViewHelperManager')->get('url'), $this->getPipelineConfig($config), - $nonce, $config['Site']['asset_pipeline_max_css_import_size'] ?? null ); } diff --git a/module/VuFindTheme/src/VuFindTheme/View/Helper/AssetManager.php b/module/VuFindTheme/src/VuFindTheme/View/Helper/AssetManager.php new file mode 100644 index 00000000000..7cce177ab50 --- /dev/null +++ b/module/VuFindTheme/src/VuFindTheme/View/Helper/AssetManager.php @@ -0,0 +1,402 @@ + + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development Wiki + */ + +namespace VuFindTheme\View\Helper; + +use VuFindTheme\AssetPipeline; +use VuFindTheme\ThemeInfo; + +/** + * Asset manager view helper (for pre-processing, combining when appropriate, etc.) + * + * @category VuFind + * @package View_Helpers + * @author Demian Katz + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development Wiki + */ +class AssetManager extends \Laminas\View\Helper\AbstractHelper +{ + use RelativePathTrait; + + /** + * Array of accumulated scripts, indexed by position (header/footer). + * + * @var array + */ + protected $scripts = ['header' => [], 'footer' => []]; + + /** + * Array of accumulated styles. + * + * @var array + */ + protected $styles = []; + + /** + * Array of accumulated stylesheets. + * + * @var array + */ + protected $stylesheets = []; + + /** + * Constructor + * + * @param ThemeInfo $themeInfo Theme information service + * @param AssetPipeline $pipeline Asset pipeline helper + * @param string $cspNonce Nonce from nonce generator (for content security policy) + */ + public function __construct( + protected ThemeInfo $themeInfo, + protected AssetPipeline $pipeline, + protected string $cspNonce = '' + ) { + } + + /** + * Add raw CSS to the pipeline. + * + * @param string $css Raw CSS. + * @param array $attributes Extra attributes for style tag + * + * @return void + */ + public function appendStyle(string $css, array $attributes = []): void + { + $this->styles[] = compact('css', 'attributes'); + } + + /** + * Add an entry to the list of stylesheets. + * + * @param string $href Stylesheet href + * @param string $media Media + * @param string $conditionalStylesheet Any conditions + * @param array $extras Array of extra attributes + * + * @return void + */ + public function appendStylesheet( + string $href, + string $media = 'screen', + string $conditionalStylesheet = '', + array $extras = [] + ): void { + $this->stylesheets[] = compact('href', 'media', 'conditionalStylesheet', 'extras'); + } + + /** + * Forcibly prepend a stylesheet, removing it from any existing position + * + * @param string $href Stylesheet href + * @param string $media Media + * @param string $conditionalStylesheet Any conditions + * @param array $extras Array of extra attributes + * + * @return void + */ + public function forcePrependStylesheet( + string $href, + string $media = 'screen', + string $conditionalStylesheet = '', + array $extras = [] + ): void { + $newSheets = [compact('href', 'media', 'conditionalStylesheet', 'extras')]; + foreach ($this->stylesheets as $sheet) { + if ($sheet['href'] !== $newSheets[0]['href']) { + $newSheets[] = $sheet; + } + } + $this->stylesheets = $newSheets; + } + + /** + * Clear the list of stylesheets, re-establishing it with the provided one. + * + * @param string $href Stylesheet href + * @param string $media Media + * @param string $conditionalStylesheet Any conditions + * @param array $extras Array of extra attributes + * + * @return void + */ + public function setStylesheet( + string $href, + string $media = 'screen', + string $conditionalStylesheet = '', + array $extras = [] + ): void { + $this->stylesheets = [compact('href', 'media', 'conditionalStylesheet', 'extras')]; + } + + /** + * Append raw script code. + * + * @param string $script Script code + * @param string $type Script type + * @param array $attrs Additional attributes for the script tag + * @param bool $allowArbitraryAttrs Should we allow arbitrary attributes in $attrs? + * @param string $position Position to output script (header or footer) + * + * @return void + */ + public function appendScript( + string $script, + string $type = 'text/javascript', + array $attrs = [], + bool $allowArbitraryAttrs = false, + string $position = 'header' + ) { + $this->scripts[$position][] = compact('script', 'type', 'attrs', 'allowArbitraryAttrs'); + } + + /** + * Add an entry to the list of script files. + * + * @param string $src Script src + * @param string $type Script type + * @param array $attrs Array of script attributes + * @param string $position Position to output script (header or footer) + * + * @return void + */ + public function appendScriptFile( + string $src, + string $type = 'text/javascript', + array $attrs = [], + string $position = 'header' + ): void { + $this->scripts[$position][] = compact('src', 'type', 'attrs'); + } + + /** + * Forcibly prepend a file, removing it from any existing position. + * + * @param string $src Script src + * @param string $type Script type + * @param array $attrs Array of script attributes + * @param string $position Position to output script (header or footer) + * + * @return void + */ + public function forcePrependScriptFile( + string $src, + string $type = 'text/javascript', + array $attrs = [], + string $position = 'header' + ): void { + $newScripts = [compact('src', 'type', 'attrs')]; + foreach ($this->scripts[$position] as $script) { + if ($script['src'] ?? null !== $newScripts[0]['src']) { + $newScripts[] = $script; + } + } + $this->scripts[$position] = $newScripts; + } + + /** + * Prepend raw script code. + * + * @param string $script Script code + * @param string $type Script type + * @param array $attrs Additional attributes for the script tag + * @param bool $allowArbitraryAttrs Should we allow arbitrary attributes in $attrs? + * @param string $position Position to output script (header or footer) + * + * @return void + */ + public function prependScript( + string $script, + string $type = 'text/javascript', + array $attrs = [], + bool $allowArbitraryAttrs = false, + string $position = 'header' + ) { + array_unshift($this->scripts[$position], compact('script', 'type', 'attrs', 'allowArbitraryAttrs')); + } + + /** + * Given a relative JS or CSS path, apply appropriate theme prefixing if possible; return null if + * the resource could not be found in a theme. + * + * @param string $relPath Relative path to find in theme + * + * @return ?string + */ + protected function applyThemeToRelativePath(string $relPath): ?string + { + $details = $this->themeInfo->findContainingTheme($relPath, ThemeInfo::RETURN_ALL_DETAILS); + if (!empty($details)) { + $urlHelper = $this->getView()->plugin('url'); + $url = $urlHelper('home') . "themes/{$details['theme']}/" . $relPath; + $url .= strstr($url, '?') ? '&_=' : '?_='; + $url .= filemtime($details['path']); + return $url; + } + // Cannot find in theme? Return null. + return null; + } + + /** + * Return the HTML to output script assets in the requested position. + * + * @param mixed $position Position of assets (header or footer) + * + * @return string + */ + protected function outputScriptAssets($position): string + { + $output = []; + $scriptHelper = $this->getView()->plugin('inlineScript'); + $processedScripts = $this->pipeline->process($this->scripts[$position], 'js'); + foreach ($processedScripts as $i => $script) { + if ($script['allowArbitraryAttrs'] ?? false) { + $scriptHelper->setAllowArbitraryAttributes(true); + } + // Every $script will have either a script attribute (inline JS) or a src attribute (file): + if (isset($script['script'])) { + $output[] = $this->outputInlineScript($script['script'], $script['type'], $script['attrs']); + } else { + if ($this->isRelativePath($script['src'])) { + if ($themePath = $this->applyThemeToRelativePath('js/' . $script['src'])) { + $script['src'] = $themePath; + } + } + $output[] = $this->outputInlineScriptFile($script['src'], $script['type'], $script['attrs']); + } + } + return implode("\n", $output); + } + + /** + * Return the HTML to output style assets. + * + * @return string + */ + protected function outputStyleAssets(): string + { + $headLink = $this->getView()->plugin('headLink'); + $processedStylesheets = $this->pipeline->process($this->stylesheets, 'css'); + foreach ($processedStylesheets as $sheet) { + // Account for the theme system (when appropriate): + if ($this->isRelativePath($sheet['href'])) { + if ($themePath = $this->applyThemeToRelativePath('css/' . $sheet['href'])) { + $sheet['href'] = $themePath; + } + } + + $headLink->appendStylesheet( + $sheet['href'], + $sheet['media'], + $sheet['conditionalStylesheet'], + $sheet['extras'] + ); + } + + $headStyle = $this->getView()->plugin('headStyle'); + foreach ($this->styles as $style) { + $headStyle->appendStyle($style['css'], $style['attributes']); + } + + return ($headLink)() . "\n" . ($headStyle)(); + } + + /** + * Output the collected assets for the header. + * + * @return string + */ + public function outputHeaderAssets(): string + { + return $this->outputStyleAssets() . "\n" . $this->outputScriptAssets('header'); + } + + /** + * Output an inline script. + * + * @param string $script Script code + * @param string $type Script type + * @param array $attrs Additional attributes for the script tag + * @param bool $allowArbitraryAttrs Should we allow arbitrary attributes in $attrs? + * + * @return string + */ + public function outputInlineScript( + string $script, + string $type = 'text/javascript', + array $attrs = [], + bool $allowArbitraryAttrs = false, + ): string { + if (!empty($this->cspNonce)) { + $attrs['nonce'] = $this->cspNonce; + } + $inlineScript = $this->getView()->plugin('inlineScript'); + if ($allowArbitraryAttrs) { + $inlineScript->setAllowArbitraryAttributes(true); + } + $inlineScript->setScript($script, $type, $attrs); + return ($inlineScript)(); + } + + /** + * Output an inline script file. + * + * @param string $src Script src + * @param string $type Script type + * @param array $attrs Array of script attributes + * + * @return string + */ + public function outputInlineScriptFile( + string $src, + string $type = 'text/javascript', + array $attrs = [], + ): string { + if (!empty($this->cspNonce)) { + $attrs['nonce'] = $this->cspNonce; + } + $inlineScript = $this->getView()->plugin('inlineScript'); + if ($this->isRelativePath($src)) { + $src = $this->applyThemeToRelativePath('js/' . $src) ?? $src; + } + $inlineScript->setFile($src, $type, $attrs); + return ($inlineScript)(); + } + + /** + * Output the collected assets for the footer. + * + * @return string + */ + public function outputFooterAssets(): string + { + return $this->outputScriptAssets('footer'); + } +} diff --git a/module/VuFindTheme/src/VuFindTheme/View/Helper/AssetManagerFactory.php b/module/VuFindTheme/src/VuFindTheme/View/Helper/AssetManagerFactory.php new file mode 100644 index 00000000000..8d6eef55497 --- /dev/null +++ b/module/VuFindTheme/src/VuFindTheme/View/Helper/AssetManagerFactory.php @@ -0,0 +1,110 @@ + + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org Main Site + */ + +namespace VuFindTheme\View\Helper; + +use Laminas\ServiceManager\Exception\ServiceNotCreatedException; +use Laminas\ServiceManager\Exception\ServiceNotFoundException; +use Laminas\ServiceManager\Factory\FactoryInterface; +use Psr\Container\ContainerExceptionInterface as ContainerException; +use Psr\Container\ContainerInterface; + +use function count; + +/** + * Factory for AssetManager view helper. + * + * @category VuFind + * @package Theme + * @author Demian Katz + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org Main Site + */ +class AssetManagerFactory implements FactoryInterface +{ + /** + * Split config and return prefixed setting with current environment. + * + * @param array $config Configuration settings + * + * @return string|bool + */ + protected function getPipelineConfig(array $config) + { + $default = false; + if (isset($config['Site']['asset_pipeline'])) { + $settings = array_map( + 'trim', + explode(';', $config['Site']['asset_pipeline']) + ); + foreach ($settings as $setting) { + $parts = array_map('trim', explode(':', $setting)); + if (APPLICATION_ENV === $parts[0]) { + return $parts[1]; + } elseif (count($parts) == 1) { + $default = $parts[0]; + } elseif ($parts[0] === '*') { + $default = $parts[1]; + } + } + } + return $default; + } + + /** + * Create an object + * + * @param ContainerInterface $container Service manager + * @param string $requestedName Service being created + * @param null|array $options Extra options (optional) + * + * @return object + * + * @throws ServiceNotFoundException if unable to resolve the service. + * @throws ServiceNotCreatedException if an exception is raised when + * creating a service. + * @throws ContainerException&\Throwable if any other error occurs + */ + public function __invoke( + ContainerInterface $container, + $requestedName, + ?array $options = null + ) { + if (!empty($options)) { + throw new \Exception('Unexpected options sent to factory.'); + } + $nonceGenerator = $container->get(\VuFind\Security\NonceGenerator::class); + $nonce = $nonceGenerator->getNonce(); + return new $requestedName( + $container->get(\VuFindTheme\ThemeInfo::class), + $container->get(\VuFindTheme\AssetPipeline::class), + $nonce + ); + } +} diff --git a/module/VuFindTheme/src/VuFindTheme/View/Helper/ConcatTrait.php b/module/VuFindTheme/src/VuFindTheme/View/Helper/ConcatTrait.php deleted file mode 100644 index 6e674a4e4bb..00000000000 --- a/module/VuFindTheme/src/VuFindTheme/View/Helper/ConcatTrait.php +++ /dev/null @@ -1,491 +0,0 @@ - - * @author Ere Maijala - * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License - * @link https://vufind.org/wiki/development Wiki - */ - -namespace VuFindTheme\View\Helper; - -use VuFindTheme\ThemeInfo; - -use function count; -use function defined; -use function in_array; -use function is_resource; - -/** - * Trait to add asset pipeline functionality (concatenation / minification) to - * a HeadLink/HeadScript-style view helper. - * - * @category VuFind - * @package View_Helpers - * @author Demian Katz - * @author Ere Maijala - * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License - * @link https://vufind.org/wiki/development:testing:unit_tests Wiki - */ -trait ConcatTrait -{ - /** - * Returns true if file should not be included in the compressed concat file - * - * @param stdClass $item Element object - * - * @return bool - */ - abstract protected function isExcludedFromConcat($item); - - /** - * Get the folder name and file extension - * - * @return string - */ - abstract protected function getFileType(); - - /** - * Get the file path from the element object - * - * @param stdClass $item Element object - * - * @return string - */ - abstract protected function getResourceFilePath($item); - - /** - * Set the file path of the element object - * - * @param stdClass $item Element object - * @param string $path New path string - * - * @return stdClass - */ - abstract protected function setResourceFilePath($item, $path); - - /** - * Get the minifier that can handle these file types - * - * @return minifying object like \MatthiasMullie\Minify\JS - */ - abstract protected function getMinifier(); - - /** - * Add a content security policy nonce to the item - * - * @param stdClass $item Item - * - * @return void - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - protected function addNonce($item) - { - // Default implementation does nothing - } - - /** - * Set the file path of the link object - * - * @param stdClass $item Link element object - * - * @return string - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public function getType($item) - { - return 'default'; - } - - /** - * Should we use the asset pipeline to join files together and minify them? - * - * @var bool - */ - protected $usePipeline = false; - - /** - * Array of resource items by type, contains key as well - * - * @var array - */ - protected $groups = []; - - /** - * Future order of the concatenated file - * - * @var number - */ - protected $concatIndex = null; - - /** - * Check if config is enabled for this file type - * - * @param string|bool $config Config for current application environment - * - * @return bool - */ - protected function enabledInConfig($config) - { - if ($config === false || $config == 'off') { - return false; - } - if ( - $config == '*' || $config == 'on' - || $config == 'true' || $config === true - ) { - return true; - } - $settings = array_map('trim', explode(',', $config)); - return in_array($this->getFileType(), $settings); - } - - /** - * Initialize class properties related to concatenation of resources. - * All of the elements to be concatenated into groups and - * and those that need to remain on their own special group 'other'. - * - * @return bool True if there are items - */ - protected function filterItems() - { - $this->groups = []; - $groupTypes = []; - - $this->getContainer()->ksort(); - - foreach ($this as $item) { - if ($this->isExcludedFromConcat($item)) { - $this->groups[] = [ - 'other' => true, - 'item' => $item, - ]; - $groupTypes[] = 'other'; - continue; - } - - $path = $this->getFileType() . '/' . $this->getResourceFilePath($item); - $details = $this->themeInfo->findContainingTheme( - $path, - ThemeInfo::RETURN_ALL_DETAILS - ); - // Deal with special case: $path was not found in any theme. - if (null === $details) { - $errorMsg = "Could not find file '$path' in theme files"; - method_exists($this, 'logError') - ? $this->logError($errorMsg) : error_log($errorMsg); - $this->groups[] = [ - 'other' => true, - 'item' => $item, - ]; - $groupTypes[] = 'other'; - continue; - } - - $type = $this->getType($item); - $index = array_search($type, $groupTypes); - if ($index === false) { - $this->groups[] = [ - 'items' => [$item], - 'key' => $details['path'] . filemtime($details['path']), - ]; - $groupTypes[] = $type; - } else { - $this->groups[$index]['items'][] = $item; - $this->groups[$index]['key'] .= - $details['path'] . filemtime($details['path']); - } - } - - return count($groupTypes) > 0; - } - - /** - * Get the path to the directory where we can cache files generated by - * this trait. The directory will be created if it does not already exist. - * - * @return string - */ - protected function getResourceCacheDir() - { - if (!defined('LOCAL_CACHE_DIR')) { - throw new \Exception( - 'Asset pipeline feature depends on the LOCAL_CACHE_DIR constant.' - ); - } - // TODO: it might be better to use \VuFind\Cache\Manager here. - $cacheDir = LOCAL_CACHE_DIR . '/public/'; - if (!is_dir($cacheDir) && !file_exists($cacheDir)) { - if (!mkdir($cacheDir)) { - throw new \Exception("Unexpected problem creating cache directory: $cacheDir"); - } - } - return $cacheDir; - } - - /** - * Using the concatKey, return the path of the concatenated file. - * Generate if it does not yet exist. - * - * @param array $group Object containing 'key' and stdobj file 'items' - * - * @return string - */ - protected function getConcatenatedFilePath($group) - { - $urlHelper = $this->getView()->plugin('url'); - - // Don't recompress individual files - if (count($group['items']) === 1) { - $path = $this->getResourceFilePath($group['items'][0]); - $details = $this->themeInfo->findContainingTheme( - $this->getFileType() . '/' . $path, - ThemeInfo::RETURN_ALL_DETAILS - ); - return $urlHelper('home') . 'themes/' . $details['theme'] - . '/' . $this->getFileType() . '/' . $path; - } - // Locate/create concatenated asset file - $filename = md5($group['key']) . '.min.' . $this->getFileType(); - // Minifier uses realpath, so do that here too to make sure we're not - // pointing to a symlink. Otherwise the path converter won't find the correct - // shared directory part. - $concatPath = realpath($this->getResourceCacheDir()) . '/' . $filename; - if (!file_exists($concatPath)) { - $lockfile = "$concatPath.lock"; - $handle = fopen($lockfile, 'c+'); - if (!is_resource($handle)) { - throw new \Exception("Could not open lock file $lockfile"); - } - if (!flock($handle, LOCK_EX)) { - fclose($handle); - throw new \Exception("Could not lock file $lockfile"); - } - // Check again if file exists after acquiring the lock - if (!file_exists($concatPath)) { - try { - $this->createConcatenatedFile($concatPath, $group); - } catch (\Exception $e) { - flock($handle, LOCK_UN); - fclose($handle); - throw $e; - } - } - flock($handle, LOCK_UN); - fclose($handle); - } - - return $urlHelper('home') . 'cache/' . $filename; - } - - /** - * Create a concatenated file from the given group of files - * - * @param string $concatPath Resulting file path - * @param array $group Object containing 'key' and stdobj file 'items' - * - * @throws \Exception - * @return void - */ - protected function createConcatenatedFile($concatPath, $group) - { - $data = []; - foreach ($group['items'] as $item) { - $details = $this->themeInfo->findContainingTheme( - $this->getFileType() . '/' - . $this->getResourceFilePath($item), - ThemeInfo::RETURN_ALL_DETAILS - ); - $details['path'] = realpath($details['path']); - $data[] = $this->getMinifiedData($details, $concatPath); - } - // Separate each file's data with a new line so that e.g. a file - // ending in a comment doesn't cause the next one to also get commented out. - file_put_contents($concatPath, implode("\n", $data)); - } - - /** - * Get minified data for a file - * - * @param array $details File details - * @param string $concatPath Target path for the resulting file (used in minifier - * for path mapping) - * - * @throws \Exception - * @return string - */ - protected function getMinifiedData($details, $concatPath) - { - if ($this->isMinifiable($details['path'])) { - $minifier = $this->getMinifier(); - $minifier->add($details['path']); - $data = $minifier->execute($concatPath); - } else { - $data = file_get_contents($details['path']); - if (false === $data) { - throw new \Exception( - "Could not read file {$details['path']}" - ); - } - } - return $data; - } - - /** - * Process and return items in index order - * - * @param string|int $indent Amount of whitespace/string to use for indentation - * - * @return string - */ - protected function outputInOrder($indent) - { - // Some of this logic was copied from HeadScript; it does not all apply - // when incorporated into HeadLink, but it has no harmful side effects. - $indent = (null !== $indent) - ? $this->getWhitespace($indent) - : $this->getIndent(); - - if ($this->view) { - $useCdata = $this->view->plugin('doctype')->isXhtml(); - } else { - $useCdata = $this->useCdata ?? false; - } - - $escapeStart = ($useCdata) ? '//' : '//-->'; - - $output = []; - foreach ($this->groups as $group) { - if (isset($group['other'])) { - /** - * PHPStan doesn't like this because of incompatible itemToString - * signatures in HeadLink/HeadScript, but it is safe to use because - * the extra parameters will be ignored appropriately. - * - * @phpstan-ignore-next-line - */ - $output[] = $this->itemToString( - $group['item'], - $indent, - $escapeStart, - $escapeEnd - ); - } else { - // Note that we use parent::itemToString() below instead of - // $this->itemToString() to bypass VuFind logic that determines - // file paths within the theme (not appropriate for concatenated - // files, which are stored in a theme-independent cache). - $path = $this->getConcatenatedFilePath($group); - $item = $this->setResourceFilePath($group['items'][0], $path); - $this->addNonce($item); - /** - * PHPStan doesn't like this because of incompatible itemToString - * signatures in HeadLink/HeadScript, but it is safe to use because - * the extra parameters will be ignored appropriately. - * - * @phpstan-ignore-next-line - */ - $output[] = parent::itemToString( - $item, - $indent, - $escapeStart, - $escapeEnd - ); - } - } - - return $indent . implode( - $this->escape($this->getSeparator()) . $indent, - $output - ); - } - - /** - * Check if a file is minifiable i.e. does not have a pattern that denotes it's - * already minified - * - * @param string $filename File name - * - * @return bool - */ - protected function isMinifiable($filename) - { - $basename = basename($filename); - return preg_match('/\.min\.(js|css)/', $basename) === 0; - } - - /** - * Can we use the asset pipeline? - * - * @return bool - */ - protected function isPipelineActive() - { - if ($this->usePipeline) { - try { - $cacheDir = $this->getResourceCacheDir(); - } catch (\Exception $e) { - $this->usePipeline = $cacheDir = false; - error_log($e->getMessage()); - } - if ($cacheDir && !is_writable($cacheDir)) { - $this->usePipeline = false; - error_log("Cannot write to $cacheDir; disabling asset pipeline."); - } - } - return $this->usePipeline; - } - - /** - * Render link elements as string - * Customized to minify and concatenate - * - * @param string|int $indent Amount of whitespace or string to use for indentation - * - * @return string - */ - public function toString($indent = null) - { - // toString must not throw exception - try { - if ( - !$this->isPipelineActive() || !$this->filterItems() - || count($this) == 1 - ) { - return parent::toString($indent); - } - - return $this->outputInOrder($indent); - } catch (\Exception $e) { - error_log($e->getMessage()); - } - - return ''; - } -} diff --git a/module/VuFindTheme/src/VuFindTheme/View/Helper/FootScript.php b/module/VuFindTheme/src/VuFindTheme/View/Helper/FootScript.php deleted file mode 100644 index 1b1f9af9cb3..00000000000 --- a/module/VuFindTheme/src/VuFindTheme/View/Helper/FootScript.php +++ /dev/null @@ -1,46 +0,0 @@ -) - * - * PHP version 8 - * - * Copyright (C) Villanova University 2021. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2, - * as published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * @category VuFind - * @package View_Helpers - * @author Chris Hallberg - * @author Demian Katz - * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License - * @link https://vufind.org/wiki/development Wiki - */ - -namespace VuFindTheme\View\Helper; - -/** - * Footer script view helper (same as HeadScript but outputs to the bottom of ) - * - * @category VuFind - * @package View_Helpers - * @author Chris Hallberg - * @author Demian Katz - * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License - * @link https://vufind.org/wiki/development Wiki - */ -class FootScript extends HeadScript -{ - // pass -} diff --git a/module/VuFindTheme/src/VuFindTheme/View/Helper/HeadLink.php b/module/VuFindTheme/src/VuFindTheme/View/Helper/HeadLink.php deleted file mode 100644 index 4745d2552ec..00000000000 --- a/module/VuFindTheme/src/VuFindTheme/View/Helper/HeadLink.php +++ /dev/null @@ -1,232 +0,0 @@ - - * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License - * @link https://vufind.org/wiki/development Wiki - */ - -namespace VuFindTheme\View\Helper; - -use stdClass; -use VuFindTheme\ThemeInfo; - -/** - * Head link view helper (extended for VuFind's theme system) - * - * @category VuFind - * @package View_Helpers - * @author Demian Katz - * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License - * @link https://vufind.org/wiki/development Wiki - * - * @method getWhitespace(string|int $indent) - * @method getIndent() - * @method getSeparator() - */ -class HeadLink extends \Laminas\View\Helper\HeadLink implements \Laminas\Log\LoggerAwareInterface -{ - use ConcatTrait; - use RelativePathTrait; - use \VuFind\Log\LoggerAwareTrait; - - /** - * Theme information service - * - * @var ThemeInfo - */ - protected $themeInfo; - - /** - * CSP nonce - * - * @var string - */ - protected $cspNonce; - - /** - * Maximum import size (for inlining of e.g. images) in kilobytes - * - * @var int|null - */ - protected $maxImportSize; - - /** - * Constructor - * - * @param ThemeInfo $themeInfo Theme information service - * @param string|bool $plconfig Config for current application environment - * @param string $nonce Nonce from nonce generator - * @param int $maxImportSize Maximum imported (inlined) file size - */ - public function __construct( - ThemeInfo $themeInfo, - $plconfig = false, - $nonce = '', - $maxImportSize = null - ) { - parent::__construct(); - $this->themeInfo = $themeInfo; - $this->usePipeline = $this->enabledInConfig($plconfig); - $this->cspNonce = $nonce; - $this->maxImportSize = $maxImportSize; - $this->itemKeys[] = 'nonce'; - } - - /** - * Folder name and file extension for trait - * - * @return string - */ - protected function getFileType() - { - return 'css'; - } - - /** - * Create HTML link element from data item - * - * @param stdClass $item data item - * - * @return string - */ - public function itemToString(stdClass $item) - { - // Normalize href to account for themes (if appropriate), then call the parent class: - if (isset($item->href) && $this->isRelativePath($item->href)) { - $relPath = 'css/' . $item->href; - $details = $this->themeInfo - ->findContainingTheme($relPath, ThemeInfo::RETURN_ALL_DETAILS); - if (!empty($details)) { - $urlHelper = $this->getView()->plugin('url'); - $url = $urlHelper('home') . "themes/{$details['theme']}/" . $relPath; - $url .= strstr($url, '?') ? '&_=' : '?_='; - $url .= filemtime($details['path']); - $item->href = $url; - } - } - $this->addNonce($item); - return parent::itemToString($item); - } - - /** - * Forcibly prepend a stylesheet removing it from any existing position - * - * @param string $href Stylesheet href - * @param string $media Media - * @param string $conditionalStylesheet Any conditions - * @param array $extras Array of extra attributes - * - * @return void - */ - public function forcePrependStylesheet( - $href, - $media = 'screen', - $conditionalStylesheet = '', - $extras = [] - ) { - // Look for existing entry and remove it if found. Comparison method - // copied from isDuplicate(). - foreach ($this->getContainer() as $offset => $item) { - if (($item->rel == 'stylesheet') && ($item->href == $href)) { - $this->offsetUnset($offset); - break; - } - } - parent::prependStylesheet($href, $media, $conditionalStylesheet, $extras); - } - - /** - * Returns true if file should not be included in the compressed concat file - * Required by ConcatTrait - * - * @param stdClass $item Link element object - * - * @return bool - */ - protected function isExcludedFromConcat($item) - { - return !isset($item->rel) || $item->rel != 'stylesheet' - || !$this->isRelativePath($item->href); - } - - /** - * Get the file path from the link object - * Required by ConcatTrait - * - * @param stdClass $item Link element object - * - * @return string - */ - protected function getResourceFilePath($item) - { - return $item->href; - } - - /** - * Set the file path of the link object - * Required by ConcatTrait - * - * @param stdClass $item Link element object - * @param string $path New path string - * - * @return stdClass - */ - protected function setResourceFilePath($item, $path) - { - $item->href = $path; - return $item; - } - - /** - * Get the file type - * - * @param stdClass $item Link element object - * - * @return string - */ - public function getType($item) - { - $type = $item->media ?? 'all'; - if (isset($item->conditionalStylesheet)) { - $type .= '_' . $item->conditionalStylesheet; - } - return $type; - } - - /** - * Get the minifier that can handle these file types - * Required by ConcatTrait - * - * @return \MatthiasMullie\Minify\JS - */ - protected function getMinifier() - { - $minifier = new \VuFindTheme\Minify\CSS(); - if (null !== $this->maxImportSize) { - $minifier->setMaxImportSize($this->maxImportSize); - } - return $minifier; - } -} diff --git a/module/VuFindTheme/src/VuFindTheme/View/Helper/HeadScript.php b/module/VuFindTheme/src/VuFindTheme/View/Helper/HeadScript.php deleted file mode 100644 index ab0a14a5c93..00000000000 --- a/module/VuFindTheme/src/VuFindTheme/View/Helper/HeadScript.php +++ /dev/null @@ -1,242 +0,0 @@ - - * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License - * @link https://vufind.org/wiki/development Wiki - */ - -namespace VuFindTheme\View\Helper; - -use VuFindTheme\ThemeInfo; - -use function array_key_exists; - -/** - * Head script view helper (extended for VuFind's theme system) - * - * @category VuFind - * @package View_Helpers - * @author Demian Katz - * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License - * @link https://vufind.org/wiki/development Wiki - * - * @method getWhitespace(string|int $indent) - * @method getIndent() - * @method getSeparator() - */ -class HeadScript extends \Laminas\View\Helper\HeadScript implements \Laminas\Log\LoggerAwareInterface -{ - use ConcatTrait { - getMinifiedData as getBaseMinifiedData; - } - use RelativePathTrait; - use \VuFind\Log\LoggerAwareTrait; - - /** - * Theme information service - * - * @var ThemeInfo - */ - protected $themeInfo; - - /** - * CSP nonce - * - * @var string - */ - protected $cspNonce; - - /** - * Constructor - * - * @param ThemeInfo $themeInfo Theme information service - * @param string|bool $plconfig Config for current application environment - * @param string $nonce Nonce from nonce generator - */ - public function __construct(ThemeInfo $themeInfo, $plconfig = false, $nonce = '') - { - parent::__construct(); - $this->themeInfo = $themeInfo; - $this->usePipeline = $this->enabledInConfig($plconfig); - $this->cspNonce = $nonce; - $this->optionalAttributes[] = 'nonce'; - } - - /** - * Folder name and file extension for trait - * - * @return string - */ - protected function getFileType() - { - return 'js'; - } - - /** - * Create script HTML - * - * @param mixed $item Item to convert - * @param string $indent String to add before the item - * @param string $escapeStart Starting sequence - * @param string $escapeEnd Ending sequence - * - * @return string - */ - public function itemToString($item, $indent, $escapeStart, $escapeEnd) - { - // Normalize href to account for themes (if appropriate): - if (!empty($item->attributes['src']) && $this->isRelativePath($item->attributes['src'])) { - $relPath = 'js/' . $item->attributes['src']; - $details = $this->themeInfo - ->findContainingTheme($relPath, ThemeInfo::RETURN_ALL_DETAILS); - - if (!empty($details)) { - $urlHelper = $this->getView()->plugin('url'); - $url = $urlHelper('home') . "themes/{$details['theme']}/" . $relPath; - $url .= strstr($url, '?') ? '&_=' : '?_='; - $url .= filemtime($details['path']); - $item->attributes['src'] = $url; - } - } - - $this->addNonce($item); - return parent::itemToString($item, $indent, $escapeStart, $escapeEnd); - } - - /** - * Forcibly prepend a file removing it from any existing position - * - * @param string $src Script src - * @param string $type Script type - * @param array $attrs Array of script attributes - * - * @return void - */ - public function forcePrependFile( - $src = null, - $type = 'text/javascript', - array $attrs = [] - ) { - // Look for existing entry and remove it if found. Comparison method - // copied from isDuplicate(). - foreach ($this->getContainer() as $offset => $item) { - if ( - ($item->source === null) - && array_key_exists('src', $item->attributes) - && ($src === $item->attributes['src']) - ) { - $this->offsetUnset($offset); - break; - } - } - parent::prependFile($src, $type, $attrs); - } - - /** - * Returns true if file should not be included in the compressed concat file - * Required by ConcatTrait - * - * @param stdClass $item Script element object - * - * @return bool - */ - protected function isExcludedFromConcat($item) - { - return empty($item->attributes['src']) - || isset($item->attributes['conditional']) - || !$this->isRelativePath($item->attributes['src']); - } - - /** - * Get the file path from the script object - * Required by ConcatTrait - * - * @param stdClass $item Script element object - * - * @return string - */ - protected function getResourceFilePath($item) - { - return $item->attributes['src']; - } - - /** - * Set the file path of the script object - * Required by ConcatTrait - * - * @param stdClass $item Script element object - * @param string $path New path string - * - * @return stdClass - */ - protected function setResourceFilePath($item, $path) - { - $item->attributes['src'] = $path; - return $item; - } - - /** - * Get the minifier that can handle these file types - * Required by ConcatTrait - * - * @return \MatthiasMullie\Minify\JS - */ - protected function getMinifier() - { - return new \MatthiasMullie\Minify\JS(); - } - - /** - * Get minified data for a file - * - * @param array $details File details - * @param string $concatPath Target path for the resulting file (used in minifier - * for path mapping) - * - * @throws \Exception - * @return string - */ - protected function getMinifiedData($details, $concatPath) - { - $data = $this->getBaseMinifiedData($details, $concatPath); - // Play it safe by terminating a script with a semicolon - if (!str_ends_with(trim($data), ';')) { - $data .= ';'; - } - return $data; - } - - /** - * Add a nonce to the item - * - * @param stdClass $item Item - * - * @return void - */ - protected function addNonce($item) - { - $item->attributes['nonce'] = $this->cspNonce; - } -} diff --git a/module/VuFindTheme/src/VuFindTheme/View/Helper/InlineScript.php b/module/VuFindTheme/src/VuFindTheme/View/Helper/InlineScript.php deleted file mode 100644 index d8bae7fdbad..00000000000 --- a/module/VuFindTheme/src/VuFindTheme/View/Helper/InlineScript.php +++ /dev/null @@ -1,66 +0,0 @@ - - * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License - * @link https://vufind.org Main Site - */ - -namespace VuFindTheme\View\Helper; - -/** - * Inline script view helper (extended for VuFind's theme system) - * - * @category VuFind - * @package View_Helpers - * @author Demian Katz - * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License - * @link https://vufind.org Main Site - */ -class InlineScript extends HeadScript -{ - /** - * Return InlineScript object - * - * Returns InlineScript helper object; optionally, allows specifying a - * script or script file to include. - * - * @param string $mode Script or file - * @param string $spec Script/url - * @param string $placement Append, prepend, or set - * @param array $attrs Array of script attributes - * @param string $type Script type and/or array of script attributes - * - * @return InlineScript - */ - public function __invoke( - $mode = HeadScript::FILE, - $spec = null, - $placement = 'APPEND', - array $attrs = [], - $type = 'text/javascript' - ) { - return parent::__invoke($mode, $spec, $placement, $attrs, $type); - } -} diff --git a/module/VuFindTheme/src/VuFindTheme/View/Helper/SetupThemeResources.php b/module/VuFindTheme/src/VuFindTheme/View/Helper/SetupThemeResources.php index 5809fee5462..27c0713a903 100644 --- a/module/VuFindTheme/src/VuFindTheme/View/Helper/SetupThemeResources.php +++ b/module/VuFindTheme/src/VuFindTheme/View/Helper/SetupThemeResources.php @@ -109,12 +109,12 @@ protected function addMetaTags() protected function addLinks(bool $partial = false) { // Convenient shortcut to view helper: - $headLink = $this->getView()->plugin('headLink'); + $assetManager = $this->getView()->plugin('assetManager'); // Load CSS (make sure we prepend them in the appropriate order; theme // resources should load before extras added by individual templates): foreach (array_reverse($this->container->getCss()) as $current) { - $headLink()->forcePrependStylesheet( + $assetManager->forcePrependStylesheet( $current['file'], empty($current['media']) ? 'all' : $current['media'], $current['conditional'] ?? '', @@ -128,6 +128,7 @@ protected function addLinks(bool $partial = false) // a link element for each. // Skip favicons in partial mode because they are illegal outside of . if (!$partial && ($favicon = $this->container->getFavicon())) { + $headLink = $this->getView()->plugin('headLink'); $imageLink = $this->getView()->plugin('imageLink'); if (is_array($favicon)) { foreach ($favicon as $attrs) { @@ -156,27 +157,26 @@ protected function addLinks(bool $partial = false) */ protected function addScripts() { - $legalHelpers = ['footScript', 'headScript']; + $legalPositions = ['header', 'footer']; // Load Javascript (same ordering considerations as CSS, above): $js = array_reverse($this->container->getJs()); foreach ($js as $current) { - $position = $current['position'] ?? 'header'; - $helper = substr($position, 0, 4) . 'Script'; - if (!in_array($helper, $legalHelpers)) { + $position = strtolower($current['position'] ?? 'header'); + if (!in_array($position, $legalPositions)) { throw new \Exception( 'Invalid script position for ' . $current['file'] . ': ' . $position . '.' ); } - $this->getView() - ->plugin($helper) - ->forcePrependFile( + ->plugin('assetManager') + ->forcePrependScriptFile( $current['file'], 'text/javascript', - $current['attributes'] ?? [] + $current['attributes'] ?? [], + $position ); } } diff --git a/module/VuFindTheme/tests/unit-tests/src/VuFindTest/View/Helper/SetupThemeResourcesTest.php b/module/VuFindTheme/tests/unit-tests/src/VuFindTest/View/Helper/SetupThemeResourcesTest.php index 11deb4d2243..e690b40a04e 100644 --- a/module/VuFindTheme/tests/unit-tests/src/VuFindTest/View/Helper/SetupThemeResourcesTest.php +++ b/module/VuFindTheme/tests/unit-tests/src/VuFindTest/View/Helper/SetupThemeResourcesTest.php @@ -29,6 +29,10 @@ namespace VuFindTest\View\Helper; +use Laminas\View\Helper\HeadLink; +use Laminas\View\Helper\HeadMeta; +use Laminas\View\Helper\HeadScript; +use PHPUnit\Framework\MockObject\MockObject; use VuFindTheme\ResourceContainer; use VuFindTheme\View\Helper\SetupThemeResources; @@ -108,9 +112,9 @@ protected function getMockView() /** * Get a fake HeadMeta helper. * - * @return \PHPUnit\Framework\MockObject\MockObject&\VuFindTheme\View\Helper\HeadMeta + * @return MockObject&HeadMeta */ - protected function getMockHeadMeta() + protected function getMockHeadMeta(): MockObject&HeadMeta { $mock = $this->getMockBuilder(\Laminas\View\Helper\HeadMeta::class) ->disableOriginalConstructor() @@ -129,28 +133,24 @@ protected function getMockHeadMeta() /** * Get a fake HeadLink helper. * - * @return \Laminas\View\Helper\HeadLink + * @return MockObject&HeadLink */ - protected function getMockHeadLink() + protected function getMockHeadLink(): MockObject&HeadLink { - $mock = $this->getMockBuilder(\VuFindTheme\View\Helper\HeadLink::class) - ->disableOriginalConstructor() - ->getMock(); - $mock->expects($this->any())->method('__invoke')->will($this->returnValue($mock)); + $mock = $this->createMock(HeadLink::class); + $mock->expects($this->any())->method('__invoke')->willReturn($mock); return $mock; } /** * Get a fake HeadScript helper. * - * @return \Laminas\View\Helper\HeadScript + * @return MockObject&HeadScript */ - protected function getMockHeadScript() + protected function getMockHeadScript(): MockObject&HeadScript { - $mock = $this->getMockBuilder(\VuFindTheme\View\Helper\HeadScript::class) - ->disableOriginalConstructor() - ->getMock(); - $mock->expects($this->any())->method('__invoke')->will($this->returnValue($mock)); + $mock = $this->createMock(HeadScript::class); + $mock->expects($this->any())->method('__invoke')->willReturn($mock); return $mock; } } diff --git a/themes/bootstrap5/templates/Auth/MultiILS/loginfields.phtml b/themes/bootstrap5/templates/Auth/MultiILS/loginfields.phtml index 5339d8bb6d5..5afa6436dea 100644 --- a/themes/bootstrap5/templates/Auth/MultiILS/loginfields.phtml +++ b/themes/bootstrap5/templates/Auth/MultiILS/loginfields.phtml @@ -33,5 +33,5 @@ $script = "setupMultiILSLoginFields($methods, 'login_{$topClass}_');"; // Inline the script for lightbox compatibility - echo $this->inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $script, 'SET'); + echo $this->assetManager()->outputInlineScript($script); ?> diff --git a/themes/bootstrap5/templates/ContentBlock/IlsStatusMonitor.phtml b/themes/bootstrap5/templates/ContentBlock/IlsStatusMonitor.phtml index 43f89948903..5db2fa31de1 100644 --- a/themes/bootstrap5/templates/ContentBlock/IlsStatusMonitor.phtml +++ b/themes/bootstrap5/templates/ContentBlock/IlsStatusMonitor.phtml @@ -12,4 +12,4 @@ $ilsStatusScript = <<inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $ilsStatusScript, 'SET'); +echo $this->assetManager()->outputInlineScript($ilsStatusScript); diff --git a/themes/bootstrap5/templates/Helpers/cookie-consent.phtml b/themes/bootstrap5/templates/Helpers/cookie-consent.phtml index 606cc0198b5..54111583db8 100644 --- a/themes/bootstrap5/templates/Helpers/cookie-consent.phtml +++ b/themes/bootstrap5/templates/Helpers/cookie-consent.phtml @@ -1,5 +1,5 @@ headScript()->appendFile('vendor/cookieconsent.umd.js'); +$this->assetManager()->appendScriptFile('vendor/cookieconsent.umd.js'); $configJson = json_encode( [ @@ -7,4 +7,4 @@ $configJson = json_encode( 'controlledVuFindServices' => $this->controlledVuFindServices, ] ); -$this->headScript()->appendScript("window.addEventListener('load', function() { VuFind.cookie.setupConsent($configJson); });"); +$this->assetManager()->appendScript("window.addEventListener('load', function() { VuFind.cookie.setupConsent($configJson); });"); diff --git a/themes/bootstrap5/templates/Helpers/copy-to-clipboard-button.phtml b/themes/bootstrap5/templates/Helpers/copy-to-clipboard-button.phtml index 275da9ccb70..1382924a996 100644 --- a/themes/bootstrap5/templates/Helpers/copy-to-clipboard-button.phtml +++ b/themes/bootstrap5/templates/Helpers/copy-to-clipboard-button.phtml @@ -37,5 +37,5 @@ $script = <<inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $script, 'SET'); +echo $this->assetManager()->outputInlineScript($script); ?> diff --git a/themes/bootstrap5/templates/Helpers/doi.phtml b/themes/bootstrap5/templates/Helpers/doi.phtml index fa14e2e9257..86071a9f4e9 100644 --- a/themes/bootstrap5/templates/Helpers/doi.phtml +++ b/themes/bootstrap5/templates/Helpers/doi.phtml @@ -1,2 +1,2 @@ -inlineScript(\Laminas\View\Helper\HeadScript::FILE, 'doi.js', 'SET');?> +assetManager()->outputInlineScriptFile('doi.js');?> \ No newline at end of file diff --git a/themes/bootstrap5/templates/Recommend/AbstractSearchObjectDeferred.phtml b/themes/bootstrap5/templates/Recommend/AbstractSearchObjectDeferred.phtml index 7ecf9f9b50f..aab7c7017f0 100644 --- a/themes/bootstrap5/templates/Recommend/AbstractSearchObjectDeferred.phtml +++ b/themes/bootstrap5/templates/Recommend/AbstractSearchObjectDeferred.phtml @@ -7,5 +7,5 @@ ?>

icon('spinner') ?> transEsc('loading_ellipsis')?>

- inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $loadJs, 'SET')?> + assetManager()->outputInlineScript($loadJs)?>
diff --git a/themes/bootstrap5/templates/Recommend/DOI.phtml b/themes/bootstrap5/templates/Recommend/DOI.phtml index 185f9c4a0bb..35a6e555e6f 100644 --- a/themes/bootstrap5/templates/Recommend/DOI.phtml +++ b/themes/bootstrap5/templates/Recommend/DOI.phtml @@ -6,6 +6,6 @@ recommend->isFullMatch() && $this->recommend->redirectFullMatch()): ?> escapeJs($url) . '";'; ?> - inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $redirect, 'SET')?> + assetManager()->outputInlineScript($redirect)?> diff --git a/themes/bootstrap5/templates/Recommend/MapSelection.phtml b/themes/bootstrap5/templates/Recommend/MapSelection.phtml index 1566cf53fdd..b9dd89845c3 100644 --- a/themes/bootstrap5/templates/Recommend/MapSelection.phtml +++ b/themes/bootstrap5/templates/Recommend/MapSelection.phtml @@ -5,15 +5,15 @@ 'rectangle_center_message' => 'rectangle_center_message', ]); - $this->headScript()->appendFile('vendor/leaflet/leaflet.js'); - $this->headScript()->appendFile('vendor/leaflet/leaflet.draw.js'); - $this->headScript()->appendFile('vendor/leaflet/leaflet.markercluster.js'); - $this->headScript()->appendFile('map_selection_leaflet.js'); - $this->headLink()->appendStylesheet('vendor/leaflet/leaflet.css'); - $this->headLink()->appendStylesheet('vendor/leaflet/leaflet.draw.css'); - $this->headLink()->appendStylesheet('vendor/leaflet/MarkerCluster.css'); - $this->headLink()->appendStylesheet('vendor/leaflet/MarkerCluster.Default.css'); - $this->headLink()->appendStylesheet('geofeatures.css'); + $this->assetManager()->appendScriptFile('vendor/leaflet/leaflet.js'); + $this->assetManager()->appendScriptFile('vendor/leaflet/leaflet.draw.js'); + $this->assetManager()->appendScriptFile('vendor/leaflet/leaflet.markercluster.js'); + $this->assetManager()->appendScriptFile('map_selection_leaflet.js'); + $this->assetManager()->appendStylesheet('vendor/leaflet/leaflet.css'); + $this->assetManager()->appendStylesheet('vendor/leaflet/leaflet.draw.css'); + $this->assetManager()->appendStylesheet('vendor/leaflet/MarkerCluster.css'); + $this->assetManager()->appendStylesheet('vendor/leaflet/MarkerCluster.Default.css'); + $this->assetManager()->appendStylesheet('geofeatures.css'); $basemap = $this->recommend->getBasemap(); $geoField = $this->recommend->getGeoField(); @@ -41,7 +41,7 @@  transEsc('link_text_need_help')?>
- inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $jsLoad, 'SET')?> + assetManager()->outputInlineScript($jsLoad)?> transEsc('draw_searchbox_start') . '";' . 'L.drawLocal.draw.handlers.simpleshape.tooltip.end = "' . $this->transEsc('draw_searchbox_end') . '";' ?> - inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $loadTranslations, 'SET')?> + assetManager()->outputInlineScript($loadTranslations)?> diff --git a/themes/bootstrap5/templates/Recommend/PubDateVisAjax.phtml b/themes/bootstrap5/templates/Recommend/PubDateVisAjax.phtml index 85772fc4113..0640fd4ce7c 100644 --- a/themes/bootstrap5/templates/Recommend/PubDateVisAjax.phtml +++ b/themes/bootstrap5/templates/Recommend/PubDateVisAjax.phtml @@ -1,10 +1,10 @@ recommend->getVisFacets()): ?> headScript()->appendFile('vendor/flot/jquery.flot.min.js'); - $this->headScript()->appendFile('vendor/flot/jquery.flot.resize.min.js'); - $this->headScript()->appendFile('vendor/flot/jquery.flot.selection.min.js'); - $this->headScript()->appendFile('pubdate_vis.js'); + $this->assetManager()->appendScriptFile('vendor/flot/jquery.flot.min.js'); + $this->assetManager()->appendScriptFile('vendor/flot/jquery.flot.resize.min.js'); + $this->assetManager()->appendScriptFile('vendor/flot/jquery.flot.selection.min.js'); + $this->assetManager()->appendScriptFile('pubdate_vis.js'); ?> $facetRange): ?>
@@ -21,7 +21,7 @@ $js = "loadVis('" . $this->recommend->getFacetFields() . "', '" . $this->recommend->getSearchParams() . "', VuFind.path, " . $this->recommend->getZooming() . ');'; - echo $this->inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $js, 'SET'); + echo $this->assetManager()->outputInlineScript($js); ?> diff --git a/themes/bootstrap5/templates/Recommend/SideFacets.phtml b/themes/bootstrap5/templates/Recommend/SideFacets.phtml index 51b9f456017..8d38e238229 100644 --- a/themes/bootstrap5/templates/Recommend/SideFacets.phtml +++ b/themes/bootstrap5/templates/Recommend/SideFacets.phtml @@ -1,6 +1,6 @@ layout()->sideFacetsInstanceCounter = ($this->layout()->sideFacetsInstanceCounter ?? 0) + 1; - $this->headScript()->appendFile('facets.js'); + $this->assetManager()->appendScriptFile('facets.js'); // Save results/options to $this so they are available to sub-templates: $this->results = $results = $this->recommend->getResults(); @@ -73,4 +73,4 @@
-inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, 'registerSideFacetTruncation();', 'SET');?> +assetManager()->outputInlineScript('registerSideFacetTruncation();');?> diff --git a/themes/bootstrap5/templates/Recommend/SideFacets/range-slider.phtml b/themes/bootstrap5/templates/Recommend/SideFacets/range-slider.phtml index 4aa1b7fc9c1..ead190f0e90 100644 --- a/themes/bootstrap5/templates/Recommend/SideFacets/range-slider.phtml +++ b/themes/bootstrap5/templates/Recommend/SideFacets/range-slider.phtml @@ -35,8 +35,8 @@ facet['type'] == 'date'): ?> - headScript()->appendFile('vendor/bootstrap-slider.min.js'); ?> - headLink()->appendStylesheet('vendor/bootstrap-slider.min.css'); ?> + assetManager()->appendScriptFile('vendor/bootstrap-slider.min.js'); ?> + assetManager()->appendStylesheet('vendor/bootstrap-slider.min.css'); ?> - inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $script, 'SET'); ?> + assetManager()->outputInlineScript($script); ?> diff --git a/themes/bootstrap5/templates/Recommend/SideFacetsDeferred.phtml b/themes/bootstrap5/templates/Recommend/SideFacetsDeferred.phtml index 875222c8c69..4c337fb6456 100644 --- a/themes/bootstrap5/templates/Recommend/SideFacetsDeferred.phtml +++ b/themes/bootstrap5/templates/Recommend/SideFacetsDeferred.phtml @@ -1,6 +1,6 @@ layout()->sideFacetsInstanceCounter = ($this->layout()->sideFacetsInstanceCounter ?? 0) + 1; - $this->headScript()->appendFile('facets.js'); + $this->assetManager()->appendScriptFile('facets.js'); $results = $this->recommend->getResults(); $activeFacets = $this->recommend->getActiveFacets(); @@ -22,8 +22,8 @@ foreach ($activeFacets as $field => $facetName) { if (isset($rangeFacets[$field]) && 'date' === $rangeFacets[$field]['type']) { - $this->headScript()->appendFile('vendor/bootstrap-slider.min.js'); - $this->headLink()->appendStylesheet('vendor/bootstrap-slider.min.css'); + $this->assetManager()->appendScriptFile('vendor/bootstrap-slider.min.js'); + $this->assetManager()->appendStylesheet('vendor/bootstrap-slider.min.css'); break; } } diff --git a/themes/bootstrap5/templates/Recommend/TopFacets.phtml b/themes/bootstrap5/templates/Recommend/TopFacets.phtml index 507f12c36db..a79ad208d96 100644 --- a/themes/bootstrap5/templates/Recommend/TopFacets.phtml +++ b/themes/bootstrap5/templates/Recommend/TopFacets.phtml @@ -58,4 +58,4 @@ VuFind.truncate.initTruncate('.top-facets-contents'); JS; ?> -inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $script, 'SET');?> +assetManager()->outputInlineScript($script);?> diff --git a/themes/bootstrap5/templates/Recommend/VisualFacets.phtml b/themes/bootstrap5/templates/Recommend/VisualFacets.phtml index 77be2dae8b5..b66db03f187 100644 --- a/themes/bootstrap5/templates/Recommend/VisualFacets.phtml +++ b/themes/bootstrap5/templates/Recommend/VisualFacets.phtml @@ -1,6 +1,6 @@ headScript()->appendFile('vendor/d3.min.js'); - $this->headScript()->appendFile('visual_facets.js'); + $this->assetManager()->appendScriptFile('vendor/d3.min.js'); + $this->assetManager()->appendScriptFile('visual_facets.js'); $visualFacetSet = $this->recommend->getPivotFacetSet(); @@ -52,5 +52,5 @@ }); JS; ?> - inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $script, 'SET');?> + assetManager()->outputInlineScript($script);?> diff --git a/themes/bootstrap5/templates/RecordDriver/AbstractBase/previewdata.phtml b/themes/bootstrap5/templates/RecordDriver/AbstractBase/previewdata.phtml index c32aed47cd6..1cd6af0f6a3 100644 --- a/themes/bootstrap5/templates/RecordDriver/AbstractBase/previewdata.phtml +++ b/themes/bootstrap5/templates/RecordDriver/AbstractBase/previewdata.phtml @@ -78,7 +78,7 @@ // add the necessary identifier code: if (!empty($html)) { $html .= ''; - $this->headScript()->appendFile('preview.js'); + $this->assetManager()->appendScriptFile('preview.js'); echo $html; } } diff --git a/themes/bootstrap5/templates/RecordDriver/DefaultRecord/collection-info.phtml b/themes/bootstrap5/templates/RecordDriver/DefaultRecord/collection-info.phtml index 38e332252eb..37a97ab9412 100644 --- a/themes/bootstrap5/templates/RecordDriver/DefaultRecord/collection-info.phtml +++ b/themes/bootstrap5/templates/RecordDriver/DefaultRecord/collection-info.phtml @@ -1,4 +1,4 @@ -headScript()->appendFile('collection_record.js'); ?> +assetManager()->appendScriptFile('collection_record.js'); ?>
record($this->driver)->getQRCode('core'); diff --git a/themes/bootstrap5/templates/RecordDriver/DefaultRecord/explain.phtml b/themes/bootstrap5/templates/RecordDriver/DefaultRecord/explain.phtml index 3faa04f56cd..8a853410e2f 100644 --- a/themes/bootstrap5/templates/RecordDriver/DefaultRecord/explain.phtml +++ b/themes/bootstrap5/templates/RecordDriver/DefaultRecord/explain.phtml @@ -1,6 +1,6 @@ headScript()->appendFile('vendor/chart.js'); -$this->headScript()->appendFile('explain.js'); +$this->assetManager()->appendScriptFile('vendor/chart.js'); +$this->assetManager()->appendScriptFile('explain.js'); $explanation = $this->explanation; $recordId = $explanation->getRecordId(); diff --git a/themes/bootstrap5/templates/RecordDriver/DefaultRecord/list-entry.phtml b/themes/bootstrap5/templates/RecordDriver/DefaultRecord/list-entry.phtml index e34ce8929b3..d7507f5c88e 100644 --- a/themes/bootstrap5/templates/RecordDriver/DefaultRecord/list-entry.phtml +++ b/themes/bootstrap5/templates/RecordDriver/DefaultRecord/list-entry.phtml @@ -256,5 +256,5 @@ }); JS; ?> - inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $script, 'SET');?> + assetManager()->outputInlineScript($script);?> diff --git a/themes/bootstrap5/templates/RecordDriver/DefaultRecord/result-list-explain.phtml b/themes/bootstrap5/templates/RecordDriver/DefaultRecord/result-list-explain.phtml index 29a0a802c70..800add51e1e 100644 --- a/themes/bootstrap5/templates/RecordDriver/DefaultRecord/result-list-explain.phtml +++ b/themes/bootstrap5/templates/RecordDriver/DefaultRecord/result-list-explain.phtml @@ -5,8 +5,8 @@ ?> request) && $score && $maxScore !== null && $maxScore > 0):?> headScript()->appendFile('vendor/chart.js'); - $this->headScript()->appendFile('explain.js'); + $this->assetManager()->appendScriptFile('vendor/chart.js'); + $this->assetManager()->appendScriptFile('explain.js'); $link = $this->recordLinker()->getActionUrl($this->driver, 'Explain', $this->request); ?> diff --git a/themes/bootstrap5/templates/RecordDriver/DefaultRecord/toolbar.phtml b/themes/bootstrap5/templates/RecordDriver/DefaultRecord/toolbar.phtml index 4d2f529be65..fa194202c7e 100644 --- a/themes/bootstrap5/templates/RecordDriver/DefaultRecord/toolbar.phtml +++ b/themes/bootstrap5/templates/RecordDriver/DefaultRecord/toolbar.phtml @@ -1,7 +1,7 @@ addThis(); if (!empty($addThis)) { - $this->headScript()->appendFile('https://s7.addthis.com/js/250/addthis_widget.js?pub=' . urlencode($addThis)); + $this->assetManager()->appendScriptFile('https://s7.addthis.com/js/250/addthis_widget.js?pub=' . urlencode($addThis)); } ?>
inlineScript(\Laminas\View\Helper\HeadScript::FILE, 'hierarchy_tree.js'); + echo $this->assetManager()->outputInlineScriptFile('hierarchy_tree.js'); $js = << VuFind.hierarchyTree.initTree(el)); JS; - echo $this->inlineScript()->appendScript($js); + echo $this->assetManager()->outputInlineScript($js); ?> diff --git a/themes/bootstrap5/templates/RecordTab/map.phtml b/themes/bootstrap5/templates/RecordTab/map.phtml index afb163447a2..f5b4e93b123 100644 --- a/themes/bootstrap5/templates/RecordTab/map.phtml +++ b/themes/bootstrap5/templates/RecordTab/map.phtml @@ -1,8 +1,8 @@ headScript()->appendFile('vendor/leaflet/leaflet.js'); - $this->headScript()->appendFile('vendor/leaflet/leaflet.latlng-graticule.js'); - $this->headScript()->appendFile('map_tab_leaflet.js'); - $this->headLink()->appendStylesheet('vendor/leaflet/leaflet.css'); + $this->assetManager()->appendScriptFile('vendor/leaflet/leaflet.js'); + $this->assetManager()->appendScriptFile('vendor/leaflet/leaflet.latlng-graticule.js'); + $this->assetManager()->appendScriptFile('map_tab_leaflet.js'); + $this->assetManager()->appendStylesheet('vendor/leaflet/leaflet.css'); $this->jsTranslations()->addStrings( ['Coordinates' => 'Coordinates', 'no_description' => 'no_description'] ); @@ -17,5 +17,5 @@ ?>
- inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $jsLoad, 'SET')?> + assetManager()->outputInlineScript($jsLoad)?>
diff --git a/themes/bootstrap5/templates/RecordTab/preview.phtml b/themes/bootstrap5/templates/RecordTab/preview.phtml index 289442f7e67..53c840dab5d 100644 --- a/themes/bootstrap5/templates/RecordTab/preview.phtml +++ b/themes/bootstrap5/templates/RecordTab/preview.phtml @@ -3,7 +3,7 @@ $this->headTitle($this->translate('Preview') . ': ' . $this->driver->getBreadcrumb()); // load the embedded preview javascript file - $this->headScript()->appendFile('https://www.google.com/books/jsapi.js'); - $this->headScript()->appendFile('embedGBS.js'); + $this->assetManager()->appendScriptFile('https://www.google.com/books/jsapi.js'); + $this->assetManager()->appendScriptFile('embedGBS.js'); ?>
diff --git a/themes/bootstrap5/templates/RecordTab/similaritemscarousel.phtml b/themes/bootstrap5/templates/RecordTab/similaritemscarousel.phtml index 951de2e3f94..ddcf99a218a 100644 --- a/themes/bootstrap5/templates/RecordTab/similaritemscarousel.phtml +++ b/themes/bootstrap5/templates/RecordTab/similaritemscarousel.phtml @@ -75,4 +75,4 @@ $('#similar-items-carousel img').on('load', normalizeHeights); JS; ?> -inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $script, 'SET') ?> +assetManager()->outputInlineScript($script) ?> diff --git a/themes/bootstrap5/templates/RecordTab/versions.phtml b/themes/bootstrap5/templates/RecordTab/versions.phtml index a3362e46afb..1af3efe6218 100644 --- a/themes/bootstrap5/templates/RecordTab/versions.phtml +++ b/themes/bootstrap5/templates/RecordTab/versions.phtml @@ -55,4 +55,4 @@ $script = << -inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $script, 'SET'); ?> +assetManager()->outputInlineScript($script); ?> diff --git a/themes/bootstrap5/templates/admin/feedback/home.phtml b/themes/bootstrap5/templates/admin/feedback/home.phtml index 6fafb0095fa..7aca6e2ba75 100644 --- a/themes/bootstrap5/templates/admin/feedback/home.phtml +++ b/themes/bootstrap5/templates/admin/feedback/home.phtml @@ -176,4 +176,4 @@ $js = << -inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $js, 'SET');?> +assetManager()->outputInlineScript($js);?> diff --git a/themes/bootstrap5/templates/cart/cart.phtml b/themes/bootstrap5/templates/cart/cart.phtml index 73ac4a2b802..8f8b5b9947b 100644 --- a/themes/bootstrap5/templates/cart/cart.phtml +++ b/themes/bootstrap5/templates/cart/cart.phtml @@ -121,4 +121,4 @@ }); JS; ?> -inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $script, 'SET') ?> +assetManager()->outputInlineScript($script) ?> diff --git a/themes/bootstrap5/templates/cart/email.phtml b/themes/bootstrap5/templates/cart/email.phtml index a7b8a75811f..bbd6f0b8333 100644 --- a/themes/bootstrap5/templates/cart/email.phtml +++ b/themes/bootstrap5/templates/cart/email.phtml @@ -40,4 +40,4 @@ $('#itemhide').removeClass('show'); JS; ?> -inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $script, 'SET') ?> +assetManager()->outputInlineScript($script) ?> diff --git a/themes/bootstrap5/templates/cart/export.phtml b/themes/bootstrap5/templates/cart/export.phtml index abaec540fc1..1a843a6717c 100644 --- a/themes/bootstrap5/templates/cart/export.phtml +++ b/themes/bootstrap5/templates/cart/export.phtml @@ -72,4 +72,4 @@ }).trigger('change'); JS; ?> -inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $script, 'SET') ?> +assetManager()->outputInlineScript($script) ?> diff --git a/themes/bootstrap5/templates/cart/save.phtml b/themes/bootstrap5/templates/cart/save.phtml index dbc59e1c498..9cc56c6cae4 100644 --- a/themes/bootstrap5/templates/cart/save.phtml +++ b/themes/bootstrap5/templates/cart/save.phtml @@ -66,4 +66,4 @@ $('#itemhide').removeClass('show'); JS; ?> -inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $script, 'SET') ?> +assetManager()->outputInlineScript($script) ?> diff --git a/themes/bootstrap5/templates/channels/channelList.phtml b/themes/bootstrap5/templates/channels/channelList.phtml index 2812b28823b..7f54db83004 100644 --- a/themes/bootstrap5/templates/channels/channelList.phtml +++ b/themes/bootstrap5/templates/channels/channelList.phtml @@ -1,8 +1,8 @@ headLink()->appendStylesheet('vendor/slick.css'); - $this->headLink()->appendStylesheet('vendor/slick-theme.css'); - $this->headScript()->appendFile('vendor/slick.min.js'); - $this->headScript()->appendFile('channels.js'); + $this->assetManager()->appendStylesheet('vendor/slick.css'); + $this->assetManager()->appendStylesheet('vendor/slick-theme.css'); + $this->assetManager()->appendScriptFile('vendor/slick.min.js'); + $this->assetManager()->appendScriptFile('channels.js'); $this->jsTranslations()->addStrings([ 'channel_browse' => 'channel_browse', 'channel_expand' => 'channel_expand', diff --git a/themes/bootstrap5/templates/checkouts/history.phtml b/themes/bootstrap5/templates/checkouts/history.phtml index 24f67b6d30c..22c872f3c61 100644 --- a/themes/bootstrap5/templates/checkouts/history.phtml +++ b/themes/bootstrap5/templates/checkouts/history.phtml @@ -5,7 +5,7 @@ // Set up breadcrumbs: $this->layout()->breadcrumbs = '
'; - $this->headScript()->appendFile('checkouts.js'); + $this->assetManager()->appendScriptFile('checkouts.js'); ?> component('show-account-menu-button')?> diff --git a/themes/bootstrap5/templates/collection/view.phtml b/themes/bootstrap5/templates/collection/view.phtml index 46c7e05ecfd..8e7200071fb 100644 --- a/themes/bootstrap5/templates/collection/view.phtml +++ b/themes/bootstrap5/templates/collection/view.phtml @@ -1,14 +1,14 @@ headScript()->appendFile('record.js'); - $this->headScript()->appendFile('check_save_statuses.js'); + $this->assetManager()->appendScriptFile('record.js'); + $this->assetManager()->appendScriptFile('check_save_statuses.js'); // Activate Syndetics Plus if necessary: if ($this->syndeticsPlus()->isActive()) { - $this->headScript()->appendFile($this->syndeticsPlus()->getScript()); + $this->assetManager()->appendScriptFile($this->syndeticsPlus()->getScript()); } // Add any extra scripts the tabs require: foreach ($this->tabsExtraScripts as $script) { - $this->headScript()->appendFile($script); + $this->assetManager()->appendScriptFile($script); } // Add RDF header link if applicable: @@ -106,4 +106,4 @@ driver->supportsCoinsOpenURL() ? '' : ''?> -inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, '$(document).ready(recordDocReady);', 'SET'); ?> +assetManager()->outputInlineScript('$(document).ready(recordDocReady);'); ?> diff --git a/themes/bootstrap5/templates/combined/results-ajax.phtml b/themes/bootstrap5/templates/combined/results-ajax.phtml index 2782549f91b..f59177d147d 100644 --- a/themes/bootstrap5/templates/combined/results-ajax.phtml +++ b/themes/bootstrap5/templates/combined/results-ajax.phtml @@ -30,5 +30,5 @@

icon('spinner') ?> transEsc('loading_ellipsis')?>

-inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $loadJs, 'SET')?> +assetManager()->outputInlineScript($loadJs)?> diff --git a/themes/bootstrap5/templates/combined/results-list.phtml b/themes/bootstrap5/templates/combined/results-list.phtml index 5178966bf7a..16274f04a63 100644 --- a/themes/bootstrap5/templates/combined/results-list.phtml +++ b/themes/bootstrap5/templates/combined/results-list.phtml @@ -75,7 +75,7 @@ } JS; ?> -inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $countsJs, 'SET')?> +assetManager()->outputInlineScript($countsJs)?>
@@ -110,7 +110,7 @@ $recommendationClass = str_replace('\\', '_', $current::class); $recommendationJs = $generateMoveRecommendationToSidebarJavascript($recommendationClass); ?> - inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $recommendationJs, 'SET')?> + assetManager()->outputInlineScript($recommendationJs)?> @@ -156,7 +156,7 @@ $recommendationClass = str_replace('\\', '_', $current::class); $recommendationJs = $generateMoveRecommendationToSidebarJavascript($recommendationClass); ?> - inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $recommendationJs, 'SET')?> + assetManager()->outputInlineScript($recommendationJs)?> diff --git a/themes/bootstrap5/templates/combined/results.phtml b/themes/bootstrap5/templates/combined/results.phtml index f2b35209604..5115cec5f53 100644 --- a/themes/bootstrap5/templates/combined/results.phtml +++ b/themes/bootstrap5/templates/combined/results.phtml @@ -39,9 +39,9 @@ // Load Javascript dependencies into header: $this->render('search/results-scripts.phtml', compact('displayVersions')); - $this->headScript()->appendFile('combined-search.js'); + $this->assetManager()->appendScriptFile('combined-search.js'); // Style - $this->headLink()->appendStylesheet('combined-search.css'); + $this->assetManager()->appendStylesheet('combined-search.css'); ?> flashmessages()?>

escapeHtml($headTitle)?>

diff --git a/themes/bootstrap5/templates/devtools/language.phtml b/themes/bootstrap5/templates/devtools/language.phtml index afb66cbec74..76046109129 100644 --- a/themes/bootstrap5/templates/devtools/language.phtml +++ b/themes/bootstrap5/templates/devtools/language.phtml @@ -175,4 +175,4 @@ Current filter mode: -inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $script, 'SET') ?> +assetManager()->outputInlineScript($script) ?> diff --git a/themes/bootstrap5/templates/holds/edit.phtml b/themes/bootstrap5/templates/holds/edit.phtml index 5745722252f..b4bd3ef5f3a 100644 --- a/themes/bootstrap5/templates/holds/edit.phtml +++ b/themes/bootstrap5/templates/holds/edit.phtml @@ -86,7 +86,7 @@ inlineScript()->appendFile('hold.js'); + echo $this->assetManager()->outputInlineScriptFile('hold.js'); $js = <<inlineScript()->appendScript($js); + echo $this->assetManager()->outputInlineScript($js); ?> diff --git a/themes/bootstrap5/templates/holds/list.phtml b/themes/bootstrap5/templates/holds/list.phtml index cfec6834b1a..aa457f1cc22 100644 --- a/themes/bootstrap5/templates/holds/list.phtml +++ b/themes/bootstrap5/templates/holds/list.phtml @@ -5,7 +5,7 @@ // Set up breadcrumbs: $this->layout()->breadcrumbs = ' '; - $this->headScript()->appendFile('requests.js'); + $this->assetManager()->appendScriptFile('requests.js'); ?> component('show-account-menu-button')?> diff --git a/themes/bootstrap5/templates/install/showsql.phtml b/themes/bootstrap5/templates/install/showsql.phtml index 6cc70323fa8..13d3044e93c 100644 --- a/themes/bootstrap5/templates/install/showsql.phtml +++ b/themes/bootstrap5/templates/install/showsql.phtml @@ -6,7 +6,7 @@ $this->layout()->breadcrumbs = ''; // Set up styles: - $this->headstyle()->appendStyle( + $this->assetManager()->appendStyle( ".pre {\n" . " white-space:pre-wrap; width:90%; overflow-y:visible; padding:8px; margin:1em 2em; background:#EEE; border:1px dashed #CCC;\n" . "}\n" @@ -29,4 +29,4 @@ $script = << -inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $script, 'SET'); +assetManager()->outputInlineScript($script); diff --git a/themes/bootstrap5/templates/layout/layout.phtml b/themes/bootstrap5/templates/layout/layout.phtml index feb9cb0afb4..7d11c6b8263 100644 --- a/themes/bootstrap5/templates/layout/layout.phtml +++ b/themes/bootstrap5/templates/layout/layout.phtml @@ -52,27 +52,25 @@ ?> layout()->rtl) { // RTL styling - $this->headLink()->appendStylesheet('vendor/bootstrap-rtl.min.css'); + $this->assetManager()->appendStylesheet('vendor/bootstrap-rtl.min.css'); } ?> - headLink()?> - headStyle()?> themeConfig(); if (!empty($themeConfig['stickyElements'])) { - $this->headScript()->appendFile('sticky_elements.js'); + $this->assetManager()->appendScriptFile('sticky_elements.js'); } if (!isset($this->renderingError)) { // Deal with cart stuff: $cart = $this->cart(); if ($cart->isActive()) { - $this->headScript()->appendFile('vendor/js.cookie.js'); - $this->headScript()->appendFile('cart.js'); + $this->assetManager()->appendScriptFile('vendor/js.cookie.js'); + $this->assetManager()->appendScriptFile('cart.js'); } - $this->headScript()->prependScript( + $this->assetManager()->prependScript( 'var userIsLoggedIn = ' . ($this->auth()->getIdentity() ? 'true' : 'false') . ';' ); } @@ -80,16 +78,20 @@ // Session keep-alive if ($this->keepAlive()) { $appendScripts[] = 'var keepAliveInterval = ' . $this->keepAlive() . ';'; - $this->headScript()->appendFile('keep_alive.js'); + $this->assetManager()->appendScriptFile('keep_alive.js'); } // If account ajax is active, load script and add language strings $account = $this->auth()->getManager(); if ($account->ajaxEnabled()) { - $this->headScript()->appendFile('account_ajax.js'); + $this->assetManager()->appendScriptFile('account_ajax.js'); if ($this->session()->put('reset_account_status', null)) { - $this->headScript()->setAllowArbitraryAttributes(true); - $this->headScript()->appendScript('VuFind.account.clearAllCaches();', 'text/javascript', ['data-lightbox-run' => 'always']); + $this->assetManager()->appendScript( + 'VuFind.account.clearAllCaches();', + 'text/javascript', + ['data-lightbox-run' => 'always'], + true + ); } } @@ -132,10 +134,10 @@ $appendScripts[] = 'VuFind.lightbox.child = ' . $lightboxChild . ';'; } - $this->headScript()->appendScript(implode("\n", $appendScripts)); + $this->assetManager()->appendScript(implode("\n", $appendScripts)); ?> cookieConsent()->render()?> - headScript() ?> + assetManager()->outputHeaderAssets()?>
@@ -172,9 +174,9 @@
render('Helpers/analytics.phtml')?> captcha()->js() as $jsInclude):?> - inlineScript(\Laminas\View\Helper\HeadScript::FILE, $jsInclude, 'SET')?> + assetManager()->outputInlineScriptFile($jsInclude)?> - footScript() ?> + assetManager()->outputFooterAssets() ?> diff --git a/themes/bootstrap5/templates/layout/lightbox.phtml b/themes/bootstrap5/templates/layout/lightbox.phtml index 6649acb9814..6829a261e9b 100644 --- a/themes/bootstrap5/templates/layout/lightbox.phtml +++ b/themes/bootstrap5/templates/layout/lightbox.phtml @@ -3,6 +3,9 @@ matomo(['context' => $this->layoutContext ?? 'lightbox'])?> googleanalytics($this->serverUrl(true))?> session()->put('reset_account_status', null) && $this->auth()->getManager()->ajaxEnabled()) { - $this->inlineScript()->setAllowArbitraryAttributes(true); - echo $this->inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, 'VuFind.account.clearAllCaches();', 'SET', ['data-lightbox-run' => 'always']); + echo $this->assetManager()->outputInlineScript( + script: 'VuFind.account.clearAllCaches();', + attrs: ['data-lightbox-run' => 'always'], + allowArbitraryAttrs: true + ); } diff --git a/themes/bootstrap5/templates/librarycards/editcard.phtml b/themes/bootstrap5/templates/librarycards/editcard.phtml index 5a8a3db37ab..3a3d92787ae 100644 --- a/themes/bootstrap5/templates/librarycards/editcard.phtml +++ b/themes/bootstrap5/templates/librarycards/editcard.phtml @@ -55,6 +55,6 @@ $script = "setupMultiILSLoginFields($methods, 'login_');"; // Inline the script for lightbox compatibility - echo $this->inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $script, 'SET'); + echo $this->assetManager()->outputInlineScript($script); } ?> diff --git a/themes/bootstrap5/templates/myresearch/cataloglogin.phtml b/themes/bootstrap5/templates/myresearch/cataloglogin.phtml index 8470c18750c..db1478cff81 100644 --- a/themes/bootstrap5/templates/myresearch/cataloglogin.phtml +++ b/themes/bootstrap5/templates/myresearch/cataloglogin.phtml @@ -69,7 +69,7 @@ $script = "setupMultiILSLoginFields($methods, 'profile_cat_');"; // Inline the script for lightbox compatibility - echo $this->inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $script, 'SET'); + echo $this->assetManager()->outputInlineScript($script); } ?> diff --git a/themes/bootstrap5/templates/myresearch/checkedout.phtml b/themes/bootstrap5/templates/myresearch/checkedout.phtml index c2b81d7b467..c95c7abc7ed 100644 --- a/themes/bootstrap5/templates/myresearch/checkedout.phtml +++ b/themes/bootstrap5/templates/myresearch/checkedout.phtml @@ -5,7 +5,7 @@ // Set up breadcrumbs: $this->layout()->breadcrumbs = ' '; - $this->headScript()->appendFile('checkouts.js'); + $this->assetManager()->appendScriptFile('checkouts.js'); // Check if "Renew All" button can be displayed: $renewAll = !$this->ilsPaging || !$paginator; diff --git a/themes/bootstrap5/templates/myresearch/deleteaccount.phtml b/themes/bootstrap5/templates/myresearch/deleteaccount.phtml index b8ee8b5ebab..4eb862d1cd1 100644 --- a/themes/bootstrap5/templates/myresearch/deleteaccount.phtml +++ b/themes/bootstrap5/templates/myresearch/deleteaccount.phtml @@ -7,7 +7,7 @@ // Logout redirect with inline script to make it lightbox compatible $script = "setTimeout(function() { window.location = '{$this->redirectUrl}'; }, 3000);"; ?> - inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $script, 'SET'); ?> + assetManager()->outputInlineScript($script); ?>
diff --git a/themes/bootstrap5/templates/myresearch/illrequests.phtml b/themes/bootstrap5/templates/myresearch/illrequests.phtml index 5124031d9ba..8c3b307d29b 100644 --- a/themes/bootstrap5/templates/myresearch/illrequests.phtml +++ b/themes/bootstrap5/templates/myresearch/illrequests.phtml @@ -6,7 +6,7 @@ $this->layout()->breadcrumbs = '' . ''; - $this->headScript()->appendFile('requests.js'); + $this->assetManager()->appendScriptFile('requests.js'); ?> component('show-account-menu-button')?> diff --git a/themes/bootstrap5/templates/myresearch/mylist.phtml b/themes/bootstrap5/templates/myresearch/mylist.phtml index 9ab17f6e12d..6b87bc52f9d 100644 --- a/themes/bootstrap5/templates/myresearch/mylist.phtml +++ b/themes/bootstrap5/templates/myresearch/mylist.phtml @@ -10,12 +10,12 @@ $this->layout()->breadcrumbs = ' '; // Load Javascript dependencies into header: - $this->headScript()->appendFile('check_item_statuses.js'); + $this->assetManager()->appendScriptFile('check_item_statuses.js'); // Load Javascript only if list view parameter is NOT full: if ($this->params->getOptions()->getListViewOption() != 'full') { - $this->headScript()->appendFile('record.js'); - $this->headScript()->appendFile('embedded_record.js'); + $this->assetManager()->appendScriptFile('record.js'); + $this->assetManager()->appendScriptFile('embedded_record.js'); } $recordTotal = $this->results->getResultTotal(); diff --git a/themes/bootstrap5/templates/myresearch/notify-account-status.phtml b/themes/bootstrap5/templates/myresearch/notify-account-status.phtml index ff256d68886..5bc4cc431c3 100644 --- a/themes/bootstrap5/templates/myresearch/notify-account-status.phtml +++ b/themes/bootstrap5/templates/myresearch/notify-account-status.phtml @@ -1,5 +1,5 @@ accountStatus) { $notifyScript = 'VuFind.account.notify("' . $this->method . '", ' . json_encode($this->accountStatus) . ');'; - echo $this->inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $notifyScript, 'SET'); + echo $this->assetManager()->outputInlineScript($notifyScript); } diff --git a/themes/bootstrap5/templates/myresearch/storageretrievalrequests.phtml b/themes/bootstrap5/templates/myresearch/storageretrievalrequests.phtml index 143c6a61d36..4949d13ff7f 100644 --- a/themes/bootstrap5/templates/myresearch/storageretrievalrequests.phtml +++ b/themes/bootstrap5/templates/myresearch/storageretrievalrequests.phtml @@ -5,7 +5,7 @@ // Set up breadcrumbs: $this->layout()->breadcrumbs = ' '; - $this->headScript()->appendFile('requests.js'); + $this->assetManager()->appendScriptFile('requests.js'); ?> component('show-account-menu-button')?> diff --git a/themes/bootstrap5/templates/record/hold.phtml b/themes/bootstrap5/templates/record/hold.phtml index dab9a69f58a..7e6d5534fe7 100644 --- a/themes/bootstrap5/templates/record/hold.phtml +++ b/themes/bootstrap5/templates/record/hold.phtml @@ -143,7 +143,7 @@ inlineScript()->appendFile('hold.js'); + echo $this->assetManager()->outputInlineScriptFile('hold.js'); $js = <<inlineScript()->appendScript($js); + echo $this->assetManager()->outputInlineScript($js); ?> diff --git a/themes/bootstrap5/templates/record/illrequest.phtml b/themes/bootstrap5/templates/record/illrequest.phtml index 2abee2727d9..96a652cde74 100644 --- a/themes/bootstrap5/templates/record/illrequest.phtml +++ b/themes/bootstrap5/templates/record/illrequest.phtml @@ -112,7 +112,7 @@ inlineScript()->appendFile('ill.js'); + echo $this->assetManager()->outputInlineScriptFile('ill.js'); $js = <<inlineScript()->appendScript($js); + echo $this->assetManager()->outputInlineScript($js); ?> diff --git a/themes/bootstrap5/templates/record/sms.phtml b/themes/bootstrap5/templates/record/sms.phtml index 00f44d9c0b0..5d6f8a15e81 100644 --- a/themes/bootstrap5/templates/record/sms.phtml +++ b/themes/bootstrap5/templates/record/sms.phtml @@ -1,7 +1,7 @@ headTitle($this->translate('Text this')); - echo $this->inlineScript(\Laminas\View\Helper\HeadScript::FILE, 'vendor/libphonenumber.js', 'SET'); + echo $this->assetManager()->outputInlineScriptFile('vendor/libphonenumber.js'); // Set up breadcrumbs: $this->layout()->breadcrumbs = $this->searchMemory()->getLastSearchLink($this->transEsc('Search'), ' ') diff --git a/themes/bootstrap5/templates/record/storageretrievalrequest.phtml b/themes/bootstrap5/templates/record/storageretrievalrequest.phtml index af1277ce580..131a7957e77 100644 --- a/themes/bootstrap5/templates/record/storageretrievalrequest.phtml +++ b/themes/bootstrap5/templates/record/storageretrievalrequest.phtml @@ -113,4 +113,4 @@ }); JS; ?> -inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $changeHandler, 'SET'); +assetManager()->outputInlineScript($changeHandler); diff --git a/themes/bootstrap5/templates/record/taglist.phtml b/themes/bootstrap5/templates/record/taglist.phtml index 184374ce042..0d2eb264b19 100644 --- a/themes/bootstrap5/templates/record/taglist.phtml +++ b/themes/bootstrap5/templates/record/taglist.phtml @@ -32,4 +32,4 @@ $script = << -inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $script, 'SET'); +assetManager()->outputInlineScript($script); diff --git a/themes/bootstrap5/templates/record/view.phtml b/themes/bootstrap5/templates/record/view.phtml index ddb10b6711a..03a66c9ca3e 100644 --- a/themes/bootstrap5/templates/record/view.phtml +++ b/themes/bootstrap5/templates/record/view.phtml @@ -5,15 +5,15 @@ $this->headTitle($this->driver->getBreadcrumb()); // Set up standard record scripts: - $this->headScript()->appendFile('record.js'); - $this->headScript()->appendFile('check_save_statuses.js'); + $this->assetManager()->appendScriptFile('record.js'); + $this->assetManager()->appendScriptFile('check_save_statuses.js'); // Activate Syndetics Plus if necessary: if ($this->syndeticsPlus()->isActive()) { - $this->headScript()->appendFile($this->syndeticsPlus()->getScript()); + $this->assetManager()->appendScriptFile($this->syndeticsPlus()->getScript()); } // Add any extra scripts the tabs require: foreach ($this->tabsExtraScripts as $script) { - $this->headScript()->appendFile($script); + $this->assetManager()->appendScriptFile($script); } // Add RDF header link if applicable: @@ -106,4 +106,4 @@ -inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, '$(document).ready(recordDocReady);', 'SET'); ?> +assetManager()->outputInlineScript('$(document).ready(recordDocReady);'); ?> diff --git a/themes/bootstrap5/templates/relais/button.phtml b/themes/bootstrap5/templates/relais/button.phtml index 49c35698aa3..fdee2d66a04 100644 --- a/themes/bootstrap5/templates/relais/button.phtml +++ b/themes/bootstrap5/templates/relais/button.phtml @@ -21,11 +21,11 @@ 'relais_success_message' => 'relais_success_message', ] ); - $this->headScript()->appendFile('relais.js'); + $this->assetManager()->appendScriptFile('relais.js'); $addLink = $this->escapeJs($this->url('relais-request')); $oclc = $this->escapeJs($driver->tryMethod('getCleanOCLCNum')); $failLink = $this->escapeJs($this->relais()->getSearchLink($driver)); $activateRelais = "$(document).ready(function() { VuFind.relais.checkAvailability('$addLink', '$oclc', '$failLink') });"; - echo $this->inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $activateRelais, 'SET'); + echo $this->assetManager()->outputInlineScript($activateRelais); } ?> \ No newline at end of file diff --git a/themes/bootstrap5/templates/relais/request.phtml b/themes/bootstrap5/templates/relais/request.phtml index 62315e7e4fc..fae50e7dfb1 100644 --- a/themes/bootstrap5/templates/relais/request.phtml +++ b/themes/bootstrap5/templates/relais/request.phtml @@ -8,7 +8,7 @@
headScript()->appendFile('relais.js'); + $this->assetManager()->appendScriptFile('relais.js'); $activateRelais = "$(document).ready(function () { VuFind.relais.addItem('$oclc', '$failLink'); });\n"; - echo $this->inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $activateRelais, 'SET') + echo $this->assetManager()->outputInlineScript($activateRelais); ?> diff --git a/themes/bootstrap5/templates/search/advanced/eds.phtml b/themes/bootstrap5/templates/search/advanced/eds.phtml index d1f5446b143..0e2644d055e 100644 --- a/themes/bootstrap5/templates/search/advanced/eds.phtml +++ b/themes/bootstrap5/templates/search/advanced/eds.phtml @@ -109,8 +109,8 @@ headScript()->appendFile('vendor/bootstrap-slider.min.js'); - $this->headLink()->appendStylesheet('vendor/bootstrap-slider.min.css'); + $this->assetManager()->appendScriptFile('vendor/bootstrap-slider.min.js'); + $this->assetManager()->appendStylesheet('vendor/bootstrap-slider.min.css'); $min = !empty($current['values'][0]) ? min($current['values'][0], 1400) : 1400; $future = date('Y', time() + 31536000); $max = !empty($current['values'][1]) ? max($future, $current['values'][1]) : $future; @@ -142,6 +142,6 @@ }); JS; ?> - inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $script, 'SET'); ?> + assetManager()->outputInlineScript($script); ?> diff --git a/themes/bootstrap5/templates/search/advanced/layout.phtml b/themes/bootstrap5/templates/search/advanced/layout.phtml index de2a14bb979..55859e5843c 100644 --- a/themes/bootstrap5/templates/search/advanced/layout.phtml +++ b/themes/bootstrap5/templates/search/advanced/layout.phtml @@ -34,11 +34,11 @@ } // Step 1: Load the javascript - $this->headScript()->appendFile( + $this->assetManager()->appendScriptFile( $this->advancedSearchJsOverride ?? 'advanced_search.js' ); // Step 2: Build the page - $this->headScript()->appendScript( + $this->assetManager()->appendScript( $this->partial( $this->buildPageOverride ?? 'search/advanced/build_page.phtml', ['options' => $this->options, 'searchDetails' => $searchDetails] @@ -235,4 +235,4 @@ $script = << -inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $script, 'SET'); +assetManager()->outputInlineScript($script); diff --git a/themes/bootstrap5/templates/search/advanced/ranges.phtml b/themes/bootstrap5/templates/search/advanced/ranges.phtml index 00771cad49a..aff11c74d97 100644 --- a/themes/bootstrap5/templates/search/advanced/ranges.phtml +++ b/themes/bootstrap5/templates/search/advanced/ranges.phtml @@ -20,8 +20,8 @@ headScript()->appendFile('vendor/bootstrap-slider.min.js'); - $this->headLink()->appendStylesheet('vendor/bootstrap-slider.min.css'); + $this->assetManager()->appendScriptFile('vendor/bootstrap-slider.min.js'); + $this->assetManager()->appendStylesheet('vendor/bootstrap-slider.min.css'); $min = !empty($current['values'][0]) ? min($current['values'][0], 1400) : 1400; $future = date('Y', time() + 31536000); $max = !empty($current['values'][1]) ? max($future, $current['values'][1]) : $future; @@ -70,7 +70,7 @@ }); JS; ?> - inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $script, 'SET'); ?> + assetManager()->outputInlineScript($script); ?> diff --git a/themes/bootstrap5/templates/search/facet-list.phtml b/themes/bootstrap5/templates/search/facet-list.phtml index 2f4f79290d5..9c064baca82 100644 --- a/themes/bootstrap5/templates/search/facet-list.phtml +++ b/themes/bootstrap5/templates/search/facet-list.phtml @@ -20,8 +20,8 @@ } $this->headTitle($this->translate('facet_list_for', ['%%field%%' => $this->facetLabel])); $multiFacetsSelection = $this->multiFacetsSelection ? 'true' : 'false'; - $this->headScript()->appendScript('var multiFacetsSelectionEnabled = ' . $multiFacetsSelection . ';'); - $this->headScript()->appendFile('facets.js'); + $this->assetManager()->appendScript('var multiFacetsSelectionEnabled = ' . $multiFacetsSelection . ';'); + $this->assetManager()->appendScriptFile('facets.js'); ?>

transEsc($this->facetLabel) ?>

@@ -67,7 +67,7 @@ -inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, 'VuFind.facetList.setup();', 'SET')?> +assetManager()->outputInlineScript('VuFind.facetList.setup();')?> inLightbox): ?> - inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, 'VuFind.lightbox_facets.setup();', 'SET')?> + assetManager()->outputInlineScript('VuFind.lightbox_facets.setup();')?> diff --git a/themes/bootstrap5/templates/search/history.phtml b/themes/bootstrap5/templates/search/history.phtml index ddc91c13021..54fcb4cca7f 100644 --- a/themes/bootstrap5/templates/search/history.phtml +++ b/themes/bootstrap5/templates/search/history.phtml @@ -76,4 +76,4 @@ }) JS; ?> -inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, $script, 'SET') ?> +assetManager()->outputInlineScript($script) ?> diff --git a/themes/bootstrap5/templates/search/home.phtml b/themes/bootstrap5/templates/search/home.phtml index e25140bec8e..f296ba5069c 100644 --- a/themes/bootstrap5/templates/search/home.phtml +++ b/themes/bootstrap5/templates/search/home.phtml @@ -17,7 +17,7 @@
slot('search-home-hero')->start() ?> render('search/searchbox.phtml')?> - inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, '$("#searchForm_lookfor").focus();', 'SET'); ?> + assetManager()->outputInlineScript('$("#searchForm_lookfor").focus();'); ?> slot('search-home-hero')->end() ?>
diff --git a/themes/bootstrap5/templates/search/newitem.phtml b/themes/bootstrap5/templates/search/newitem.phtml index c05a573979c..d00d30c5ad9 100644 --- a/themes/bootstrap5/templates/search/newitem.phtml +++ b/themes/bootstrap5/templates/search/newitem.phtml @@ -6,7 +6,7 @@ $this->layout()->breadcrumbs = ''; // Load advanced search Javascript to activate the clear button: - $this->headScript()->appendFile('advanced_search.js'); + $this->assetManager()->appendScriptFile('advanced_search.js'); // Convenience variable: $offlineMode = $this->ils()->getOfflineMode(); diff --git a/themes/bootstrap5/templates/search/reservessearch.phtml b/themes/bootstrap5/templates/search/reservessearch.phtml index 2925c427c12..a088794118f 100644 --- a/themes/bootstrap5/templates/search/reservessearch.phtml +++ b/themes/bootstrap5/templates/search/reservessearch.phtml @@ -48,7 +48,7 @@ ] ); ?> - inlineScript(\Laminas\View\Helper\HeadScript::SCRIPT, "$('#reservesSearchForm_lookfor').focus()", 'SET')?> + assetManager()->outputInlineScript("$('#reservesSearchForm_lookfor').focus()")?>
diff --git a/themes/bootstrap5/templates/search/results-scripts.phtml b/themes/bootstrap5/templates/search/results-scripts.phtml index a18328cd41e..798b679bffc 100644 --- a/themes/bootstrap5/templates/search/results-scripts.phtml +++ b/themes/bootstrap5/templates/search/results-scripts.phtml @@ -1,16 +1,16 @@ headScript()->appendFile('check_item_statuses.js'); -$this->headScript()->appendFile('check_save_statuses.js'); +$this->assetManager()->appendScriptFile('check_item_statuses.js'); +$this->assetManager()->appendScriptFile('check_save_statuses.js'); if ($this->displayVersions) { - $this->headScript()->appendFile('record_versions.js'); - $this->headScript()->appendFile('combined-search.js'); + $this->assetManager()->appendScriptFile('record_versions.js'); + $this->assetManager()->appendScriptFile('combined-search.js'); } // Load only if list view parameter is NOT full: if (($this->listViewOption ?? 'full') !== 'full') { - $this->headScript()->appendFile('record.js'); - $this->headScript()->appendFile('embedded_record.js'); + $this->assetManager()->appendScriptFile('record.js'); + $this->assetManager()->appendScriptFile('embedded_record.js'); } if ($this->jsResults ?? false) { - $this->headScript()->appendFile('search.js'); + $this->assetManager()->appendScriptFile('search.js'); } diff --git a/themes/bootstrap5/templates/search/results.phtml b/themes/bootstrap5/templates/search/results.phtml index b4acd00723e..acd66b8aa3c 100644 --- a/themes/bootstrap5/templates/search/results.phtml +++ b/themes/bootstrap5/templates/search/results.phtml @@ -48,7 +48,7 @@ ); $recommendations = $this->results->getRecommendations('side'); $multiFacetsSelection = $this->multiFacetsSelection ? 'true' : 'false'; - $this->headScript()->appendScript('var multiFacetsSelectionEnabled = ' . $multiFacetsSelection . ';'); + $this->assetManager()->appendScript('var multiFacetsSelectionEnabled = ' . $multiFacetsSelection . ';'); ?>

escapeHtml($headTitle)?>

diff --git a/themes/bootstrap5/templates/search/searchTabs.phtml b/themes/bootstrap5/templates/search/searchTabs.phtml index 5102f98fc12..8b8b3ee5d97 100644 --- a/themes/bootstrap5/templates/search/searchTabs.phtml +++ b/themes/bootstrap5/templates/search/searchTabs.phtml @@ -37,6 +37,6 @@ showCounts): ?> - headScript()->appendFile('resultcount.js'); ?> + assetManager()->appendScriptFile('resultcount.js'); ?> diff --git a/themes/bootstrap5/templates/search/searchbox.phtml b/themes/bootstrap5/templates/search/searchbox.phtml index d8e3b674a6c..f64cea46da2 100644 --- a/themes/bootstrap5/templates/search/searchbox.phtml +++ b/themes/bootstrap5/templates/search/searchbox.phtml @@ -110,10 +110,10 @@ headScript()->appendFile('vendor/js.cookie.js'); - $this->headScript()->appendFile('vendor/simple-keyboard/index.js'); - $this->headScript()->appendFile('vendor/simple-keyboard-layouts/index.js'); - $this->headLink()->appendStylesheet('vendor/simple-keyboard/index.css'); + $this->assetManager()->appendScriptFile('vendor/js.cookie.js'); + $this->assetManager()->appendScriptFile('vendor/simple-keyboard/index.js'); + $this->assetManager()->appendScriptFile('vendor/simple-keyboard-layouts/index.js'); + $this->assetManager()->appendStylesheet('vendor/simple-keyboard/index.css'); ?>