dpl/app/Services/MailFlow/AutomaticMailPolicy.php

84 lines
3.6 KiB
PHP

<?php
namespace App\Services\MailFlow;
use App\Models\Setting;
use App\Models\User;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
class AutomaticMailPolicy
{
// One atomic record; read directly so long-running workers never use a stale tier.
public static function state(): ?array
{
try {
$state = Setting::where('name', 'automatic_mail_policy')->first()?->value;
if ($state === null) { return ['level' => 1, 'since' => null]; }
if (!is_array($state) || !in_array($state['level'] ?? null, [1, 2, 3], true)) { return null; }
return $state;
} catch (\Throwable $e) {
return null;
}
}
public static function save(int $level): void
{
if (!in_array($level, [1, 2, 3], true)) { throw new \InvalidArgumentException('Invalid automatic mail level'); }
Cache::lock('automatic-mail-policy:save', 10)->block(5, function () use ($level) {
DB::transaction(function () use ($level) {
$previous = self::state();
if ($previous === null) { throw new \RuntimeException('automatic_mail_policy_unavailable'); }
Setting::createOrUpdate('automatic_mail_policy', [
'level' => $level,
'since' => $level === 2 ? (($previous['level'] === 2 ? $previous['since'] : null) ?? now()->timestamp) : null,
]);
});
});
Cache::store('redis')->forget(\App\Support\Setting::CACHE_KEY);
}
public static function purpose(array $params): string
{
// Compatibility for older queued jobs. Never infer notify: it also serves replies/tests.
return $params['purpose'] ?? match ($params['template_name'] ?? '') {
'verify' => 'verification', 'mailLogin' => 'login_link',
'remindTraffic' => 'traffic_reminder', 'remindExpire' => 'expiry_reminder',
default => 'other',
};
}
public static function decision(string $purpose, array $context = []): ?array
{
if (!in_array($purpose, ['traffic_reminder', 'expiry_reminder', 'automatic_broadcast', 'service_expired'], true)) {
return null; // Includes manual broadcast, all responses and explicitly classified admin tests.
}
$state = self::state();
if ($state === null) {
return ['status' => 'pending', 'error' => 'automatic_mail_policy_unavailable', 'retryable' => true];
}
$allowed = $purpose === 'service_expired' ? $state['level'] === 2 : $state['level'] === 1;
if (!$allowed) { return self::blocked('automatic_mail_level_blocked'); }
if ($purpose === 'service_expired') {
$due = (int) ($context['subscription_expired_at'] ?? 0);
try {
$user = User::find($context['user_id'] ?? 0);
$reminders = Setting::where('name', 'remind_mail_enable')->first()?->value;
} catch (\Throwable $e) {
return ['status' => 'pending', 'error' => 'subscription_expiry_check_unavailable', 'retryable' => true];
}
if (!$state['since'] || $due <= $state['since'] || $due > now()->timestamp
|| !$reminders || !$user || !$user->remind_expire || $user->banned || !$user->plan_id
|| (int) $user->expired_at !== $due) {
return self::blocked('subscription_expiry_no_longer_valid');
}
}
return null;
}
private static function blocked(string $error): array
{
return ['status' => 'policy_blocked', 'error' => $error, 'retryable' => false];
}
}