111 lines
3.0 KiB
PHP
111 lines
3.0 KiB
PHP
<?php
|
|
|
|
namespace classes;
|
|
|
|
use RuntimeException;
|
|
|
|
class application_write_freeze
|
|
{
|
|
public static function freeze(string $reason, string $owner, int $ttlSeconds = 300): void
|
|
{
|
|
$payload = [
|
|
'reason' => $reason,
|
|
'owner' => $owner,
|
|
'created_at' => date('c'),
|
|
'expires_at' => date('c', time() + max(30, $ttlSeconds)),
|
|
];
|
|
|
|
self::writeState($payload);
|
|
}
|
|
|
|
public static function unfreeze(?string $owner = null): void
|
|
{
|
|
$state = self::state();
|
|
if ($owner !== null && isset($state['owner']) && $state['owner'] !== $owner) {
|
|
return;
|
|
}
|
|
|
|
$path = self::statePath();
|
|
if (is_file($path)) {
|
|
@unlink($path);
|
|
}
|
|
}
|
|
|
|
public static function state(): array
|
|
{
|
|
$path = self::statePath();
|
|
if (!is_file($path)) {
|
|
return [];
|
|
}
|
|
|
|
$state = json_decode((string)file_get_contents($path), true);
|
|
if (!is_array($state)) {
|
|
@unlink($path);
|
|
return [];
|
|
}
|
|
|
|
$expiresAt = strtotime((string)($state['expires_at'] ?? ''));
|
|
if ($expiresAt !== false && $expiresAt < time()) {
|
|
@unlink($path);
|
|
return [];
|
|
}
|
|
|
|
return $state;
|
|
}
|
|
|
|
public static function isFrozen(): bool
|
|
{
|
|
return self::state() !== [];
|
|
}
|
|
|
|
public static function shouldBlock(string $method, string $uri, bool $isCronOrCli): bool
|
|
{
|
|
if (!self::isFrozen()) {
|
|
return false;
|
|
}
|
|
|
|
if ($isCronOrCli) {
|
|
return true;
|
|
}
|
|
|
|
$method = strtoupper($method);
|
|
if (in_array($method, ['GET', 'HEAD', 'OPTIONS'], true)) {
|
|
return false;
|
|
}
|
|
|
|
$path = parse_url($uri, PHP_URL_PATH) ?: '';
|
|
return !str_starts_with($path, '/superuser/replication');
|
|
}
|
|
|
|
private static function writeState(array $state): void
|
|
{
|
|
$path = self::statePath();
|
|
$dir = dirname($path);
|
|
if (!is_dir($dir) && !mkdir($dir, 0770, true) && !is_dir($dir)) {
|
|
throw new RuntimeException('Could not create write-freeze directory.');
|
|
}
|
|
|
|
$tempPath = tempnam($dir, 'write-freeze-');
|
|
if ($tempPath === false) {
|
|
throw new RuntimeException('Could not create write-freeze temp file.');
|
|
}
|
|
|
|
try {
|
|
file_put_contents($tempPath, json_encode($state, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . PHP_EOL, LOCK_EX);
|
|
if (!rename($tempPath, $path)) {
|
|
throw new RuntimeException('Could not atomically replace write-freeze state.');
|
|
}
|
|
} finally {
|
|
if (is_file($tempPath)) {
|
|
@unlink($tempPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static function statePath(): string
|
|
{
|
|
$root = defined('WD') ? WD : dirname(__DIR__);
|
|
return $root . DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . 'application-write-freeze.json';
|
|
}
|
|
}
|