diff --git a/composer.json b/composer.json index 29aa3705..d5ef3a45 100644 --- a/composer.json +++ b/composer.json @@ -1,4 +1,6 @@ { + "name": "hathitrust/catalog", + "description": "HathiTrust Catalog application", "require": { "geoip2/geoip2": "2.13.*", "pear/pear-core-minimal": "1.10.*", @@ -6,6 +8,6 @@ "pear/http_request2": "2.7.*", "pear/pager": "2.5.*", "phpunit/phpunit": "9.6.*", - "smarty/smarty": "4.5.5" + "smarty/smarty": "^5.0" } } diff --git a/composer.lock b/composer.lock index 22a6c60f..b9a678a3 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": "de1097bf96cb807c7cd157a939574026", + "content-hash": "377d61821fbfbe921d2f4fd8ea24cdda", "packages": [ { "name": "composer/ca-bundle", @@ -2393,35 +2393,39 @@ }, { "name": "smarty/smarty", - "version": "v4.5.5", + "version": "v5.8.0", "source": { "type": "git", "url": "https://github.com/smarty-php/smarty.git", - "reference": "c4851c12e34ff80073ddeb7d98b059d57dea9de2" + "reference": "78d259d3b971c59a0cd719c270cc5cbb740c36a7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/smarty-php/smarty/zipball/c4851c12e34ff80073ddeb7d98b059d57dea9de2", - "reference": "c4851c12e34ff80073ddeb7d98b059d57dea9de2", + "url": "https://api.github.com/repos/smarty-php/smarty/zipball/78d259d3b971c59a0cd719c270cc5cbb740c36a7", + "reference": "78d259d3b971c59a0cd719c270cc5cbb740c36a7", "shasum": "" }, "require": { - "php": "^7.1 || ^8.0" + "php": "^7.2 || ^8.0", + "symfony/polyfill-mbstring": "^1.27" }, "require-dev": { "phpunit/phpunit": "^8.5 || ^7.5", - "smarty/smarty-lexer": "^3.1" + "smarty/smarty-lexer": "^4.0.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.0.x-dev" + "dev-master": "5.0.x-dev" } }, "autoload": { - "classmap": [ - "libs/" - ] + "files": [ + "src/functions.php" + ], + "psr-4": { + "Smarty\\": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2453,9 +2457,100 @@ "support": { "forum": "https://github.com/smarty-php/smarty/discussions", "issues": "https://github.com/smarty-php/smarty/issues", - "source": "https://github.com/smarty-php/smarty/tree/v4.5.5" + "source": "https://github.com/smarty-php/smarty/tree/v5.8.0" + }, + "funding": [ + { + "url": "https://github.com/wisskid", + "type": "github" + } + ], + "time": "2026-02-15T14:27:15+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.33.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" }, - "time": "2024-11-21T22:06:22+00:00" + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.33.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-23T08:48:59+00:00" }, { "name": "theseer/tokenizer", diff --git a/docker/nginx-default.conf b/docker/nginx-default.conf index 1fc9cf68..5ffe82c4 100644 --- a/docker/nginx-default.conf +++ b/docker/nginx-default.conf @@ -3,9 +3,9 @@ server { server_name _; root /app; - rewrite_log on; + rewrite_log off; index index.php; - error_log /dev/stdout debug; + error_log /dev/stdout notice; # Translated from .htaccess location @rewrite { diff --git a/interface/plugins/function.css_link.php b/interface/plugins/function.css_link.php index 5fc549be..0b5b3297 100644 --- a/interface/plugins/function.css_link.php +++ b/interface/plugins/function.css_link.php @@ -14,11 +14,11 @@ function smarty_function_css_link($params, $template) $root = dirname($_SERVER['DOCUMENT_ROOT']); $filename = FALSE; if ( is_dir("$root/common") ) { - $filename = "$root/${base_filename}"; + $filename = "$root/{$base_filename}"; } elseif ( is_dir("$root/../babel/common") ) { - $filename = "$root/../babel/${base_filename}"; + $filename = "$root/../babel/{$base_filename}"; } $modtime = ( $filename === FALSE ) ? time() : filemtime($filename); - return ""; + return ""; } ?> \ No newline at end of file diff --git a/interface/plugins/function.js_link.php b/interface/plugins/function.js_link.php index 5b15083d..268472b3 100644 --- a/interface/plugins/function.js_link.php +++ b/interface/plugins/function.js_link.php @@ -14,12 +14,12 @@ function smarty_function_js_link($params, $template) $root = dirname($_SERVER['DOCUMENT_ROOT']); $filename = FALSE; if ( is_dir("$root/common") ) { - $filename = "$root/${base_filename}"; + $filename = "$root/{$base_filename}"; } elseif ( is_dir("$root/../babel/common") ) { - $filename = "$root/../babel/${base_filename}"; + $filename = "$root/../babel/{$base_filename}"; } $modtime = ( $filename === FALSE ) ? time() : filemtime($filename); - return ""; + return ""; } ?> diff --git a/interface/themes/firebird/Search/advanced.tpl b/interface/themes/firebird/Search/advanced.tpl index 3d3c9770..2fa66bf5 100644 --- a/interface/themes/firebird/Search/advanced.tpl +++ b/interface/themes/firebird/Search/advanced.tpl @@ -14,9 +14,9 @@
diff --git a/interface/themes/firebird/Search/list-list.tpl b/interface/themes/firebird/Search/list-list.tpl index fce9cd4f..849b1f6f 100644 --- a/interface/themes/firebird/Search/list-list.tpl +++ b/interface/themes/firebird/Search/list-list.tpl @@ -12,8 +12,6 @@ {/if}
- {* {$ld.handle|@var_dump} - {$record.title|@var_dump} *}
{if $ld.handle} @@ -68,8 +66,7 @@
{assign var="dfields" value=$ru->displayable_ht_fields($record.marc)} - {* {$dfields|@var_dump} *} - {if false && $dfields|@count gt 1} + {if false && $dfields|count gt 1}

Use the Catalog Record to view multiple volumes

@@ -78,7 +75,7 @@
Catalog Record - {if $dfields|@count eq 1 && isset($ld) && is_array($ld)} + {if $dfields|count eq 1 && isset($ld) && is_array($ld)} {if ( $ld.is_resource_sharing ) } Registered Access {elseif ( $ld.role_name !== 'resourceSharing' && ! $ld.is_fullview && ( $ld.has_activated_role ) ) } @@ -90,7 +87,7 @@ {else} Limited (search only) {/if} - {elseif $dfields|@count gt 1} + {elseif $dfields|count gt 1} Multiple Items {/if}
diff --git a/interface/themes/firebird/Search/list.sidebar.tpl b/interface/themes/firebird/Search/list.sidebar.tpl index 5a443704..c257f570 100644 --- a/interface/themes/firebird/Search/list.sidebar.tpl +++ b/interface/themes/firebird/Search/list.sidebar.tpl @@ -110,10 +110,10 @@ {/foreach}
- {if $counts.$cluster|@count gt 6} + {if $counts.$cluster|count gt 6} diff --git a/static/api/templates/volumes/oclchtml.tpl b/static/api/templates/volumes/oclchtml.tpl index 149dd2b1..e9bd225c 100644 --- a/static/api/templates/volumes/oclchtml.tpl +++ b/static/api/templates/volumes/oclchtml.tpl @@ -3,7 +3,10 @@ PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> - {$doc.titles[0]} (OCLC {', '|implode:$doc.oclcs }) + + {$doc.titles[0]|default:''} {if !empty($doc.oclcs|default:[])} (OCLC {$doc.oclcs|join:', '}){/if} + + diff --git a/static/api/volumes.php b/static/api/volumes.php index ccae61c5..2d97e3a6 100644 --- a/static/api/volumes.php +++ b/static/api/volumes.php @@ -16,6 +16,7 @@ # require_once 'Apache/Solr/Service.php'; require_once 'vendor/autoload.php'; +use Smarty\Smarty; require_once 'sys/SolrConnection.php'; require_once 'services/Record/RecordUtils.php'; require_once 'sys/Normalize.php'; @@ -629,4 +630,3 @@ function lccnnormalize($val) { - diff --git a/sys/Interface.php b/sys/Interface.php index ee426403..08ff2edb 100755 --- a/sys/Interface.php +++ b/sys/Interface.php @@ -18,10 +18,9 @@ * */ -# Smarty 4.5.5 uses classmap autoload with bare Smarty class require_once 'vendor/autoload.php'; -# use Smarty\Smarty; +use Smarty\Smarty; # Smarty Extension class class UInterface extends Smarty @@ -33,6 +32,7 @@ function __construct() $local = $configArray['Site']['local']; $theme = $configArray['Site']['theme']; + # set a single directory where the config file are stored. $this->setTemplateDir("$local/interface/themes/$theme"); # Set up the space for compiled files @@ -44,10 +44,13 @@ function __construct() chmod($comp, 0777); } + // set another path to store compiled templates $this->setCompileDir($comp); + + // set another path to store caches of templates to speed up the loading of templates $this->setCacheDir("$local/interface/cache"); - # Add custom plugin directory - $this->addPluginsDir("$local/interface/plugins"); + # Register legacy plugin files from the custom directory + $this->registerLegacyPlugins("$local/interface/plugins"); $this->setCaching(Smarty::CACHING_OFF); $this->setDebugging(false); $this->setCompileCheck(Smarty::COMPILECHECK_ON); @@ -55,10 +58,20 @@ function __construct() unset($local); - // Register custom functions (Smarty 3 method) + // Register custom functions + // These are used in the templates as {translate text="Back to Record"} to output the translated text + // registerPlugin documentation: https://www.smarty.net/docs/en/api.register.plugin.tpl $this->registerPlugin('function', 'translate', 'translate'); $this->registerPlugin('function', 'char', 'char'); + // Register PHP functions that used to be invoked via Smarty's @modifier syntax. + // These are used in the templates as {$var|json_encode="value"} to output the JSON-encoded value of $var, + // or {$array|count} to output the count of items in $array. + // modifier are used to transform the output of a variable, so they are registered as 'modifier' plugins. + $this->registerPlugin('modifier', 'json_encode', 'json_encode'); + $this->registerPlugin('modifier', 'count', 'count'); + + $this->assign('site', $configArray['Site']); $this->assign('path', $configArray['Site']['path']); $this->assign('url', $configArray['Site']['url']); @@ -105,6 +118,42 @@ function setPageTitle($title) { $this->assign('pageTitle', $title); } + + /** + * Registers legacy plugins from a specified directory. + * + * This method looks for PHP files in the given directory that match the naming convention + * for Smarty plugins (function.{name}.php or modifier.{name}.php). It then includes these files + * and registers the corresponding functions as Smarty plugins. + * + * Smarty 5 requires to register each plugin or load an extension instead of pointing it at a directory. + * + * + * @param string $directory The directory to search for legacy plugin files. + */ + + private function registerLegacyPlugins(string $directory): void + { + if (!is_dir($directory)) { + return; + } + + //foreach (glob(rtrim($directory, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . '*.php') as $file) { + foreach (glob($directory . '/*.php') as $file) { + require_once $file; + $basename = basename($file); + if (!preg_match('/^(function|modifier)\\.([^\\.]+)\\.php$/', $basename, $matches)) { + continue; + } + $type = $matches[1]; + $name = $matches[2]; + $callback = 'smarty_' . $type . '_' . $name; + if (!function_exists($callback)) { + continue; + } + $this->registerPlugin($type, $name, $callback); + } + } } function translate($params) diff --git a/test/SmartyTemplateTest.php b/test/SmartyTemplateTest.php index d01b542f..8bb95d33 100644 --- a/test/SmartyTemplateTest.php +++ b/test/SmartyTemplateTest.php @@ -2,6 +2,7 @@ declare(strict_types=1); use PHPUnit\Framework\TestCase; +use Smarty\Smarty; class VolumesApiTest extends TestCase { @@ -15,10 +16,10 @@ public function test_oclcscrape_smarty_template_rendering(): void // Setup environment $_SERVER['HTTP_HOST'] = 'localhost'; - // Verify Smarty 4.x class is available (no namespace) - $this->assertTrue(class_exists('Smarty'), 'Smarty class should be available without namespace'); + // Verify the namespaced Smarty class loads + $this->assertTrue(class_exists(Smarty::class), 'Smarty\\Smarty class should be available'); - // Create Smarty instance using Smarty 4.x syntax + // Create Smarty instance using the namespaced class $interface = new Smarty(); $interface->setCompileDir(__DIR__ . '/../interface/compile'); $interface->setTemplateDir(__DIR__ . '/../static/api/templates'); @@ -47,4 +48,4 @@ public function test_oclcscrape_smarty_template_rendering(): void $this->assertStringContainsString('https://localhost:8080/item', $output); } } -?> \ No newline at end of file +?>