238 lines
7.5 KiB
PHP
238 lines
7.5 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\Artwork;
|
|
use App\Models\Collection;
|
|
use App\Models\Group;
|
|
use App\Models\HomepageAnnouncement;
|
|
use App\Models\User;
|
|
use App\Models\World;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\Route;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use cPad\Plugins\News\Models\NewsArticle;
|
|
|
|
class HomepageAnnouncementService
|
|
{
|
|
public const ACTIVE_CACHE_KEY = 'skinbase:homepage:announcement:active';
|
|
|
|
public function __construct(private readonly HomepageAnnouncementSanitizer $sanitizer)
|
|
{
|
|
}
|
|
|
|
public function getActiveForHomepage(): ?HomepageAnnouncement
|
|
{
|
|
return Cache::remember(self::ACTIVE_CACHE_KEY, now()->addMinutes(5), function (): ?HomepageAnnouncement {
|
|
return HomepageAnnouncement::query()
|
|
->published()
|
|
->active()
|
|
->visibleNow()
|
|
->forPlacement(HomepageAnnouncement::PLACEMENT_HOMEPAGE_AFTER_FEATURED)
|
|
->orderByDesc('priority')
|
|
->orderByDesc('starts_at')
|
|
->first();
|
|
});
|
|
}
|
|
|
|
public function clearActiveCache(): void
|
|
{
|
|
Cache::forget(self::ACTIVE_CACHE_KEY);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $attributes
|
|
* @return array<string, mixed>
|
|
*/
|
|
public function sanitizeAttributes(array $attributes): array
|
|
{
|
|
foreach (['primary_link_url', 'secondary_link_url'] as $key) {
|
|
$attributes[$key] = $this->sanitizer->sanitizeCustomUrl($attributes[$key] ?? null);
|
|
}
|
|
|
|
$attributes['content_html'] = $this->sanitizer->sanitizeHtml($attributes['content_html'] ?? null);
|
|
|
|
return $attributes;
|
|
}
|
|
|
|
public function toHomepagePayload(?HomepageAnnouncement $announcement): ?array
|
|
{
|
|
if (! $announcement) {
|
|
return null;
|
|
}
|
|
|
|
return [
|
|
'id' => (int) $announcement->id,
|
|
'dismiss_version' => max(1, (int) $announcement->dismiss_version),
|
|
'title' => (string) $announcement->title,
|
|
'subtitle' => $announcement->subtitle,
|
|
'badge_text' => $announcement->badge_text,
|
|
'content_html' => $announcement->content_html,
|
|
'gradient_preset' => $announcement->gradient_preset,
|
|
'theme_preset' => $announcement->theme_preset,
|
|
'background_image_url' => $this->resolveBackgroundImageUrl($announcement->background_image),
|
|
'is_dismissible' => (bool) $announcement->is_dismissible,
|
|
'overlay_opacity' => (int) ($announcement->overlay_opacity ?? 55),
|
|
'primary_link' => $this->buildLinkPayload($announcement, 'primary'),
|
|
'secondary_link' => $this->buildLinkPayload($announcement, 'secondary'),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $attributes
|
|
*/
|
|
public function previewPayload(array $attributes): ?array
|
|
{
|
|
$announcement = new HomepageAnnouncement();
|
|
$announcement->forceFill($this->sanitizeAttributes($attributes));
|
|
$announcement->id = 0;
|
|
|
|
return $this->toHomepagePayload($announcement);
|
|
}
|
|
|
|
private function buildLinkPayload(HomepageAnnouncement $announcement, string $prefix): ?array
|
|
{
|
|
$label = trim((string) $announcement->getAttribute($prefix . '_link_label'));
|
|
if ($label === '') {
|
|
return null;
|
|
}
|
|
|
|
$url = $this->resolveLinkUrl($announcement, $prefix);
|
|
if ($url === null) {
|
|
return null;
|
|
}
|
|
|
|
return [
|
|
'label' => $label,
|
|
'url' => $url,
|
|
];
|
|
}
|
|
|
|
private function resolveLinkUrl(HomepageAnnouncement $announcement, string $prefix): ?string
|
|
{
|
|
$type = (string) ($announcement->getAttribute($prefix . '_link_type') ?? HomepageAnnouncement::LINK_TYPE_NONE);
|
|
$url = $this->sanitizer->sanitizeCustomUrl($announcement->getAttribute($prefix . '_link_url'));
|
|
$targetId = (int) ($announcement->getAttribute($prefix . '_link_target_id') ?? 0);
|
|
|
|
return match ($type) {
|
|
HomepageAnnouncement::LINK_TYPE_NONE => null,
|
|
HomepageAnnouncement::LINK_TYPE_CUSTOM_URL => $url,
|
|
HomepageAnnouncement::LINK_TYPE_EXPLORE => Route::has('explore.index') ? route('explore.index') : ($url ?? '/explore'),
|
|
HomepageAnnouncement::LINK_TYPE_UPLOAD => Route::has('upload') ? route('upload') : ($url ?? '/upload'),
|
|
HomepageAnnouncement::LINK_TYPE_NEWS => $this->newsUrl($targetId) ?? $url,
|
|
HomepageAnnouncement::LINK_TYPE_WORLD => $this->worldUrl($targetId) ?? $url,
|
|
HomepageAnnouncement::LINK_TYPE_COLLECTION => $this->collectionUrl($targetId) ?? $url,
|
|
HomepageAnnouncement::LINK_TYPE_GROUP => $this->groupUrl($targetId) ?? $url,
|
|
HomepageAnnouncement::LINK_TYPE_PROFILE => $this->profileUrl($targetId) ?? $url,
|
|
HomepageAnnouncement::LINK_TYPE_ARTWORK => $this->artworkUrl($targetId) ?? $url,
|
|
default => $url,
|
|
};
|
|
}
|
|
|
|
private function resolveBackgroundImageUrl(?string $backgroundImage): ?string
|
|
{
|
|
$backgroundImage = trim((string) $backgroundImage);
|
|
if ($backgroundImage === '') {
|
|
return null;
|
|
}
|
|
|
|
if (str_starts_with($backgroundImage, 'http://') || str_starts_with($backgroundImage, 'https://') || str_starts_with($backgroundImage, '/')) {
|
|
return $backgroundImage;
|
|
}
|
|
|
|
return Storage::disk('public')->url($backgroundImage);
|
|
}
|
|
|
|
private function artworkUrl(int $artworkId): ?string
|
|
{
|
|
if ($artworkId < 1) {
|
|
return null;
|
|
}
|
|
|
|
$artwork = Artwork::query()->find($artworkId);
|
|
if (! $artwork) {
|
|
return null;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function newsUrl(int $articleId): ?string
|
|
{
|
|
if ($articleId < 1) {
|
|
return null;
|
|
}
|
|
|
|
$article = NewsArticle::query()->find($articleId);
|
|
if (! $article) {
|
|
return null;
|
|
}
|
|
|
|
return route('news.show', ['slug' => $article->slug]);
|
|
}
|
|
|
|
private function worldUrl(int $worldId): ?string
|
|
{
|
|
if ($worldId < 1) {
|
|
return null;
|
|
}
|
|
|
|
$world = World::query()->find($worldId);
|
|
if (! $world) {
|
|
return null;
|
|
}
|
|
|
|
if (method_exists($world, 'publicUrl')) {
|
|
return $world->publicUrl();
|
|
}
|
|
|
|
return route('worlds.show', ['world' => $world->slug]);
|
|
}
|
|
|
|
private function collectionUrl(int $collectionId): ?string
|
|
{
|
|
if ($collectionId < 1) {
|
|
return null;
|
|
}
|
|
|
|
$collection = Collection::query()->with('user')->find($collectionId);
|
|
if (! $collection || ! $collection->user?->username) {
|
|
return null;
|
|
}
|
|
|
|
return route('profile.collections.show', [
|
|
'username' => strtolower((string) $collection->user->username),
|
|
'slug' => $collection->slug,
|
|
]);
|
|
}
|
|
|
|
private function groupUrl(int $groupId): ?string
|
|
{
|
|
if ($groupId < 1) {
|
|
return null;
|
|
}
|
|
|
|
$group = Group::query()->find($groupId);
|
|
if (! $group) {
|
|
return null;
|
|
}
|
|
|
|
return route('groups.show', ['group' => $group]);
|
|
}
|
|
|
|
private function profileUrl(int $userId): ?string
|
|
{
|
|
if ($userId < 1) {
|
|
return null;
|
|
}
|
|
|
|
$user = User::query()->find($userId);
|
|
if (! $user?->username) {
|
|
return null;
|
|
}
|
|
|
|
return route('profile.show', ['username' => strtolower((string) $user->username)]);
|
|
}
|
|
} |