1313use FluidTYPO3 \Vhs \ViewHelpers \Asset \AssetInterface ;
1414use Psr \Http \Message \ServerRequestInterface ;
1515use Psr \Log \LoggerInterface ;
16+ use TYPO3 \CMS \Core \Cache \CacheManager ;
1617use TYPO3 \CMS \Core \Log \LogManager ;
18+ use TYPO3 \CMS \Core \Routing \PageArguments ;
1719use TYPO3 \CMS \Core \SingletonInterface ;
1820use TYPO3 \CMS \Core \Utility \ArrayUtility ;
1921use TYPO3 \CMS \Core \Utility \GeneralUtility ;
@@ -40,6 +42,11 @@ class AssetService implements SingletonInterface
4042 */
4143 protected $ configurationManager ;
4244
45+ /**
46+ * @var CacheManager
47+ */
48+ protected $ cacheManager ;
49+
4350 protected static bool $ typoScriptAssetsBuilt = false ;
4451 protected static ?array $ settingsCache = null ;
4552 protected static array $ cachedDependencies = [];
@@ -50,6 +57,11 @@ public function injectConfigurationManager(ConfigurationManagerInterface $config
5057 $ this ->configurationManager = $ configurationManager ;
5158 }
5259
60+ public function injectCacheManager (CacheManager $ cacheManager ): void
61+ {
62+ $ this ->cacheManager = $ cacheManager ;
63+ }
64+
5365 public function usePageCache (object $ caller , bool $ shouldUsePageCache ): bool
5466 {
5567 $ this ->buildAll ([], $ caller );
@@ -63,7 +75,10 @@ public function buildAll(array $parameters, object $caller, bool $cached = true,
6375 }
6476
6577 $ settings = $ this ->getSettings ();
66- $ buildTypoScriptAssets = (!static ::$ typoScriptAssetsBuilt && ($ cached || $ GLOBALS ['TSFE ' ]->no_cache ));
78+ $ buildTypoScriptAssets = (
79+ !static ::$ typoScriptAssetsBuilt
80+ && ($ cached || $ this ->readCacheDisabledInstructionFromContext ())
81+ );
6782 if ($ buildTypoScriptAssets && isset ($ settings ['asset ' ]) && is_array ($ settings ['asset ' ])) {
6883 foreach ($ settings ['asset ' ] as $ name => $ typoScriptAsset ) {
6984 if (!isset ($ GLOBALS ['VhsAssets ' ][$ name ]) && is_array ($ typoScriptAsset )) {
@@ -130,15 +145,53 @@ public function isAlreadyDefined(string $assetName): bool
130145 public function getSettings (): array
131146 {
132147 if (null === static ::$ settingsCache ) {
148+ static ::$ settingsCache = $ this ->getTypoScript ()['settings ' ] ?? [];
149+ }
150+ $ settings = (array ) static ::$ settingsCache ;
151+ return $ settings ;
152+ }
153+
154+ protected function getTypoScript (): array
155+ {
156+ $ cache = $ this ->cacheManager ->getCache ('vhs_main ' );
157+ $ pageUid = $ this ->readPageUidFromContext ();
158+ $ cacheId = 'vhs_asset_ts_ ' . $ pageUid ;
159+ $ cacheTag = 'pageId_ ' . $ pageUid ;
160+
161+ try {
133162 $ allTypoScript = $ this ->configurationManager ->getConfiguration (
134163 ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT
135164 );
136- static ::$ settingsCache = GeneralUtility::removeDotsFromTS (
137- $ allTypoScript ['plugin. ' ]['tx_vhs. ' ]['settings. ' ] ?? []
138- );
165+ $ typoScript = GeneralUtility::removeDotsFromTS ($ allTypoScript ['plugin. ' ]['tx_vhs. ' ] ?? []);
166+ $ cache ->set ($ cacheId , $ typoScript , [$ cacheTag ]);
167+ } catch (\RuntimeException $ exception ) {
168+ if ($ exception ->getCode () !== 1666513645 ) {
169+ // Re-throw, but only if the exception is not the specific "Setup array has not been initialized" one.
170+ throw $ exception ;
171+ }
172+
173+ // Note: this case will only ever be entered on TYPO3v13 and above. Earlier versions will consistently
174+ // produce the necessary TS array from ConfigurationManager - and will not raise the specified exception.
175+
176+ // We will only look in VHS's cache for a TS array if it wasn't already retrieved by ConfigurationManager.
177+ // This is for performance reasons: the TS array may be relatively large and the cache may be DB-based.
178+ // Whereas if the TS is already in ConfigurationManager, it costs nearly nothing to read. The TS is returned
179+ // even if it is empty.
180+ /** @var array|false $fromCache */
181+ $ fromCache = $ cache ->get ($ cacheId );
182+ if (is_array ($ fromCache )) {
183+ return $ fromCache ;
184+ }
185+
186+ // Graceful: it's better to return empty settings than either adding massive code chunks dealing with
187+ // custom TS reading or allowing an exception to be raised. Note that reaching this case means that the
188+ // PAGE was cached, but VHS's cache for the page is empty. This can be caused by TTL skew. The solution is
189+ // to flush all caches tagged with the page's ID, so the next request will correctly regenerate the entry.
190+ $ typoScript = [];
191+ $ this ->cacheManager ->flushCachesByTag ($ cacheTag );
139192 }
140- $ settings = ( array ) static :: $ settingsCache ;
141- return $ settings ;
193+
194+ return $ typoScript ;
142195 }
143196
144197 /**
@@ -274,8 +327,9 @@ protected function writeCachedMergedFileAndReturnTag(array $assets, string $type
274327 sort ($ keys );
275328 $ assetName = implode ('- ' , $ keys );
276329 unset($ keys );
277- if (isset ($ GLOBALS ['TSFE ' ]->tmpl ->setup ['plugin. ' ]['tx_vhs. ' ]['assets. ' ]['mergedAssetsUseHashedFilename ' ])) {
278- if ($ GLOBALS ['TSFE ' ]->tmpl ->setup ['plugin. ' ]['tx_vhs. ' ]['assets. ' ]['mergedAssetsUseHashedFilename ' ]) {
330+ $ typoScript = $ this ->getTypoScript ();
331+ if (isset ($ typoScript ['assets ' ]['mergedAssetsUseHashedFilename ' ])) {
332+ if ($ typoScript ['assets ' ]['mergedAssetsUseHashedFilename ' ]) {
279333 $ assetName = md5 ($ assetName );
280334 }
281335 }
@@ -284,8 +338,7 @@ protected function writeCachedMergedFileAndReturnTag(array $assets, string $type
284338 if (!file_exists ($ fileAbsolutePathAndFilename )
285339 || 0 === filemtime ($ fileAbsolutePathAndFilename )
286340 || isset ($ GLOBALS ['BE_USER ' ])
287- || ($ GLOBALS ['TSFE ' ]->no_cache ?? false )
288- || ($ GLOBALS ['TSFE ' ]->page ['no_cache ' ] ?? false )
341+ || $ this ->readCacheDisabledInstructionFromContext ()
289342 ) {
290343 foreach ($ assets as $ name => $ asset ) {
291344 $ assetSettings = $ this ->extractAssetSettings ($ asset );
@@ -348,6 +401,7 @@ protected function generateTagForAssetType(
348401 $ file = PathUtility::getAbsoluteWebPath ($ file );
349402 $ file = $ this ->prefixPath ($ file );
350403 }
404+ $ settings = $ this ->getTypoScript ();
351405 switch ($ type ) {
352406 case 'js ' :
353407 $ tagBuilder ->setTagName ('script ' );
@@ -359,7 +413,7 @@ protected function generateTagForAssetType(
359413 $ tagBuilder ->addAttribute ('src ' , (string ) $ file );
360414 }
361415 if (!empty ($ integrity )) {
362- if (!empty ($ GLOBALS [ ' TSFE ' ]-> tmpl -> setup [ ' plugin. ' ][ ' tx_vhs. ' ][ ' settings. ' ] ['prependPath ' ])) {
416+ if (!empty ($ settings ['prependPath ' ])) {
363417 $ tagBuilder ->addAttribute ('crossorigin ' , 'anonymous ' );
364418 }
365419 $ tagBuilder ->addAttribute ('integrity ' , $ integrity );
@@ -387,7 +441,7 @@ protected function generateTagForAssetType(
387441 $ tagBuilder ->addAttribute ('href ' , $ file );
388442 }
389443 if (!empty ($ integrity )) {
390- if (!empty ($ GLOBALS [ ' TSFE ' ]-> tmpl -> setup [ ' plugin. ' ][ ' tx_vhs. ' ][ ' settings. ' ] ['prependPath ' ])) {
444+ if (!empty ($ settings ['prependPath ' ])) {
391445 $ tagBuilder ->addAttribute ('crossorigin ' , 'anonymous ' );
392446 }
393447 $ tagBuilder ->addAttribute ('integrity ' , $ integrity );
@@ -742,19 +796,19 @@ protected function mergeArrays(array $array1, array $array2): array
742796
743797 protected function getFileIntegrity (string $ file ): ?string
744798 {
745- $ typoScript = $ GLOBALS [ ' TSFE ' ]-> tmpl -> setup [ ' plugin. ' ][ ' tx_vhs. ' ] ?? null ;
746- if (isset ($ typoScript ['assets. ' ]['tagsAddSubresourceIntegrity ' ])) {
799+ $ typoScript = $ this -> getTypoScript () ;
800+ if (isset ($ typoScript ['assets ' ]['tagsAddSubresourceIntegrity ' ])) {
747801 // Note: 3 predefined hashing strategies (the ones suggestes in the rfc sheet)
748- if (0 < $ typoScript ['assets. ' ]['tagsAddSubresourceIntegrity ' ]
749- && $ typoScript ['assets. ' ]['tagsAddSubresourceIntegrity ' ] < 4
802+ if (0 < $ typoScript ['assets ' ]['tagsAddSubresourceIntegrity ' ]
803+ && $ typoScript ['assets ' ]['tagsAddSubresourceIntegrity ' ] < 4
750804 ) {
751805 if (!file_exists ($ file )) {
752806 return null ;
753807 }
754808
755809 $ integrity = null ;
756810 $ integrityMethod = ['sha256 ' ,'sha384 ' ,'sha512 ' ][
757- $ typoScript ['assets. ' ]['tagsAddSubresourceIntegrity ' ] - 1
811+ $ typoScript ['assets ' ]['tagsAddSubresourceIntegrity ' ] - 1
758812 ];
759813 $ integrityFile = sprintf (
760814 $ this ->getTempPath () . 'vhs-assets-%s.%s ' ,
@@ -799,6 +853,16 @@ protected function resolveAbsolutePathForFile(string $filename): string
799853 return GeneralUtility::getFileAbsFileName ($ filename );
800854 }
801855
856+ protected function readPageUidFromContext (): int
857+ {
858+ /** @var ServerRequestInterface $serverRequest */
859+ $ serverRequest = $ GLOBALS ['TYPO3_REQUEST ' ];
860+
861+ /** @var PageArguments $pageArguments */
862+ $ pageArguments = $ serverRequest ->getAttribute ('routing ' );
863+ return $ pageArguments ->getPageId ();
864+ }
865+
802866 protected function readCacheDisabledInstructionFromContext (): bool
803867 {
804868 $ hasDisabledInstructionInRequest = false ;
0 commit comments