151 lines
5.5 KiB
PHP
151 lines
5.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Evomedien\Vitec\Service;
|
|
|
|
use TYPO3\CMS\Core\Cache\CacheManager;
|
|
use TYPO3\CMS\Core\Core\Environment;
|
|
use TYPO3\CMS\Core\Exception\SiteNotFoundException;
|
|
use TYPO3\CMS\Core\Site\SiteFinder;
|
|
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
/**
|
|
* Auto-failover (vitec:ssr-health): turns SSR delivery hard OFF.
|
|
*
|
|
* Removing only the flag file would not stick - the self-healing
|
|
* SyncSsrFlag middleware recreates it from the still-true site setting
|
|
* on the very next request. So the setting itself is flipped to false
|
|
* in config/sites/<identifier>/settings.yaml (the file the backend
|
|
* settings editor writes, flat dotted keys), caches are flushed so the
|
|
* compiled site settings forget the old value, and only then the flag
|
|
* is removed. Re-enabling is deliberately manual (backend switch or
|
|
* settings.yaml + cache flush).
|
|
*/
|
|
public function disableSsr(): void
|
|
{
|
|
$changed = false;
|
|
foreach ($this->siteFinder->getAllSites() as $site) {
|
|
$file = Environment::getConfigPath() . '/sites/' . $site->getIdentifier() . '/settings.yaml';
|
|
if (!is_file($file)) {
|
|
continue;
|
|
}
|
|
$yaml = (string)file_get_contents($file);
|
|
$new = preg_replace('/^(vitec\.ssr\.enabled\s*:).*$/m', '$1 false', $yaml, 1, $count);
|
|
if ($new !== null && $count > 0 && $new !== $yaml) {
|
|
if (file_put_contents($file, $new) === false) {
|
|
throw new \RuntimeException('Could not write ' . $file, 1762800001);
|
|
}
|
|
$changed = true;
|
|
}
|
|
}
|
|
if (!$changed && $this->isEnabledInSettings()) {
|
|
throw new \RuntimeException(
|
|
'vitec.ssr.enabled not found in any config/sites/*/settings.yaml - cannot disable it, '
|
|
. 'and the SyncSsrFlag middleware would recreate the flag from the setting',
|
|
1762800002
|
|
);
|
|
}
|
|
if ($changed) {
|
|
GeneralUtility::makeInstance(CacheManager::class)->flushCaches();
|
|
}
|
|
@unlink($this->getFlagPath());
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
}
|
|
}
|
|
}
|