Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 51 additions & 32 deletions Classes/Cache/Backend/ReverseProxyCacheBackend.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@

use B13\Proxycachemanager\Provider\ProxyProviderInterface;
use Psr\Log\LoggerAwareTrait;
use TYPO3\CMS\Core\Cache\Backend\TransientBackendInterface;
use TYPO3\CMS\Core\Cache\Backend\Typo3DatabaseBackend;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility;
Expand All @@ -29,7 +28,7 @@
* when removing or flushing, additionally does a HTTP Request
* of course "setting" works naturally in am already working reverse proxy environment.
*/
class ReverseProxyCacheBackend extends Typo3DatabaseBackend implements TransientBackendInterface
class ReverseProxyCacheBackend extends Typo3DatabaseBackend
{
use LoggerAwareTrait;
protected ProxyProviderInterface $reverseProxyProvider;
Expand All @@ -39,30 +38,11 @@ public function setReverseProxyProvider(ProxyProviderInterface $reverseProxyProv
$this->reverseProxyProvider = $reverseProxyProvider;
}

/**
* Removes all cache entries matching the specified identifier.
* Usually this only affects one entry.
*
* Please note: As remove() is called when using set() in the parent method,
* it would also flush the cache when the FE is accessed, resulting in a lot of
* cache entries. This should be avoided, for this reason, we do not call the
* provider anymore. Ideally we should not invalidate but rather push this actively
* to the proxy in the future.
*
* @param string $entryIdentifier Specifies the cache entry to remove
*
* @return bool TRUE if (at least) an entry could be removed or FALSE if no entry was found
*/
public function remove($entryIdentifier)
{
return parent::remove($entryIdentifier);
}

/**
* Removes all cache entries of this cache.
* Also let the proxy provider know to clear everything as well.
*/
public function flush()
public function flush(): void
{
// make the HTTP Purge call
if ($this->reverseProxyProvider->isActive()) {
Expand All @@ -77,13 +57,13 @@ public function flush()
*
* @param string $tag The tag the entries must have
*/
public function flushByTag($tag)
public function flushByTag($tag): void
{
if ($this->reverseProxyProvider->isActive()) {
$identifiers = $this->findIdentifiersByTag($tag);
foreach ($identifiers as $entryIdentifier) {
$url = $this->get($entryIdentifier);
if ($url) {
$url = $this->resolveCachedUrl($entryIdentifier);
if ($url !== null) {
$this->reverseProxyProvider->flushCacheForUrls([$url]);
}
}
Expand All @@ -97,7 +77,7 @@ public function flushByTag($tag)
*
* @param string[] $tags
*/
public function flushByTags(array $tags)
public function flushByTags(array $tags): void
{
if ($this->reverseProxyProvider->isActive()) {
$identifiers = [];
Expand All @@ -108,11 +88,16 @@ public function flushByTags(array $tags)

$urls = [];
foreach ($identifiers as $entryIdentifier) {
$urls[] = $this->get($entryIdentifier);
$url = $this->resolveCachedUrl($entryIdentifier);
if ($url !== null) {
$urls[] = $url;
}
}
$urls = array_unique($urls);

$this->reverseProxyProvider->flushCacheForUrls($urls);
if ($urls !== []) {
$this->reverseProxyProvider->flushCacheForUrls($urls);
}
}

try {
Expand All @@ -129,10 +114,44 @@ protected function getAllCachedUrls(): array
{
$urls = [];
$conn = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable($this->cacheTable);
$stmt = $conn->select(['content'], $this->cacheTable);
while ($url = $stmt->fetchOne()) {
$urls[] = $url;
$rows = $conn->select(['identifier', 'content'], $this->cacheTable)->fetchAllAssociative();
foreach ($rows as $row) {
$url = $this->resolveCachedUrl((string)$row['identifier'], $row['content']);
if ($url !== null) {
$urls[] = $url;
}
}
return $urls;
return array_values(array_unique($urls));
}

/**
* Resolves the plain page URL stored for a cache entry.
*
* Since the backend no longer implements TransientBackendInterface (the v14
* contract is incompatible with Typo3DatabaseBackend), the configured
* VariableFrontend serializes — and on v13.4+ HMAC-signs — every entry before
* it is written to the database. Reads must therefore go through the frontend
* to obtain the original URL again; a raw column read would only yield the
* serialized blob.
*
* Rows written by older versions (TransientBackendInterface era) still hold the
* plain URL and fail deserialization, so we fall back to the raw value for those.
*/
protected function resolveCachedUrl(string $entryIdentifier, ?string $rawContent = null): ?string
{
$url = $this->cache->get($entryIdentifier);
if (is_string($url) && $url !== '') {
return $url;
}

// Legacy fallback: rows from < 5.0.0 stored the plain URL directly.
if ($rawContent === null) {
$rawContent = (string)$this->get($entryIdentifier);
}
if (preg_match('#^https?://#', $rawContent)) {
return $rawContent;
}

return null;
}
}
45 changes: 5 additions & 40 deletions Classes/Controller/ManagementController.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@
use TYPO3\CMS\Backend\Template\ModuleTemplateFactory;
use TYPO3\CMS\Core\Cache\CacheManager;
use TYPO3\CMS\Core\Http\RedirectResponse;
use TYPO3\CMS\Core\Information\Typo3Version;
use TYPO3\CMS\Core\Messaging\AbstractMessage;
use TYPO3\CMS\Core\Type\ContextualFeedbackSeverity;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
Expand All @@ -41,78 +39,45 @@ public function __construct(protected ModuleTemplateFactory $moduleTemplateFacto
public function indexAction(): ResponseInterface
{
$moduleTemplate = $this->moduleTemplateFactory->create($this->request);
if ((GeneralUtility::makeInstance(Typo3Version::class))->getMajorVersion() === 11) {
$this->view->setTemplateRootPaths(['EXT:proxycachemanager/Resources/Private/TemplatesV11/']);
$moduleTemplate->setContent($this->view->render());
$response = $this->htmlResponse($moduleTemplate->renderContent());
} else {
$response = $moduleTemplate->renderResponse('Management/Index');
}
return $response;
return $moduleTemplate->renderResponse('Management/Index');
}

/**
* @param string $tag
*/
public function clearTagAction(string $tag): ResponseInterface
{
GeneralUtility::makeInstance(CacheManager::class)->flushCachesByTags([htmlspecialchars($tag)]);
if ((GeneralUtility::makeInstance(Typo3Version::class))->getMajorVersion() === 11) {
$severity = AbstractMessage::OK;
} else {
$severity = ContextualFeedbackSeverity::OK;
}
$this->addFlashMessage(
'Successfully purged cache tag "' . htmlspecialchars($tag) . '".',
'Cache flushed',
$severity
ContextualFeedbackSeverity::OK
);
return new RedirectResponse($this->uriBuilder->reset()->uriFor('index'));
}

/**
* @param string $url
*/
public function purgeUrlAction(string $url): ResponseInterface
{
if (empty($url)) {
if ((GeneralUtility::makeInstance(Typo3Version::class))->getMajorVersion() === 11) {
$severity = AbstractMessage::WARNING;
} else {
$severity = ContextualFeedbackSeverity::WARNING;
}
$this->addFlashMessage(
'Please specify url',
'Cache not flushed',
$severity
ContextualFeedbackSeverity::WARNING
);
return new RedirectResponse($this->uriBuilder->reset()->uriFor('index'));
}
$url = htmlspecialchars($url);
if (!$this->proxyProvider->isActive()) {
if ((GeneralUtility::makeInstance(Typo3Version::class))->getMajorVersion() === 11) {
$severity = AbstractMessage::ERROR;
} else {
$severity = ContextualFeedbackSeverity::ERROR;
}
$this->addFlashMessage(
'Attempting to purge URL "' . $url . '". No active provider configured.',
'Cache not flushed',
$severity
ContextualFeedbackSeverity::ERROR
);
return new RedirectResponse($this->uriBuilder->reset()->uriFor('index'));
}

$this->proxyProvider->flushCacheForUrls([$url]);
if ((GeneralUtility::makeInstance(Typo3Version::class))->getMajorVersion() === 11) {
$severity = AbstractMessage::OK;
} else {
$severity = ContextualFeedbackSeverity::OK;
}
$this->addFlashMessage(
'Successfully purged URL "' . $url . '".',
'Cache flushed',
$severity
ContextualFeedbackSeverity::OK
);
return new RedirectResponse($this->uriBuilder->reset()->uriFor('index'));
}
Expand Down
62 changes: 0 additions & 62 deletions Classes/Hook/FrontendHook.php

This file was deleted.

2 changes: 1 addition & 1 deletion Classes/Listener/AfterCacheIsPersisted.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public function __invoke(AfterCachedPageIsPersistedEvent $event): void
if (!$this->proxyProvider->isActive()) {
return;
}
$cacheTags = $event->getController()->getPageCacheTags();
$cacheTags = $event->getCacheData()['cacheTags'] ?? [];
$url = (string)$event->getRequest()->getUri();
$this->cache->set(md5($url), $url, $cacheTags, $event->getCacheLifetime());
}
Expand Down
4 changes: 0 additions & 4 deletions Configuration/Services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,6 @@ services:
identifier: 'b13-proxycachemanager-afterCacheIsPersisted'
arguments:
$cache: '@cache.tx_proxy'
B13\Proxycachemanager\Hook\FrontendHook:
public: true
arguments:
$cache: '@cache.tx_proxy'

B13\Proxycachemanager\ResourceStorageOperations\:
resource: '../Classes/ResourceStorageOperations/*'
Expand Down
23 changes: 0 additions & 23 deletions Resources/Private/TemplatesV11/Management/Index.html

This file was deleted.

43 changes: 43 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,48 @@
# Upgrade Instructions

v4 => v5

Proxycachemanager v5 drops support for TYPO3 v11 and v12. The minimum
supported version is now TYPO3 v13 LTS; v14 is fully supported.

## Removed

* `Classes/Hook/FrontendHook.php` — the v11-only `tslib/class.tslib_fe.php`
`insertPageIncache` hook. Cache writes are handled exclusively via the
`AfterCachedPageIsPersistedEvent` listener now.
* `Resources/Private/TemplatesV11/` — fallback Fluid template for the v11
backend module.
* `ext_tables.php` — v11-only `ExtensionUtility::registerModule()` call.
The module is registered through `Configuration/Backend/Modules.php` on
v12 and up.

## Changed

* `Classes/Listener/AfterCacheIsPersisted` no longer calls
`$event->getController()->getPageCacheTags()` — TYPO3 v14 dropped the
`controller` argument from `AfterCachedPageIsPersistedEvent`. Cache tags
are now read from `$event->getCacheData()['cacheTags']`, which TYPO3
populates in both v13 and v14.
* `Classes/Controller/ManagementController` no longer branches on the
TYPO3 major version. `\TYPO3\CMS\Core\Messaging\AbstractMessage::OK`,
`::WARNING`, `::ERROR` were removed in TYPO3 v14; the controller now
always uses `ContextualFeedbackSeverity::*` enum cases.
* `Classes/Cache/Backend/ReverseProxyCacheBackend` no longer implements
`TransientBackendInterface` — on TYPO3 v14 that interface became a typed
contract incompatible with `Typo3DatabaseBackend`. As a result the
configured `VariableFrontend` now serializes (and HMAC-signs on v13.4+)
the stored page URLs. The backend reads them back through the frontend,
so reverse-proxy/CDN purging keeps working, and rows written by v4 (plain
URLs) are still handled via a fallback. No manual migration is required;
flushing the `tx_proxy` cache once after the upgrade is optional but tidy.

## Required platform

* PHP 8.2+
* TYPO3 13.4+ or 14.x

---

v3 => v4

Proxycachemanager v3 served for many years as a well thought out basis
Expand Down
Loading
Loading