Skip to content

Commit e99a017

Browse files
committed
fix(theming): read cachebuster via IAppConfig in ImageManager
The cachebuster is written via IAppConfig::setAppValueInt() but was read back via the deprecated IConfig::getAppValue(). Within the same request the IConfig layer may return a stale cached value, so the URL returned by getImageUrl() after an upload could carry the old buster value - causing the browser to serve the old cached thumbnail on a second consecutive upload without a page reload. Fix by injecting IAppConfig (app-scoped) into ImageManager and reading the cachebuster through the same API that writes it. Signed-off-by: Anna Larch <anna@nextcloud.com> AI-Assisted-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 7954c0e commit e99a017

3 files changed

Lines changed: 51 additions & 33 deletions

File tree

apps/theming/lib/ImageManager.php

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
namespace OCA\Theming;
88

99
use OCA\Theming\AppInfo\Application;
10+
use OCA\Theming\ConfigLexicon;
1011
use OCA\Theming\Service\BackgroundService;
12+
use OCP\AppFramework\Services\IAppConfig;
1113
use OCP\Files\IAppData;
1214
use OCP\Files\NotFoundException;
1315
use OCP\Files\NotPermittedException;
@@ -30,6 +32,7 @@ public function __construct(
3032
private LoggerInterface $logger,
3133
private ITempManager $tempManager,
3234
private BackgroundService $backgroundService,
35+
private IAppConfig $appConfig,
3336
) {
3437
}
3538

@@ -40,7 +43,7 @@ public function __construct(
4043
* @return string the image url
4144
*/
4245
public function getImageUrl(string $key): string {
43-
$cacheBusterCounter = $this->config->getAppValue(Application::APP_ID, 'cachebuster', '0');
46+
$cacheBusterCounter = (string)$this->appConfig->getAppValueInt(ConfigLexicon::CACHE_BUSTER);
4447
if ($this->hasImage($key)) {
4548
return $this->urlGenerator->linkToRoute('theming.Theming.getImage', [ 'key' => $key ]) . '?v=' . $cacheBusterCounter;
4649
} elseif ($key === 'backgroundDark' && $this->hasImage('background')) {
@@ -139,7 +142,7 @@ public function getCustomImages(): array {
139142
* @throws NotPermittedException
140143
*/
141144
public function getCacheFolder(): ISimpleFolder {
142-
$cacheBusterValue = $this->config->getAppValue('theming', 'cachebuster', '0');
145+
$cacheBusterValue = (string)$this->appConfig->getAppValueInt(ConfigLexicon::CACHE_BUSTER);
143146
try {
144147
$folder = $this->getRootFolder()->getFolder($cacheBusterValue);
145148
} catch (NotFoundException $e) {

apps/theming/tests/ImageManagerTest.php

Lines changed: 41 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
use OCA\Theming\ImageManager;
1111
use OCA\Theming\Service\BackgroundService;
12+
use OCP\AppFramework\Services\IAppConfig;
1213
use OCP\Files\IAppData;
1314
use OCP\Files\NotFoundException;
1415
use OCP\Files\SimpleFS\ISimpleFile;
@@ -29,6 +30,7 @@ class ImageManagerTest extends TestCase {
2930
private LoggerInterface&MockObject $logger;
3031
private ITempManager&MockObject $tempManager;
3132
private ISimpleFolder&MockObject $rootFolder;
33+
private IAppConfig&MockObject $appConfig;
3234
protected ImageManager $imageManager;
3335

3436
protected function setUp(): void {
@@ -41,6 +43,7 @@ protected function setUp(): void {
4143
$this->tempManager = $this->createMock(ITempManager::class);
4244
$this->rootFolder = $this->createMock(ISimpleFolder::class);
4345
$backgroundService = $this->createMock(BackgroundService::class);
46+
$this->appConfig = $this->createMock(IAppConfig::class);
4447
$this->imageManager = new ImageManager(
4548
$this->config,
4649
$this->appData,
@@ -49,6 +52,7 @@ protected function setUp(): void {
4952
$this->logger,
5053
$this->tempManager,
5154
$backgroundService,
55+
$this->appConfig,
5256
);
5357
$this->appData
5458
->expects($this->any())
@@ -96,25 +100,29 @@ public function mockGetImage($key, $file) {
96100

97101
public function testGetImageUrl(): void {
98102
$this->checkImagick();
99-
$this->config->expects($this->exactly(2))
103+
$this->appConfig->expects($this->once())
104+
->method('getAppValueInt')
105+
->with('cachebuster')
106+
->willReturn(0);
107+
$this->config->expects($this->once())
100108
->method('getAppValue')
101-
->willReturnMap([
102-
['theming', 'cachebuster', '0', '0'],
103-
['theming', 'logoMime', '', '0'],
104-
]);
109+
->with('theming', 'logoMime', '')
110+
->willReturn('image/png');
105111
$this->urlGenerator->expects($this->once())
106112
->method('linkToRoute')
107113
->willReturn('url-to-image');
108114
$this->assertEquals('url-to-image?v=0', $this->imageManager->getImageUrl('logo', false));
109115
}
110116

111117
public function testGetImageUrlDefault(): void {
112-
$this->config->expects($this->exactly(2))
118+
$this->appConfig->expects($this->once())
119+
->method('getAppValueInt')
120+
->with('cachebuster')
121+
->willReturn(0);
122+
$this->config->expects($this->once())
113123
->method('getAppValue')
114-
->willReturnMap([
115-
['theming', 'cachebuster', '0', '0'],
116-
['theming', 'logoMime', '', ''],
117-
]);
124+
->with('theming', 'logoMime', '')
125+
->willReturn('');
118126
$this->urlGenerator->expects($this->once())
119127
->method('imagePath')
120128
->with('core', 'logo/logo.png')
@@ -124,12 +132,14 @@ public function testGetImageUrlDefault(): void {
124132

125133
public function testGetImageUrlAbsolute(): void {
126134
$this->checkImagick();
127-
$this->config->expects($this->exactly(2))
135+
$this->appConfig->expects($this->once())
136+
->method('getAppValueInt')
137+
->with('cachebuster')
138+
->willReturn(0);
139+
$this->config->expects($this->once())
128140
->method('getAppValue')
129-
->willReturnMap([
130-
['theming', 'cachebuster', '0', '0'],
131-
['theming', 'logoMime', '', ''],
132-
]);
141+
->with('theming', 'logoMime', '')
142+
->willReturn('');
133143
$this->urlGenerator->expects($this->any())
134144
->method('getAbsoluteUrl')
135145
->willReturn('url-to-image-absolute?v=0');
@@ -212,10 +222,10 @@ public function testGetImageUnset(): void {
212222

213223
public function testGetCacheFolder(): void {
214224
$folder = $this->createMock(ISimpleFolder::class);
215-
$this->config->expects($this->once())
216-
->method('getAppValue')
217-
->with('theming', 'cachebuster', '0')
218-
->willReturn('0');
225+
$this->appConfig->expects($this->once())
226+
->method('getAppValueInt')
227+
->with('cachebuster')
228+
->willReturn(0);
219229
$this->rootFolder->expects($this->once())
220230
->method('getFolder')
221231
->with('0')
@@ -224,10 +234,10 @@ public function testGetCacheFolder(): void {
224234
}
225235
public function testGetCacheFolderCreate(): void {
226236
$folder = $this->createMock(ISimpleFolder::class);
227-
$this->config->expects($this->exactly(2))
228-
->method('getAppValue')
229-
->with('theming', 'cachebuster', '0')
230-
->willReturn('0');
237+
$this->appConfig->expects($this->exactly(2))
238+
->method('getAppValueInt')
239+
->with('cachebuster')
240+
->willReturn(0);
231241
$this->rootFolder->expects($this->exactly(2))
232242
->method('getFolder')
233243
->with('0')
@@ -303,10 +313,10 @@ public function testSetCachedImageCreate(): void {
303313

304314
private function setupCacheFolder() {
305315
$folder = $this->createMock(ISimpleFolder::class);
306-
$this->config->expects($this->once())
307-
->method('getAppValue')
308-
->with('theming', 'cachebuster', '0')
309-
->willReturn('0');
316+
$this->appConfig->expects($this->once())
317+
->method('getAppValueInt')
318+
->with('cachebuster')
319+
->willReturn(0);
310320
$this->rootFolder->expects($this->once())
311321
->method('getFolder')
312322
->with('0')
@@ -328,10 +338,10 @@ public function testCleanup(): void {
328338
$folders[0]->expects($this->once())->method('delete');
329339
$folders[1]->expects($this->once())->method('delete');
330340
$folders[2]->expects($this->never())->method('delete');
331-
$this->config->expects($this->once())
332-
->method('getAppValue')
333-
->with('theming', 'cachebuster', '0')
334-
->willReturn('2');
341+
$this->appConfig->expects($this->once())
342+
->method('getAppValueInt')
343+
->with('cachebuster')
344+
->willReturn(2);
335345
$this->rootFolder->expects($this->once())
336346
->method('getDirectoryListing')
337347
->willReturn($folders);

lib/private/Server.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1078,6 +1078,11 @@ public function __construct(
10781078
$c->get(LoggerInterface::class),
10791079
$c->get(ITempManager::class),
10801080
$backgroundService,
1081+
new AppConfig(
1082+
$c->get(IConfig::class),
1083+
$c->get(IAppConfig::class),
1084+
'theming',
1085+
),
10811086
);
10821087
return new ThemingDefaults(
10831088
new AppConfig(

0 commit comments

Comments
 (0)