summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/Cli/Commands/SiteOffline.php23
-rw-r--r--app/Cli/Commands/SiteOnline.php22
-rw-r--r--app/Http/Middleware/CheckForMaintenanceMode.php18
-rw-r--r--app/Http/RequestHandlers/UpgradeWizardStep.php28
-rw-r--r--app/Services/MaintenanceModeService.php95
-rw-r--r--app/Services/UpgradeService.php20
-rw-r--r--app/Webtrees.php3
-rw-r--r--tests/app/Http/Middleware/CheckForMaintenanceModeTest.php25
-rw-r--r--tests/app/Http/RequestHandlers/UpgradeWizardStepTest.php16
-rw-r--r--tests/app/Services/MaintenanceModeServiceTest.php115
10 files changed, 288 insertions, 77 deletions
diff --git a/app/Cli/Commands/SiteOffline.php b/app/Cli/Commands/SiteOffline.php
index be5d01e5bb..bea84691e2 100644
--- a/app/Cli/Commands/SiteOffline.php
+++ b/app/Cli/Commands/SiteOffline.php
@@ -19,18 +19,21 @@ declare(strict_types=1);
namespace Fisharebest\Webtrees\Cli\Commands;
-use Fisharebest\Webtrees\Webtrees;
+use Fisharebest\Webtrees\Services\MaintenanceModeService;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Throwable;
-use function file_exists;
-use function file_put_contents;
-
final class SiteOffline extends AbstractCommand
{
+ public function __construct(
+ private readonly MaintenanceModeService $maintenance_mode_service,
+ ) {
+ parent::__construct();
+ }
+
protected function configure(): void
{
$this
@@ -45,22 +48,18 @@ final class SiteOffline extends AbstractCommand
$io = new SymfonyStyle(input: $input, output: $output);
- $file_exists = file_exists(filename: Webtrees::OFFLINE_FILE);
+ $file = $this->maintenance_mode_service->file();
try {
- file_put_contents(filename: Webtrees::OFFLINE_FILE, data: $message);
+ $this->maintenance_mode_service->offline($message);
} catch (Throwable $ex) {
- $io->error(message: 'Failed to write file ' . Webtrees::OFFLINE_FILE);
+ $io->error(message: 'Failed to write file ' . $file);
$io->error(message: $ex->getMessage());
return self::FAILURE;
}
- if ($file_exists) {
- $io->success(message: Webtrees::OFFLINE_FILE . ' updated. Site is offline.');
- } else {
- $io->success(message: Webtrees::OFFLINE_FILE . ' created. Site is offline.');
- }
+ $io->success(message: $file . ' created. Site is offline.');
return self::SUCCESS;
}
diff --git a/app/Cli/Commands/SiteOnline.php b/app/Cli/Commands/SiteOnline.php
index 36c357c6cb..77f4680def 100644
--- a/app/Cli/Commands/SiteOnline.php
+++ b/app/Cli/Commands/SiteOnline.php
@@ -19,16 +19,20 @@ declare(strict_types=1);
namespace Fisharebest\Webtrees\Cli\Commands;
-use Fisharebest\Webtrees\Webtrees;
+use Fisharebest\Webtrees\Services\MaintenanceModeService;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Throwable;
-use function file_exists;
-
final class SiteOnline extends AbstractCommand
{
+ public function __construct(
+ private readonly MaintenanceModeService $maintenanceModeService,
+ ) {
+ parent::__construct();
+ }
+
protected function configure(): void
{
$this
@@ -40,17 +44,17 @@ final class SiteOnline extends AbstractCommand
{
$io = new SymfonyStyle(input: $input, output: $output);
- $file_exists = file_exists(filename: Webtrees::OFFLINE_FILE);
+ $file = $this->maintenanceModeService->file();
- if (!$file_exists) {
- $io->success(message: Webtrees::OFFLINE_FILE . ' does not exist. Site is already online.');
+ if (!$this->maintenanceModeService->isOffline()) {
+ $io->success(message: $file . ' does not exist. Site is already online.');
}
try {
- unlink(filename: Webtrees::OFFLINE_FILE);
- $io->success(message: Webtrees::OFFLINE_FILE . ' deleted. Site is online.');
+ $this->maintenanceModeService->online();
+ $io->success(message: $file . ' deleted. Site is online.');
} catch (Throwable $ex) {
- $io->error(message: 'Unable to delete ' . Webtrees::OFFLINE_FILE . ' - ' . $ex->getMessage());
+ $io->error(message: 'Unable to delete ' . $file . ' - ' . $ex->getMessage());
}
return self::SUCCESS;
diff --git a/app/Http/Middleware/CheckForMaintenanceMode.php b/app/Http/Middleware/CheckForMaintenanceMode.php
index e93705979d..d12785a91f 100644
--- a/app/Http/Middleware/CheckForMaintenanceMode.php
+++ b/app/Http/Middleware/CheckForMaintenanceMode.php
@@ -20,6 +20,7 @@ declare(strict_types=1);
namespace Fisharebest\Webtrees\Http\Middleware;
use Fig\Http\Message\StatusCodeInterface;
+use Fisharebest\Webtrees\Services\MaintenanceModeService;
use Fisharebest\Webtrees\Webtrees;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
@@ -29,19 +30,18 @@ use Psr\Http\Server\RequestHandlerInterface;
/**
* Middleware to check whether the site is offline.
*/
-class CheckForMaintenanceMode implements MiddlewareInterface, StatusCodeInterface
+readonly class CheckForMaintenanceMode implements MiddlewareInterface, StatusCodeInterface
{
- /**
- * @param ServerRequestInterface $request
- * @param RequestHandlerInterface $handler
- *
- * @return ResponseInterface
- */
+ public function __construct(
+ private MaintenanceModeService $maintenance_mode_service,
+ ) {
+ }
+
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
- if (file_exists(Webtrees::OFFLINE_FILE) && !is_dir(Webtrees::OFFLINE_FILE)) {
+ if ($this->maintenance_mode_service->isOffline()) {
$html = view('layouts/offline', [
- 'message' => file_get_contents(Webtrees::OFFLINE_FILE),
+ 'message' => $this->maintenance_mode_service->message(),
'url' => (string) $request->getUri(),
]);
diff --git a/app/Http/RequestHandlers/UpgradeWizardStep.php b/app/Http/RequestHandlers/UpgradeWizardStep.php
index 922f18b80f..2c6142806b 100644
--- a/app/Http/RequestHandlers/UpgradeWizardStep.php
+++ b/app/Http/RequestHandlers/UpgradeWizardStep.php
@@ -25,6 +25,7 @@ use Fisharebest\Webtrees\Http\Exceptions\HttpServerErrorException;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\Registry;
use Fisharebest\Webtrees\Services\GedcomExportService;
+use Fisharebest\Webtrees\Services\MaintenanceModeService;
use Fisharebest\Webtrees\Services\TreeService;
use Fisharebest\Webtrees\Services\UpgradeService;
use Fisharebest\Webtrees\Tree;
@@ -49,7 +50,7 @@ use function view;
/**
* Upgrade to a new version of webtrees.
*/
-class UpgradeWizardStep implements RequestHandlerInterface
+readonly class UpgradeWizardStep implements RequestHandlerInterface
{
// We make the upgrade in a number of small steps to keep within server time limits.
private const string STEP_CHECK = 'Check';
@@ -76,25 +77,12 @@ class UpgradeWizardStep implements RequestHandlerInterface
'vendor',
];
- private GedcomExportService $gedcom_export_service;
-
- private UpgradeService $upgrade_service;
-
- private TreeService $tree_service;
-
- /**
- * @param GedcomExportService $gedcom_export_service
- * @param TreeService $tree_service
- * @param UpgradeService $upgrade_service
- */
public function __construct(
- GedcomExportService $gedcom_export_service,
- TreeService $tree_service,
- UpgradeService $upgrade_service
+ private GedcomExportService $gedcom_export_service,
+ private MaintenanceModeService $maintenance_mode_service,
+ private TreeService $tree_service,
+ private UpgradeService $upgrade_service,
) {
- $this->gedcom_export_service = $gedcom_export_service;
- $this->tree_service = $tree_service;
- $this->upgrade_service = $upgrade_service;
}
/**
@@ -276,9 +264,9 @@ class UpgradeWizardStep implements RequestHandlerInterface
$source_filesystem = Registry::filesystem()->root(self::UPGRADE_FOLDER . self::ZIP_FILE_PREFIX);
$root_filesystem = Registry::filesystem()->root();
- $this->upgrade_service->startMaintenanceMode();
+ $this->maintenance_mode_service->offline();
$this->upgrade_service->moveFiles($source_filesystem, $root_filesystem);
- $this->upgrade_service->endMaintenanceMode();
+ $this->maintenance_mode_service->online();
// While we have time, clean up any old files.
$files_to_keep = $this->upgrade_service->webtreesZipContents($zip_file);
diff --git a/app/Services/MaintenanceModeService.php b/app/Services/MaintenanceModeService.php
new file mode 100644
index 0000000000..084a85797b
--- /dev/null
+++ b/app/Services/MaintenanceModeService.php
@@ -0,0 +1,95 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2025 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Services;
+
+use Fisharebest\Webtrees\Webtrees;
+use InvalidArgumentException;
+
+use function file_get_contents;
+use function file_put_contents;
+use function is_dir;
+use function is_file;
+use function is_link;
+use function is_readable;
+use function is_string;
+use function realpath;
+use function rmdir;
+use function unlink;
+
+/**
+ * Manage the site's online/offline status.
+ */
+readonly class MaintenanceModeService
+{
+ private const string OFFLINE_FILE = 'offline.txt';
+
+ public function __construct(private string $data_dir = Webtrees::DATA_DIR)
+ {
+ if (!is_dir($data_dir)) {
+ throw new InvalidArgumentException($data_dir . ' does not exist');
+ }
+ }
+
+ public function file(): string
+ {
+ // Remove any '/../' from the path.
+ return realpath($this->data_dir) . DIRECTORY_SEPARATOR . self::OFFLINE_FILE;
+ }
+
+ public function isOffline(): bool
+ {
+ $file = $this->file();
+
+ return is_file($file) || is_link($file) || is_dir($file);
+ }
+
+ public function message(): string
+ {
+ $file = $this->file();
+
+ if ($this->isOffline() && is_file($file) && is_readable($file)) {
+ $message = file_get_contents($file);
+
+ if (is_string($message)) {
+ return $message;
+ }
+ }
+
+ return '';
+ }
+
+ public function offline(string $message = ''): void
+ {
+ $this->online();
+
+ file_put_contents($this->file(), $message);
+ }
+
+ public function online(): void
+ {
+ $file = $this->file();
+
+ if (is_dir($file)) {
+ rmdir($file);
+ } elseif (is_link($file) || is_file($file)) {
+ unlink($file);
+ }
+ }
+}
diff --git a/app/Services/UpgradeService.php b/app/Services/UpgradeService.php
index 13893216a2..6c44780e70 100644
--- a/app/Services/UpgradeService.php
+++ b/app/Services/UpgradeService.php
@@ -301,26 +301,6 @@ class UpgradeService
}
/**
- * @return void
- */
- public function startMaintenanceMode(): void
- {
- $message = I18N::translate('This website is being upgraded. Try again in a few minutes.');
-
- file_put_contents(Webtrees::OFFLINE_FILE, $message);
- }
-
- /**
- * @return void
- */
- public function endMaintenanceMode(): void
- {
- if (file_exists(Webtrees::OFFLINE_FILE)) {
- unlink(Webtrees::OFFLINE_FILE);
- }
- }
-
- /**
* Check with the webtrees.net server for the latest version of webtrees.
* Fetching the remote file can be slow, so check infrequently, and cache the result.
* Pass the current versions of webtrees, PHP and database, as the response
diff --git a/app/Webtrees.php b/app/Webtrees.php
index 5a0ec56b71..ffde03b866 100644
--- a/app/Webtrees.php
+++ b/app/Webtrees.php
@@ -109,9 +109,6 @@ class Webtrees
// Location of the file containing the database connection details.
public const string CONFIG_FILE = self::DATA_DIR . 'config.ini.php';
- // Location of the file that triggers maintenance mode.
- public const string OFFLINE_FILE = self::DATA_DIR . 'offline.txt';
-
// Location of our modules.
public const string MODULES_PATH = 'modules_v4/';
public const string MODULES_DIR = self::ROOT_DIR . self::MODULES_PATH;
diff --git a/tests/app/Http/Middleware/CheckForMaintenanceModeTest.php b/tests/app/Http/Middleware/CheckForMaintenanceModeTest.php
index c2d45d7c56..a047f552ac 100644
--- a/tests/app/Http/Middleware/CheckForMaintenanceModeTest.php
+++ b/tests/app/Http/Middleware/CheckForMaintenanceModeTest.php
@@ -20,6 +20,7 @@ declare(strict_types=1);
namespace Fisharebest\Webtrees\Http\Middleware;
use Fig\Http\Message\StatusCodeInterface;
+use Fisharebest\Webtrees\Services\MaintenanceModeService;
use Fisharebest\Webtrees\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use Psr\Http\Server\RequestHandlerInterface;
@@ -29,13 +30,33 @@ use function response;
#[CoversClass(CheckForMaintenanceMode::class)]
class CheckForMaintenanceModeTest extends TestCase
{
- public function testMiddleware(): void
+ public function testSiteIsOffline(): void
{
$handler = $this->createMock(RequestHandlerInterface::class);
$handler->method('handle')->willReturn(response());
+ $maintenance_mode_service = $this->createMock(MaintenanceModeService::class);
+ $maintenance_mode_service->method('isOffline')->willReturn(true);
+ $maintenance_mode_service->method('message')->willReturn('XYZZY');
+
+ $request = self::createRequest();
+ $middleware = new CheckForMaintenanceMode($maintenance_mode_service);
+ $response = $middleware->process($request, $handler);
+
+ self::assertSame(StatusCodeInterface::STATUS_SERVICE_UNAVAILABLE, $response->getStatusCode());
+ self::assertStringContainsString('XYZZY', $response->getBody()->getContents());
+ }
+
+ public function testSiteIsOnline(): void
+ {
+ $handler = $this->createMock(RequestHandlerInterface::class);
+ $handler->method('handle')->willReturn(response());
+
+ $maintenance_mode_service = $this->createMock(MaintenanceModeService::class);
+ $maintenance_mode_service->method('isOffline')->willReturn(false);
+
$request = self::createRequest();
- $middleware = new CheckForMaintenanceMode();
+ $middleware = new CheckForMaintenanceMode($maintenance_mode_service);
$response = $middleware->process($request, $handler);
self::assertSame(StatusCodeInterface::STATUS_NO_CONTENT, $response->getStatusCode());
diff --git a/tests/app/Http/RequestHandlers/UpgradeWizardStepTest.php b/tests/app/Http/RequestHandlers/UpgradeWizardStepTest.php
index 4b9c747a5a..030a7135fa 100644
--- a/tests/app/Http/RequestHandlers/UpgradeWizardStepTest.php
+++ b/tests/app/Http/RequestHandlers/UpgradeWizardStepTest.php
@@ -26,6 +26,7 @@ use Fisharebest\Webtrees\Auth;
use Fisharebest\Webtrees\Http\Exceptions\HttpServerErrorException;
use Fisharebest\Webtrees\Services\GedcomExportService;
use Fisharebest\Webtrees\Services\GedcomImportService;
+use Fisharebest\Webtrees\Services\MaintenanceModeService;
use Fisharebest\Webtrees\Services\PhpService;
use Fisharebest\Webtrees\Services\TimeoutService;
use Fisharebest\Webtrees\Services\TreeService;
@@ -45,8 +46,9 @@ class UpgradeWizardStepTest extends TestCase
{
$handler = new UpgradeWizardStep(
new GedcomExportService(new Psr17Factory(), new Psr17Factory()),
+ new MaintenanceModeService(__DIR__ . '/../../../data/'),
new TreeService(new GedcomImportService()),
- new UpgradeService(new TimeoutService(php_service: new PhpService()))
+ new UpgradeService(new TimeoutService(php_service: new PhpService())),
);
$request = self::createRequest(RequestMethodInterface::METHOD_POST, ['step' => 'Invalid']);
@@ -62,6 +64,7 @@ class UpgradeWizardStepTest extends TestCase
$mock_upgrade_service->method('latestVersion')->willReturn('999.999.999');
$handler = new UpgradeWizardStep(
new GedcomExportService(new Psr17Factory(), new Psr17Factory()),
+ new MaintenanceModeService(__DIR__ . '/../../../data/'),
new TreeService(new GedcomImportService()),
$mock_upgrade_service
);
@@ -80,6 +83,7 @@ class UpgradeWizardStepTest extends TestCase
$mock_upgrade_service->method('latestVersion')->willReturn('');
$handler = new UpgradeWizardStep(
new GedcomExportService(new Psr17Factory(), new Psr17Factory()),
+ new MaintenanceModeService(__DIR__ . '/../../../data/'),
new TreeService(new GedcomImportService()),
$mock_upgrade_service
);
@@ -96,6 +100,7 @@ class UpgradeWizardStepTest extends TestCase
$mock_upgrade_service->method('latestVersion')->willReturn('0.0.0');
$handler = new UpgradeWizardStep(
new GedcomExportService(new Psr17Factory(), new Psr17Factory()),
+ new MaintenanceModeService(__DIR__ . '/../../../data/'),
new TreeService(new GedcomImportService()),
$mock_upgrade_service
);
@@ -108,6 +113,7 @@ class UpgradeWizardStepTest extends TestCase
{
$handler = new UpgradeWizardStep(
new GedcomExportService(new Psr17Factory(), new Psr17Factory()),
+ new MaintenanceModeService(__DIR__ . '/../../../data/'),
new TreeService(new GedcomImportService()),
new UpgradeService(new TimeoutService(php_service: new PhpService()))
);
@@ -122,6 +128,7 @@ class UpgradeWizardStepTest extends TestCase
{
$handler = new UpgradeWizardStep(
new GedcomExportService(new Psr17Factory(), new Psr17Factory()),
+ new MaintenanceModeService(__DIR__ . '/../../../data/'),
new TreeService(new GedcomImportService()),
new UpgradeService(new TimeoutService(php_service: new PhpService()))
);
@@ -143,6 +150,7 @@ class UpgradeWizardStepTest extends TestCase
$handler = new UpgradeWizardStep(
new GedcomExportService(new Psr17Factory(), new Psr17Factory()),
+ new MaintenanceModeService(__DIR__ . '/../../../data/'),
new TreeService(new GedcomImportService()),
new UpgradeService(new TimeoutService(php_service: new PhpService()))
);
@@ -162,6 +170,7 @@ class UpgradeWizardStepTest extends TestCase
$handler = new UpgradeWizardStep(
new GedcomExportService(new Psr17Factory(), new Psr17Factory()),
+ new MaintenanceModeService(__DIR__ . '/../../../data/'),
$tree_service,
new UpgradeService(new TimeoutService(php_service: new PhpService()))
);
@@ -182,9 +191,10 @@ class UpgradeWizardStepTest extends TestCase
$this->expectException(HttpServerErrorException::class);
$mock_upgrade_service = $this->createMock(UpgradeService::class);
- $mock_upgrade_service->method('downloadFile')->will(self::throwException(new Exception()));
+ $mock_upgrade_service->method('downloadFile')->will($this->throwException(new Exception()));
$handler = new UpgradeWizardStep(
new GedcomExportService(new Psr17Factory(), new Psr17Factory()),
+ new MaintenanceModeService(__DIR__ . '/../../../data/'),
new TreeService(new GedcomImportService()),
$mock_upgrade_service
);
@@ -199,6 +209,7 @@ class UpgradeWizardStepTest extends TestCase
$mock_upgrade_service->method('downloadFile')->willReturn(123456);
$handler = new UpgradeWizardStep(
new GedcomExportService(new Psr17Factory(), new Psr17Factory()),
+ new MaintenanceModeService(__DIR__ . '/../../../data/'),
new TreeService(new GedcomImportService()),
$mock_upgrade_service
);
@@ -215,6 +226,7 @@ class UpgradeWizardStepTest extends TestCase
$mock_upgrade_service->method('webtreesZipContents')->willReturn(new Collection());
$handler = new UpgradeWizardStep(
new GedcomExportService(new Psr17Factory(), new Psr17Factory()),
+ new MaintenanceModeService(__DIR__ . '/../../../data/'),
new TreeService(new GedcomImportService()),
$mock_upgrade_service
);
diff --git a/tests/app/Services/MaintenanceModeServiceTest.php b/tests/app/Services/MaintenanceModeServiceTest.php
new file mode 100644
index 0000000000..6e11385e59
--- /dev/null
+++ b/tests/app/Services/MaintenanceModeServiceTest.php
@@ -0,0 +1,115 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2025 webtrees development team
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Services;
+
+use Fisharebest\Webtrees\TestCase;
+use InvalidArgumentException;
+use PHPUnit\Framework\Attributes\CoversClass;
+
+use function file_put_contents;
+
+#[CoversClass(MaintenanceModeService::class)]
+class MaintenanceModeServiceTest extends TestCase
+{
+ private const string TEST_DATA_DIR = __DIR__ . '/../../data/';
+
+ public function testInvalidFolder(): void
+ {
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage(__DIR__ . '/non-existent does not exist');
+
+ new MaintenanceModeService(__DIR__ . '/non-existent');
+ }
+
+ public function testOfflineAndOnline(): void
+ {
+ $service = new MaintenanceModeService(self::TEST_DATA_DIR);
+
+ $this->assertFalse($service->isOffline());
+
+ $service->offline();
+
+ $this->assertTrue($service->isOffline());
+
+ $service->online();
+
+ $this->assertFalse($service->isOffline());
+ }
+
+ public function testMessage(): void
+ {
+ $service = new MaintenanceModeService(self::TEST_DATA_DIR);
+
+ $service->offline('foo bar');
+
+ $this->assertSame('foo bar', $service->message());
+
+ $service->online();
+ }
+
+ public function testOfflineFileIsAFolder(): void
+ {
+ $service = new MaintenanceModeService(self::TEST_DATA_DIR);
+
+ mkdir(self::TEST_DATA_DIR . 'offline.txt', 0777, true);
+
+ $this->assertTrue($service->isOffline());
+ $this->assertSame('', $service->message());
+
+ $service->online();
+
+ $this->assertFalse($service->isOffline());
+ }
+
+ public function testOfflineFileIsUnreadable(): void
+ {
+ $service = new MaintenanceModeService(self::TEST_DATA_DIR);
+
+ file_put_contents(self::TEST_DATA_DIR . 'offline.txt', 'foo');
+ chmod(self::TEST_DATA_DIR . 'offline.txt', 0);
+
+ $this->assertTrue($service->isOffline());
+ $this->assertSame('', $service->message());
+
+ $service->online();
+
+ $this->assertFalse($service->isOffline());
+ }
+
+ public function testOfflineFileIsSymbolicLink(): void
+ {
+ $service = new MaintenanceModeService(self::TEST_DATA_DIR);
+
+ file_put_contents(self::TEST_DATA_DIR . 'foo', 'foo');
+ symlink(self::TEST_DATA_DIR . 'foo', self::TEST_DATA_DIR . 'offline.txt');
+
+ $this->assertTrue($service->isOffline());
+ $this->assertSame('foo', $service->message());
+
+ unlink(self::TEST_DATA_DIR . 'foo');
+
+ $this->assertTrue($service->isOffline());
+ $this->assertSame('', $service->message());
+
+ $service->online();
+
+ $this->assertFalse($service->isOffline());
+ }
+}