Files
typo3-13/packages/ep_theme/Classes/Middleware/CdnImageMiddleware.php
T
frommeandClaude Opus 5.5 4fe06267c9 refactor: code quality via Rector, strict types where safe
rector.php gets the codeQuality set: declare(strict_types=1) in the 85
files where Rector finds no call relying on scalar type juggling (mostly
TCA and configuration, models, the content feed API and the import
command), "or die" as "|| die", instanceof instead of null checks for
object types, early returns and self:: in final classes.

Hotel::setRooms() goes instead of getting the property Rector wanted to
declare: nothing calls it, and there is no rooms column any more.

Checked locally: cache warmup, the ep:* commands load, frontend crawl
(~1,900 URLs) and the backchannel APIs render the same as before.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-26 11:15:38 +01:00

66 lines
2.7 KiB
PHP

<?php
declare(strict_types=1);
namespace EP\EpTheme\Middleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
use TYPO3\CMS\Core\Configuration\ExtensionConfiguration;
use TYPO3\CMS\Core\Http\Stream;
/**
* Points image URLs in frontend pages at the CDN (formerly EXT:replacer).
*
* Only images go to the CDN. The theme's own css/js/fonts are served by the production
* host: they are already minified by Webpack, gzipped by mod_deflate and content-hashed
* for a year of immutable browser caching, and keeping them on the page's own origin
* means they arrive over the connection the browser has already opened rather than
* costing a second DNS lookup and TLS handshake on the critical path.
*
* It also keeps them clear of the pull zone's habit of occasionally wedging a single
* cache key (10.0s -> 504 errorcode 106 until that key is purged, while the origin
* serves the same bytes in under 100ms). For an image that degrades one element; for
* js/main.<hash>.js it would take every visitor's JavaScript down.
*
* typo3temp is scoped to generated images on purpose - assets/compressed holds other
* extensions' concatenated css/js, which belongs on the origin for the same reason.
* The quote character is captured rather than matched literally, so url() values
* written with single quotes are rewritten too.
*
* Registered after the page resolver, so the /api/* responses of ep_products never pass here.
*/
final readonly class CdnImageMiddleware implements MiddlewareInterface
{
private const string PATTERN = '/(["\'])\/?(fileadmin|uploads|typo3temp\/assets\/(?:_processed_|images))/';
public function __construct(private ExtensionConfiguration $extensionConfiguration)
{
}
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$response = $handler->handle($request);
$cdnBaseUrl = rtrim((string)($this->extensionConfiguration->get('ep_theme', 'cdnBaseUrl') ?? ''), '/');
$contentType = $response->getHeaderLine('Content-Type');
if ($cdnBaseUrl === ''
|| !str_starts_with($contentType, 'text/html') && !str_starts_with($contentType, 'application/json')
) {
return $response;
}
$body = (string)$response->getBody();
$replaced = preg_replace(self::PATTERN, '$1' . $cdnBaseUrl . '/$2', $body);
if ($replaced === null || $replaced === $body) {
return $response;
}
$stream = new Stream('php://temp', 'rw');
$stream->write($replaced);
return $response->withBody($stream);
}
}