166 lines
5.2 KiB
PHP
166 lines
5.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services\WebStories;
|
|
|
|
use App\Models\Artwork;
|
|
use App\Models\World;
|
|
use App\Models\WorldSubmission;
|
|
use App\Models\WorldWebStory;
|
|
use App\Models\WorldWebStoryPage;
|
|
use App\Services\ThumbnailPresenter;
|
|
|
|
final class WorldWebStoryAssetService
|
|
{
|
|
public function defaultPublisherLogoPath(): string
|
|
{
|
|
return 'https://cdn.skinbase.org/images/skinbase_logo_96.webp';
|
|
}
|
|
|
|
/**
|
|
* @return array{updated: bool, story: array<string, string>, pages: array<int, array<string, mixed>>}
|
|
*/
|
|
public function buildAssets(WorldWebStory $story, bool $force = false, bool $dryRun = false): array
|
|
{
|
|
$story->loadMissing(['world', 'orderedPages.artwork']);
|
|
$world = $story->world;
|
|
$storyChanges = [];
|
|
$pageChanges = [];
|
|
|
|
$primaryImage = $this->bestWorldImage($story);
|
|
|
|
if (($force || blank($story->poster_portrait_path)) && filled($primaryImage)) {
|
|
$storyChanges['poster_portrait_path'] = $primaryImage;
|
|
}
|
|
|
|
if (($force || blank($story->poster_square_path)) && filled($primaryImage)) {
|
|
$storyChanges['poster_square_path'] = $primaryImage;
|
|
}
|
|
|
|
if ($force || blank($story->publisher_logo_path)) {
|
|
$storyChanges['publisher_logo_path'] = $this->defaultPublisherLogoPath();
|
|
}
|
|
|
|
foreach ($story->orderedPages as $page) {
|
|
$changes = [];
|
|
$background = $this->bestPageBackground($page, $world, $primaryImage);
|
|
|
|
if (($force || blank($page->background_path)) && filled($background)) {
|
|
$changes['background_path'] = $background;
|
|
}
|
|
|
|
if (($force || blank($page->background_mobile_path)) && filled($background)) {
|
|
$changes['background_mobile_path'] = $background;
|
|
}
|
|
|
|
if (($force || blank($page->alt_text)) && filled($page->headline)) {
|
|
$changes['alt_text'] = (string) $page->headline;
|
|
}
|
|
|
|
if ($changes !== []) {
|
|
$pageChanges[(int) $page->id] = $changes;
|
|
if (! $dryRun) {
|
|
$page->forceFill($changes)->save();
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($storyChanges !== [] && ! $dryRun) {
|
|
$story->forceFill($storyChanges)->save();
|
|
}
|
|
|
|
return [
|
|
'updated' => $storyChanges !== [] || $pageChanges !== [],
|
|
'story' => $storyChanges,
|
|
'pages' => $pageChanges,
|
|
];
|
|
}
|
|
|
|
public function storyBasePath(WorldWebStory $story): string
|
|
{
|
|
$slug = trim((string) ($story->world?->slug ?: $story->slug));
|
|
|
|
return 'web-stories/worlds/' . $slug;
|
|
}
|
|
|
|
private function bestWorldImage(WorldWebStory $story): ?string
|
|
{
|
|
$world = $story->world;
|
|
|
|
if ($world instanceof World) {
|
|
foreach ([$world->ogImageUrl(), $world->coverUrl(), $world->teaserImageUrl()] as $candidate) {
|
|
if (filled($candidate)) {
|
|
return (string) $candidate;
|
|
}
|
|
}
|
|
|
|
$artwork = $this->bestWorldArtwork($world);
|
|
if ($artwork instanceof Artwork) {
|
|
return $this->artworkImage($artwork);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function bestPageBackground(WorldWebStoryPage $page, ?World $world, ?string $fallback): ?string
|
|
{
|
|
if ($page->artwork instanceof Artwork) {
|
|
$artworkImage = $this->artworkImage($page->artwork);
|
|
if (filled($artworkImage)) {
|
|
return $artworkImage;
|
|
}
|
|
}
|
|
|
|
if ($world instanceof World) {
|
|
$artwork = $this->bestWorldArtwork($world);
|
|
if ($artwork instanceof Artwork) {
|
|
$artworkImage = $this->artworkImage($artwork);
|
|
if (filled($artworkImage)) {
|
|
return $artworkImage;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $fallback;
|
|
}
|
|
|
|
private function bestWorldArtwork(World $world): ?Artwork
|
|
{
|
|
$relatedArtworkIds = $world->worldRelations()
|
|
->where('related_type', 'artwork')
|
|
->orderByDesc('is_featured')
|
|
->orderBy('sort_order')
|
|
->pluck('related_id')
|
|
->map(fn ($id) => (int) $id)
|
|
->filter()
|
|
->values();
|
|
|
|
if ($relatedArtworkIds->isNotEmpty()) {
|
|
return Artwork::query()
|
|
->whereIn('id', $relatedArtworkIds)
|
|
->get()
|
|
->sortBy(fn (Artwork $artwork): int => (int) ($relatedArtworkIds->search((int) $artwork->id) ?? PHP_INT_MAX))
|
|
->first();
|
|
}
|
|
|
|
$submission = WorldSubmission::query()
|
|
->with('artwork')
|
|
->where('world_id', $world->id)
|
|
->where('status', WorldSubmission::STATUS_LIVE)
|
|
->orderByDesc('is_featured')
|
|
->orderByDesc('featured_at')
|
|
->orderByDesc('id')
|
|
->first();
|
|
|
|
return $submission?->artwork;
|
|
}
|
|
|
|
private function artworkImage(Artwork $artwork): ?string
|
|
{
|
|
$preview = ThumbnailPresenter::present($artwork, 'xl');
|
|
|
|
return (string) ($preview['url'] ?? $artwork->thumbnail_url ?? $artwork->thumb_url ?? '');
|
|
}
|
|
} |