Files
fotospiel-app/app/Services/Addons/EventFeatureEntitlementService.php
Codex Agent d2808ffa4f
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled
feat(addons): finalize event addon catalog and ai styling upgrade flow
2026-02-07 12:35:07 +01:00

175 lines
5.0 KiB
PHP

<?php
namespace App\Services\Addons;
use App\Models\Event;
use App\Models\EventPackage;
use App\Models\EventPackageAddon;
use App\Models\Package;
use Carbon\CarbonImmutable;
use Illuminate\Support\Arr;
class EventFeatureEntitlementService
{
/**
* @param array<int, string> $addonKeys
* @return array{
* allowed: bool,
* granted_by: 'package'|'addon'|null,
* required_feature: string,
* addon_keys: array<int, string>
* }
*/
public function resolveForEvent(Event $event, string $requiredFeature, array $addonKeys = []): array
{
$eventPackage = $this->resolveEventPackage($event);
if (! $eventPackage) {
return [
'allowed' => false,
'granted_by' => null,
'required_feature' => $requiredFeature,
'addon_keys' => $addonKeys,
];
}
if ($this->packageGrantsFeature($eventPackage->package, $requiredFeature)) {
return [
'allowed' => true,
'granted_by' => 'package',
'required_feature' => $requiredFeature,
'addon_keys' => $addonKeys,
];
}
if ($this->addonGrantsFeature($eventPackage, $addonKeys, $requiredFeature)) {
return [
'allowed' => true,
'granted_by' => 'addon',
'required_feature' => $requiredFeature,
'addon_keys' => $addonKeys,
];
}
return [
'allowed' => false,
'granted_by' => null,
'required_feature' => $requiredFeature,
'addon_keys' => $addonKeys,
];
}
private function resolveEventPackage(Event $event): ?EventPackage
{
$event->loadMissing('eventPackage.package', 'eventPackage.addons');
if ($event->eventPackage) {
return $event->eventPackage;
}
return $event->eventPackages()
->with(['package', 'addons'])
->orderByDesc('purchased_at')
->orderByDesc('id')
->first();
}
private function packageGrantsFeature(?Package $package, string $requiredFeature): bool
{
if (! $package) {
return false;
}
return in_array($requiredFeature, $this->normalizeFeatureList($package->features), true);
}
/**
* @param array<int, string> $addonKeys
*/
private function addonGrantsFeature(EventPackage $eventPackage, array $addonKeys, string $requiredFeature): bool
{
$addons = $eventPackage->relationLoaded('addons')
? $eventPackage->addons
: $eventPackage->addons()
->where('status', 'completed')
->get();
return $addons->contains(function (EventPackageAddon $addon) use ($addonKeys, $requiredFeature): bool {
if (! $this->addonIsActive($addon)) {
return false;
}
if ($addonKeys !== [] && in_array((string) $addon->addon_key, $addonKeys, true)) {
return true;
}
$metadataFeatures = $this->normalizeFeatureList(
Arr::get($addon->metadata ?? [], 'entitlements.features', Arr::get($addon->metadata ?? [], 'features', []))
);
return in_array($requiredFeature, $metadataFeatures, true);
});
}
private function addonIsActive(EventPackageAddon $addon): bool
{
if ($addon->status !== 'completed') {
return false;
}
$expiryCandidates = [
Arr::get($addon->metadata ?? [], 'entitlements.expires_at'),
Arr::get($addon->metadata ?? [], 'expires_at'),
Arr::get($addon->metadata ?? [], 'valid_until'),
];
foreach ($expiryCandidates as $candidate) {
if (! is_string($candidate) || trim($candidate) === '') {
continue;
}
try {
$expiresAt = CarbonImmutable::parse($candidate);
} catch (\Throwable) {
continue;
}
if ($expiresAt->isPast()) {
return false;
}
}
return true;
}
/**
* @return array<int, string>
*/
private function normalizeFeatureList(mixed $value): array
{
if (is_string($value)) {
$decoded = json_decode($value, true);
if (json_last_error() === JSON_ERROR_NONE) {
$value = $decoded;
}
}
if (! is_array($value)) {
return [];
}
if (array_is_list($value)) {
return array_values(array_filter(array_map(
static fn (mixed $feature): string => trim((string) $feature),
$value
)));
}
return array_values(array_filter(array_map(
static fn (mixed $feature): string => trim((string) $feature),
array_keys(array_filter($value, static fn (mixed $enabled): bool => (bool) $enabled))
)));
}
}