loadMissing('eventPackage.package', 'eventPackages.package'); $package = $event->eventPackage?->package; if (! $package && $event->relationLoaded('eventPackages')) { $package = $event->eventPackages->first()?->package; } // If no package is attached, default to allowing branding to avoid silently stripping event/tenant branding. if (! $package) { return true; } return $package->branding_allowed !== false; } public static function determinePolicy(Event $event): string { $event->loadMissing('eventPackage.package'); return self::determineRemovalAllowed($event) ? 'none' : 'basic'; } public static function determineRemovalAllowed(Event $event): bool { $event->loadMissing('eventPackage.package', 'eventPackages.package'); $package = $event->eventPackage?->package; if (! $package && $event->relationLoaded('eventPackages')) { $package = $event->eventPackages->first()?->package; } return self::packageHasFeature($package, 'no_watermark') || self::packageHasFeature($package, 'watermark_removal'); } /** * @return array{type:string, policy:string, asset?:string, position?:string, opacity?:float, scale?:float, padding?:int, offset_x?:int, offset_y?:int} */ public static function resolve(Event $event): array { $policy = self::determinePolicy($event); $baseSetting = null; if (class_exists(\App\Models\WatermarkSetting::class) && \Illuminate\Support\Facades\Schema::hasTable('watermark_settings')) { try { $baseSetting = \App\Models\WatermarkSetting::query()->first(); } catch (\Throwable) { $baseSetting = null; } } $base = [ 'asset' => $baseSetting?->asset ?? config('watermark.base.asset', 'branding/fotospiel-watermark.png'), 'position' => $baseSetting?->position ?? config('watermark.base.position', 'bottom-right'), 'opacity' => $baseSetting?->opacity ?? config('watermark.base.opacity', 0.25), 'scale' => $baseSetting?->scale ?? config('watermark.base.scale', 0.2), 'padding' => $baseSetting?->padding ?? config('watermark.base.padding', 16), 'offset_x' => $baseSetting?->offset_x ?? config('watermark.base.offset_x', 0), 'offset_y' => $baseSetting?->offset_y ?? config('watermark.base.offset_y', 0), ]; $event->loadMissing('eventPackage.package', 'eventPackages.package', 'tenant'); $package = $event->eventPackage?->package; if (! $package && $event->relationLoaded('eventPackages')) { $package = $event->eventPackages->first()?->package; } $brandingAllowed = self::determineBrandingAllowed($event); $watermarkAllowed = $package?->watermark_allowed !== false; $removalAllowed = self::determineRemovalAllowed($event); $eventWatermark = Arr::get($event->settings, 'watermark', []); $tenantWatermark = Arr::get($event->tenant?->settings, 'watermark', []); $serveOriginals = (bool) Arr::get($event->settings, 'watermark_serve_originals', false); $mode = $brandingAllowed ? ($eventWatermark['mode'] ?? $tenantWatermark['mode'] ?? 'base') : 'base'; if ($mode === 'custom' && (! $brandingAllowed || ! $watermarkAllowed)) { $mode = 'base'; } if ($mode === 'off' && ! $removalAllowed) { $mode = 'base'; } if ($mode === 'off') { return [ 'type' => 'none', 'policy' => $policy, ]; } $source = $mode === 'custom' && $brandingAllowed ? ($eventWatermark ?: $tenantWatermark) : []; $asset = $source['asset'] ?? $base['asset'] ?? null; $position = $source['position'] ?? $base['position'] ?? 'bottom-right'; $opacity = (float) ($source['opacity'] ?? $base['opacity'] ?? 0.25); $scale = (float) ($source['scale'] ?? $base['scale'] ?? 0.2); $padding = (int) ($source['padding'] ?? $base['padding'] ?? 16); $offsetX = (int) ($source['offset_x'] ?? $base['offset_x'] ?? 0); $offsetY = (int) ($source['offset_y'] ?? $base['offset_y'] ?? 0); $clamp = static fn (float $value, float $min, float $max) => max($min, min($max, $value)); return [ 'type' => $mode === 'custom' && $brandingAllowed ? 'custom' : 'base', 'policy' => $policy, 'asset' => $asset, 'position' => $position, 'opacity' => $clamp($opacity, 0.0, 1.0), 'scale' => $clamp($scale, 0.05, 1.0), 'padding' => max(0, $padding), 'offset_x' => max(-500, min(500, $offsetX)), 'offset_y' => max(-500, min(500, $offsetY)), 'serve_originals' => $serveOriginals, ]; } private static function packageHasFeature(?Package $package, string $feature): bool { if (! $package) { return false; } $features = $package->features ?? []; if (is_string($features)) { $decoded = json_decode($features, true); if (json_last_error() === JSON_ERROR_NONE) { $features = $decoded; } } if (! is_array($features)) { return false; } if (array_is_list($features)) { return in_array($feature, $features, true); } return isset($features[$feature]) && (bool) $features[$feature]; } }