Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/Http/Controllers/PhoneHomeController.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@

use Statamic\Exceptions\NotFoundHttpException;
use Statamic\Facades\Config;
use Statamic\Licensing\Outpost;
use Statamic\Licensing\Radio;

class PhoneHomeController
{
public function __invoke(Outpost $outpost, $token)
public function __invoke(Radio $radio, $token)
{
if (! password_verify(Config::getLicenseKey(), base64_decode($token))) {
throw new NotFoundHttpException;
}

$outpost->radio();
$radio->forcePing();

return response()->json(['success' => true]);
}
Expand Down
23 changes: 0 additions & 23 deletions src/Http/Middleware/CP/ContactOutpost.php

This file was deleted.

31 changes: 31 additions & 0 deletions src/Http/Middleware/PingOutpost.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace Statamic\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Statamic\Licensing\Radio;
use Symfony\Component\HttpFoundation\Response;

class PingOutpost
{
public function __construct(private Radio $radio)
{
}

public function handle(Request $request, Closure $next): mixed
{
if ($this->radio->shouldPingDuringRequest($request)) {
$this->radio->ping();
}

return $next($request);
}

public function terminate(Request $request, Response $response): void
{
if ($this->radio->shouldPingAfterResponse($request)) {
$this->radio->ping();
}
}
}
2 changes: 1 addition & 1 deletion src/Licensing/Outpost.php
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ private function hasCachedResponse()

private function payloadHasChanged($previous, $current)
{
$exclude = ['ip', 'php_version'];
$exclude = ['host', 'ip', 'port', 'php_version'];

return Arr::except($previous, $exclude) !== Arr::except($current, $exclude);
}
Expand Down
186 changes: 186 additions & 0 deletions src/Licensing/Radio.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
<?php

namespace Statamic\Licensing;

use Illuminate\Contracts\Cache\Repository;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;
use InvalidArgumentException;
use Statamic\Facades\Glide;
use Statamic\Support\Str;
use Throwable;

class Radio
{
const PING_CACHE_KEY = 'statamic.outpost.pinged';
const PING_INTERVAL = 300; // seconds

public function __construct(private Outpost $outpost)
{
}

public function ping(): void
{
if (! $this->markAsPinged()) {
return;
}

$this->contactOutpost();
}

public function forcePing(): void
{
$this->cache()->put(self::PING_CACHE_KEY, now()->timestamp, self::PING_INTERVAL);

$this->contactOutpost();
}

private function contactOutpost(): void
{
try {
$this->outpost->radio();
} catch (Throwable $e) {
Log::debug('Error contacting Outpost: '.$e->getMessage());
}
}

/**
* An atomic put-if-absent, so concurrent requests at marker expiry
* cannot herd into Outpost together. Returns false when the
* marker already exists and the ping should be skipped.
*/
private function markAsPinged(): bool
{
return $this->cache()->add(self::PING_CACHE_KEY, now()->timestamp, self::PING_INTERVAL);
}

private function cache(): Repository
{
try {
return Cache::store('outpost');
} catch (InvalidArgumentException $e) {
return Cache::store();
}
}

public function shouldPingRequest(Request $request): bool
{
if ($request->isLivePreview()) {
return false;
}

return ! $this->isGlideRequest($request);
}

public function shouldPingDuringRequest(Request $request): bool
{
if (app()->runningUnitTests()) {
return false;
}

return $this->isCpRequest($request) && $this->shouldPingRequest($request);
}

public function shouldPingAfterResponse(Request $request): bool
{
if (app()->runningUnitTests()) {
return false;
}

return ! $this->isCpRequest($request) && $this->shouldPingRequest($request);
}

public function shouldPingCommand(?string $command): bool
{
if (app()->runningUnitTests() || $this->runningInCi()) {
return false;
}

return ! $this->isCommandIgnored($command);
}

public function isCommandIgnored(?string $command): bool
{
if (! $command) {
return true;
}

if (in_array($command, $this->ignoredCommands(), true)) {
return true;
}

foreach ($this->ignoredCommandPrefixes() as $prefix) {
if ($command === $prefix || str_starts_with($command, $prefix.':')) {
return true;
}
}

return false;
}

private function isCpRequest(Request $request): bool
{
if (! config('statamic.cp.enabled')) {
return false;
}

$cp = config('statamic.cp.route');
$path = $request->path();

return $path === $cp
|| Str::startsWith($path, Str::finish($cp, '/'));
}

private function isGlideRequest(Request $request): bool
{
$route = trim((string) Glide::route(), '/');

if ($route === '') {
return false;
}

return $request->is($route, $route.'/*', '*/'.$route, '*/'.$route.'/*');
}

private function runningInCi(): bool
{
return filter_var($_SERVER['CI'] ?? $_ENV['CI'] ?? getenv('CI'), FILTER_VALIDATE_BOOLEAN);
}

/**
* @return list<string>
*/
private function ignoredCommandPrefixes(): array
{
return [
'horizon',
'nightwatch',
'octane',
'pail',
'pulse',
'queue',
'reverb',
'schedule',
];
}

/**
* @return list<string>
*/
private function ignoredCommands(): array
{
return [
'completion',
'docs',
'dump-server',
'help',
'inspire',
'list',
'pest',
'serve',
'test',
'tinker',
];
}
}
22 changes: 22 additions & 0 deletions src/Listeners/PingOutpostOnCommandFinished.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace Statamic\Listeners;

use Illuminate\Console\Events\CommandFinished;
use Statamic\Licensing\Radio;

class PingOutpostOnCommandFinished
{
public function __construct(private Radio $radio)
{
}

public function handle(CommandFinished $event): void
{
if (! $this->radio->shouldPingCommand($event->command)) {
return;
}

$this->radio->ping();
}
}
11 changes: 10 additions & 1 deletion src/Providers/AppServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
use Statamic\Facades\Token;
use Statamic\Facades\User;
use Statamic\Fields\FieldsetRecursionStack;
use Statamic\Http\Middleware\PingOutpost;
use Statamic\Jobs\HandleEntrySchedule;
use Statamic\Licensing\Radio;
use Statamic\Notifications\ElevatedSessionVerificationCode;
use Statamic\Sites\Sites;
use Statamic\Stache\Query\RevisionQueryBuilder;
Expand Down Expand Up @@ -51,7 +53,8 @@ public function boot()
->pushMiddleware(\Statamic\Http\Middleware\PoweredByHeader::class)
->pushMiddleware(\Statamic\Http\Middleware\CheckComposerJsonScripts::class)
->pushMiddleware(\Statamic\Http\Middleware\CheckMultisite::class)
->pushMiddleware(\Statamic\Http\Middleware\StopImpersonating::class);
->pushMiddleware(\Statamic\Http\Middleware\StopImpersonating::class)
->pushMiddleware(PingOutpost::class);

$this->loadViewsFrom("{$this->root}/resources/views", 'statamic');

Expand Down Expand Up @@ -140,6 +143,12 @@ public function boot()
if (config('statamic.system.handle_scheduled_entries')) {
$this->app->make(Schedule::class)->job(HandleEntrySchedule::class)->everyMinute();
}

$this->app->make(Schedule::class)
->call(fn () => app(Radio::class)->ping())
->hourly()
->name('statamic-outpost')
->withoutOverlapping();
}

public function register()
Expand Down
1 change: 0 additions & 1 deletion src/Providers/CpServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ protected function registerMiddlewareGroups()
\Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class,
\Statamic\Http\Middleware\CP\HandleInertiaRequests::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\Statamic\Http\Middleware\CP\ContactOutpost::class,
\Statamic\Http\Middleware\CP\AuthGuard::class,
\Statamic\Http\Middleware\CP\AddToasts::class,
\Statamic\Http\Middleware\CP\TrimStrings::class,
Expand Down
3 changes: 3 additions & 0 deletions src/Providers/EventServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ class EventServiceProvider extends ServiceProvider
\Illuminate\Foundation\Http\Events\RequestHandled::class => [
\Statamic\Listeners\ClearState::class,
],
\Illuminate\Console\Events\CommandFinished::class => [
\Statamic\Listeners\PingOutpostOnCommandFinished::class,
],
];

protected $subscribe = [
Expand Down
Loading
Loading