Files
VITEC-website/packages/vitec/Classes/Service/SsrFlagService.php
T
orascheandClaude Fable 5 63f950f9db SSR control center: auto-purge webhook, dashboard tiles, backend module
Package 1: SsrPurgeService posts to the node /__purge (endpoint+secret in var/vitec-ssr-purge.env, outside git) and logs every attempt to tx_vitec_ssrpurgelog. DataHandler hooks queue per request and flush once: pages with nav-relevant field changes and all tx_news_/tx_vitec_ records purge everything, other page/content edits purge the hosting page path. Gated by the routing flag and the new Site Set switch vitec.ssr.autoPurge. Flipping the SSR switch ON purges everything once. CLI: vitec:ssr-purge, vitec:ssr-health (samples /__health into tx_vitec_ssrhealthlog, 14 days kept, scheduler-ready). Package 2: dashboard widgets vitecSsrStatus (live health, release, switch, last purge) and vitecSsrStats (purge counts, failures, uptime, response time). Package 3: backend module web_vitecssr with manual purge (paths or everything) and both protocols. Tested end to end against the running node (purge single/all 200, health sample logged).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-11-09 20:46:16 +01:00

107 lines
3.6 KiB
PHP

<?php
declare(strict_types=1);
namespace Evomedien\Vitec\Service;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Exception\SiteNotFoundException;
use TYPO3\CMS\Core\Site\SiteFinder;
/**
* Keeps the SSR routing flag file in sync with the site setting
* "vitec.ssr.enabled" (Site Set switch, editable in the backend under
* Site Management > Settings).
*
* The flag file is what public/.htaccess checks per request (RewriteCond
* ... -f): if it exists, page requests are proxied to the SSR node on the
* Solr VPS; if not, the SPA fallback serves _frontend/index.html as
* before. TYPO3 itself never routes differently - the flag only steers
* Apache, which is why a plain file is used: zero runtime cost in the
* hot path and trivially inspectable on the server.
*
* Saving site settings in the TYPO3 v14 backend fires NO event (core
* SiteWriter::writeSettings dispatches nothing, checked 2026-11-09), so
* the sync runs self-healingly from the SyncSsrFlag middleware on every
* frontend and backend request - a mismatch costs one file operation
* once, agreement costs a file_exists(). The vitec:ssr-flag CLI command
* offers the same sync plus a status view.
*/
final class SsrFlagService
{
public function __construct(private readonly SiteFinder $siteFinder)
{
}
public function getFlagPath(): string
{
return Environment::getVarPath() . '/vitec-ssr-on.flag';
}
/**
* True as soon as ANY site enables the setting (there is only the
* "vitec" site; iterating keeps us independent of the identifier).
*/
public function isEnabledInSettings(): bool
{
try {
foreach ($this->siteFinder->getAllSites() as $site) {
if ((bool)$site->getSettings()->get('vitec.ssr.enabled', false)) {
return true;
}
}
} catch (SiteNotFoundException) {
// No sites configured - treat as disabled.
}
return false;
}
public function isFlagSet(): bool
{
return is_file($this->getFlagPath());
}
/**
* Creates or removes the flag file so it matches the setting.
*
* @return bool|null The new state if something changed, null if the
* flag already matched the setting.
*/
public function sync(): ?bool
{
$desired = $this->isEnabledInSettings();
if ($desired === $this->isFlagSet()) {
return null;
}
if ($desired) {
file_put_contents(
$this->getFlagPath(),
"SSR routing is ON (site setting vitec.ssr.enabled).\n"
. "Managed by SsrFlagService - do not create/delete manually,\n"
. "flip the switch in Site Management > Settings instead.\n"
. 'Last sync: ' . date('c') . "\n"
);
// Fresh ON: whatever the node cached while the switch was OFF
// is stale by definition - drop its whole page cache once.
\TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\Evomedien\Vitec\Service\SsrPurgeService::class)
->purgeQuietly(['*'], 'switch-on');
} else {
@unlink($this->getFlagPath());
}
return $desired;
}
/**
* Sync variant for the request hot path: never lets an IO hiccup
* (read-only FS, concurrent unlink ...) break request handling.
*/
public function syncQuietly(): void
{
try {
$this->sync();
} catch (\Throwable) {
// Intentionally silent - next request retries.
}
}
}