- Added fonts:sync-google command (uses GOOGLE_FONTS_API_KEY, generates /public/fonts/google files, manifest, CSS, cache flush) and
exposed manifest via new GET /api/v1/tenant/fonts endpoint with fallbacks for existing local fonts.
- Imported generated fonts CSS, added API client + font loader hook, and wired branding page font fields to searchable selects (with
custom override) that auto-load selected fonts.
- Invites layout editor now offers font selection per element with runtime font loading for previews/export alignment.
- New tests cover font sync command and font manifest API.
Tests run: php artisan test --filter=Fonts --testsuite=Feature.
Note: repository already has other modified files (e.g., EventPublicController, SettingsStoreRequest, guest components, etc.); left
untouched. Run php artisan fonts:sync-google after setting the API key to populate /public/fonts/google.
105 lines
3.7 KiB
PHP
105 lines
3.7 KiB
PHP
<?php
|
|
|
|
namespace App\Support;
|
|
|
|
use App\Models\Event;
|
|
use Illuminate\Support\Arr;
|
|
use Illuminate\Support\Facades\Schema;
|
|
|
|
class WatermarkConfigResolver
|
|
{
|
|
public static function determineBrandingAllowed(Event $event): bool
|
|
{
|
|
$event->loadMissing('eventPackage.package', 'eventPackages.package');
|
|
|
|
$package = $event->eventPackage?->package;
|
|
|
|
if (! $package && $event->relationLoaded('eventPackages')) {
|
|
$package = $event->eventPackages->first()?->package;
|
|
}
|
|
|
|
return $package?->branding_allowed === true;
|
|
}
|
|
|
|
public static function determinePolicy(Event $event): string
|
|
{
|
|
$event->loadMissing('eventPackage.package');
|
|
|
|
return $event->eventPackage?->package?->watermark_allowed === false ? 'none' : 'basic';
|
|
}
|
|
|
|
/**
|
|
* @return array{type:string, policy:string, asset?:string, position?:string, opacity?:float, scale?:float, padding?:int}
|
|
*/
|
|
public static function resolve(Event $event): array
|
|
{
|
|
$policy = self::determinePolicy($event);
|
|
|
|
if ($policy === 'none') {
|
|
return [
|
|
'type' => 'none',
|
|
'policy' => $policy,
|
|
];
|
|
}
|
|
|
|
$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),
|
|
];
|
|
|
|
$event->loadMissing('eventPackage.package', 'tenant');
|
|
$brandingAllowed = self::determineBrandingAllowed($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 === 'off' && $policy === 'basic') {
|
|
$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);
|
|
|
|
$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),
|
|
'serve_originals' => $serveOriginals,
|
|
];
|
|
}
|
|
}
|