175 lines
5.0 KiB
PHP
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))
|
|
)));
|
|
}
|
|
}
|