summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLester Caine <lester@lsces.co.uk>2026-06-06 19:15:57 +0100
committerLester Caine <lester@lsces.co.uk>2026-06-06 19:15:57 +0100
commit28332b74759d85135fa4242f75824349ce766f83 (patch)
tree9ec90c5f54d2a9c4816fdb0330b48e1d2eadc7d3
parente245a4e9778354b9e75f4ef652ee351e45ca9f7f (diff)
downloadthemes-28332b74759d85135fa4242f75824349ce766f83.tar.gz
themes-28332b74759d85135fa4242f75824349ce766f83.tar.bz2
themes-28332b74759d85135fa4242f75824349ce766f83.zip
themes: fix APCu-poisoning of mRawFiles causing duplicate JS/CSS headers
cleanAuxFiles() was converting mRawFiles filesystem paths to URL?filemtime strings in-place. Because mRawFiles is serialised into APCu, the URL form was persisted across requests. On the next request, isAuxFile(path) did in_array(path, [URL?timestamp]) — always a miss — so the file was added again at the next free position. Each request added another copy, growing the header indefinitely. Fix: cleanAuxFiles() now writes converted URLs into a new mRawUrls property (not in __sleep(), so never serialised) and leaves mRawFiles untouched as stable filesystem paths. html_head_inc.tpl reads mRawUrls for rendering. Also fixes a pre-existing strpos('?', $file) arg-order bug — the needle and haystack were swapped, so the ?/& separator was always wrong. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
-rwxr-xr-xincludes/classes/BitThemes.php38
-rwxr-xr-xtemplates/html_head_inc.tpl8
2 files changed, 30 insertions, 16 deletions
diff --git a/includes/classes/BitThemes.php b/includes/classes/BitThemes.php
index eb9c34d..1e21fc5 100755
--- a/includes/classes/BitThemes.php
+++ b/includes/classes/BitThemes.php
@@ -38,11 +38,19 @@ class BitThemes extends BitSingleton {
'css' => [],
];
- // Raw Javascript and Css Files
+ // Raw Javascript and Css Files — kept as filesystem paths for stable deduplication.
+ // Never mutated to URL form; see mRawUrls.
public $mRawFiles = [
'js' => [],
'css' => [],
];
+
+ // Per-request URL form of mRawFiles, built by cleanAuxFiles() for template rendering.
+ // Not serialised — rebuilt each request.
+ public $mRawUrls = [
+ 'js' => [],
+ 'css' => [],
+ ];
public $mUnloadFiles = [];
// Display Mode
@@ -1729,24 +1737,30 @@ class BitThemes extends BitSingleton {
}
}
- // convert full file path to URL in mRawFiles hash
+ // Build per-request mRawUrls — convert filesystem paths to URL?filemtime for rendering.
+ // mRawFiles is kept as stable paths so isAuxFile() deduplication survives APCu round-trips.
+ $this->mRawUrls[$pType] = [];
if( !empty( $this->mRawFiles[$pType] )) {
foreach( $this->mRawFiles[$pType] as $pos => $file ) {
if ( KernelTools::is_windows() ) {
- $file = str_replace( '\\', '/', $file );
- // Put first forward slash back
- $file = substr_replace( $file, '\\', 2, 1 );
- $winBitRootPath = str_replace( '\\', '/', BIT_ROOT_PATH );
- // Put first forward slash back
- $winBitRootPath = substr_replace($winBitRootPath, '\\', 2, 1 );
- if ( strpos( $file, $winBitRootPath ) !== false ) {
- $this->mRawFiles[$pType][$pos] = BIT_ROOT_URL.substr( $file, strlen( $winBitRootPath ));
+ $winFile = str_replace( '\\', '/', $file );
+ $winFile = substr_replace( $winFile, '\\', 2, 1 );
+ $winBitRootPath = str_replace( '\\', '/', BIT_ROOT_PATH );
+ $winBitRootPath = substr_replace( $winBitRootPath, '\\', 2, 1 );
+ if ( strpos( $winFile, $winBitRootPath ) !== false ) {
+ $this->mRawUrls[$pType][$pos] = BIT_ROOT_URL.substr( $winFile, strlen( $winBitRootPath ));
+ } else {
+ $this->mRawUrls[$pType][$pos] = $file;
}
} else if ( strpos( $file, BIT_ROOT_PATH ) !== false ) {
- $this->mRawFiles[$pType][$pos] = BIT_ROOT_URL.substr( $file, strlen( BIT_ROOT_PATH ));
+ $url = BIT_ROOT_URL.substr( $file, strlen( BIT_ROOT_PATH ));
if( file_exists( $file ) && ($cacheTime = filemtime( $file )) ) {
- $this->mRawFiles[$pType][$pos] .= (strpos('?',$file) ? '&' : '?' ).$cacheTime;
+ $url .= ( strpos( $url, '?' ) !== false ? '&' : '?' ).$cacheTime;
}
+ $this->mRawUrls[$pType][$pos] = $url;
+ } else {
+ // Already a URL (e.g. CDN) — use as-is
+ $this->mRawUrls[$pType][$pos] = $file;
}
}
}
diff --git a/templates/html_head_inc.tpl b/templates/html_head_inc.tpl
index 0c2c1cd..bb93d88 100755
--- a/templates/html_head_inc.tpl
+++ b/templates/html_head_inc.tpl
@@ -1,11 +1,11 @@
{strip}
-{if !empty($gBitThemes->mRawFiles.css)}
- {foreach from=$gBitThemes->mRawFiles.css item=cssFile}
+{if !empty($gBitThemes->mRawUrls.css)}
+ {foreach from=$gBitThemes->mRawUrls.css item=cssFile}
<link rel="stylesheet" title="{$style|default:'css'}" nonce="{$cspNonce}" type="text/css" href="{$cssFile}" media="all" />
{/foreach}
{/if}
-{if !empty($gBitThemes->mRawFiles.js)}
- {foreach from=$gBitThemes->mRawFiles.js item=jsFile}
+{if !empty($gBitThemes->mRawUrls.js)}
+ {foreach from=$gBitThemes->mRawUrls.js item=jsFile}
<script nonce="{$cspNonce}" src="{$jsFile}"></script>
{/foreach}
{/if}