Add Artwork model convenience methods for featured thumbnails
This commit is contained in:
@@ -1,19 +1,21 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use App\Models\ArtworkContributor;
|
use App\Services\ThumbnailService;
|
||||||
use App\Models\Group;
|
use App\Support\ArtworkFeaturedImagePath;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||||
use App\Services\ThumbnailService;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Laravel\Scout\Searchable;
|
use Laravel\Scout\Searchable;
|
||||||
|
use Laravel\Scout\SearchableScope;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* App\Models\Artwork
|
* App\Models\Artwork
|
||||||
@@ -26,7 +28,7 @@ use Laravel\Scout\Searchable;
|
|||||||
*/
|
*/
|
||||||
class Artwork extends Model
|
class Artwork extends Model
|
||||||
{
|
{
|
||||||
use HasFactory, SoftDeletes, Searchable;
|
use HasFactory, Searchable, SoftDeletes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Override Scout's bootSearchable to skip the ModelObserver (which fires MakeSearchable
|
* Override Scout's bootSearchable to skip the ModelObserver (which fires MakeSearchable
|
||||||
@@ -36,16 +38,19 @@ class Artwork extends Model
|
|||||||
*/
|
*/
|
||||||
public static function bootSearchable(): void
|
public static function bootSearchable(): void
|
||||||
{
|
{
|
||||||
static::addGlobalScope(new \Laravel\Scout\SearchableScope);
|
static::addGlobalScope(new SearchableScope);
|
||||||
(new static)->registerSearchableMacros();
|
(new static)->registerSearchableMacros();
|
||||||
// ModelObserver intentionally omitted — indexing is handled by IndexArtworkJob.
|
// ModelObserver intentionally omitted — indexing is handled by IndexArtworkJob.
|
||||||
}
|
}
|
||||||
|
|
||||||
public const PUBLISHED_AS_USER = 'user';
|
public const PUBLISHED_AS_USER = 'user';
|
||||||
|
|
||||||
public const PUBLISHED_AS_GROUP = 'group';
|
public const PUBLISHED_AS_GROUP = 'group';
|
||||||
|
|
||||||
public const VISIBILITY_PUBLIC = 'public';
|
public const VISIBILITY_PUBLIC = 'public';
|
||||||
|
|
||||||
public const VISIBILITY_UNLISTED = 'unlisted';
|
public const VISIBILITY_UNLISTED = 'unlisted';
|
||||||
|
|
||||||
public const VISIBILITY_PRIVATE = 'private';
|
public const VISIBILITY_PRIVATE = 'private';
|
||||||
|
|
||||||
protected $table = 'artworks';
|
protected $table = 'artworks';
|
||||||
@@ -187,6 +192,7 @@ class Artwork extends Model
|
|||||||
}
|
}
|
||||||
|
|
||||||
$sizeKey = array_key_exists($size, self::THUMB_SIZES) ? $size : 'md';
|
$sizeKey = array_key_exists($size, self::THUMB_SIZES) ? $size : 'md';
|
||||||
|
|
||||||
return ThumbnailService::fromHash($this->hash, $this->thumb_ext, $sizeKey);
|
return ThumbnailService::fromHash($this->hash, $this->thumb_ext, $sizeKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,9 +219,14 @@ class Artwork extends Model
|
|||||||
public function getThumbnailUrlAttribute(): ?string
|
public function getThumbnailUrlAttribute(): ?string
|
||||||
{
|
{
|
||||||
$url = $this->getThumbUrlAttribute();
|
$url = $this->getThumbUrlAttribute();
|
||||||
if (!empty($url)) return $url;
|
if (! empty($url)) {
|
||||||
|
return $url;
|
||||||
|
}
|
||||||
$thumb = $this->getThumbAttribute();
|
$thumb = $this->getThumbAttribute();
|
||||||
if (!empty($thumb)) return $thumb;
|
if (! empty($thumb)) {
|
||||||
|
return $thumb;
|
||||||
|
}
|
||||||
|
|
||||||
return '/images/placeholder.jpg';
|
return '/images/placeholder.jpg';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,13 +235,90 @@ class Artwork extends Model
|
|||||||
*/
|
*/
|
||||||
public function getThumbSrcsetAttribute(): ?string
|
public function getThumbSrcsetAttribute(): ?string
|
||||||
{
|
{
|
||||||
if (empty($this->hash) || empty($this->thumb_ext)) return null;
|
if (empty($this->hash) || empty($this->thumb_ext)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
$sm = $this->thumbUrl('sm');
|
$sm = $this->thumbUrl('sm');
|
||||||
$md = $this->thumbUrl('md');
|
$md = $this->thumbUrl('md');
|
||||||
if (!$sm || !$md) return null;
|
if (! $sm || ! $md) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return $sm.' 320w, '.$md.' 600w';
|
return $sm.' 320w, '.$md.' 600w';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function featuredThumbnailObjectPath(string $variant = 'desktop'): ?string
|
||||||
|
{
|
||||||
|
if (empty($this->hash)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return app(ArtworkFeaturedImagePath::class)->objectPath($this, $variant);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function featuredThumbnailUrl(string $variant = 'desktop'): string
|
||||||
|
{
|
||||||
|
$helper = app(ArtworkFeaturedImagePath::class);
|
||||||
|
|
||||||
|
foreach ($helper->preferredVariantOrder($variant) as $candidate) {
|
||||||
|
if ($this->hasFeaturedThumbnail($candidate)) {
|
||||||
|
return $helper->url($this, $candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->thumbUrl('xl')
|
||||||
|
?? $this->thumbUrl('lg')
|
||||||
|
?? 'https://files.skinbase.org/default/missing_xl.webp';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasFeaturedThumbnail(?string $variant = null): bool
|
||||||
|
{
|
||||||
|
if (empty($this->hash)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$helper = app(ArtworkFeaturedImagePath::class);
|
||||||
|
$variants = $variant !== null ? [$helper->normalizeVariant($variant)] : $helper->variantNames();
|
||||||
|
$disk = Storage::disk((string) config('uploads.object_storage.disk', 's3'));
|
||||||
|
|
||||||
|
foreach ($variants as $variantName) {
|
||||||
|
if ($disk->exists($helper->objectPath($this, $variantName))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasAllFeaturedThumbnails(): bool
|
||||||
|
{
|
||||||
|
$helper = app(ArtworkFeaturedImagePath::class);
|
||||||
|
|
||||||
|
foreach ($helper->variantNames() as $variant) {
|
||||||
|
if (! $this->hasFeaturedThumbnail($variant)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function featuredImageAltText(): string
|
||||||
|
{
|
||||||
|
$title = trim((string) ($this->title ?? ''));
|
||||||
|
$author = trim((string) ($this->user?->name ?? ''));
|
||||||
|
|
||||||
|
if ($title !== '' && $author !== '') {
|
||||||
|
return sprintf('%s by %s', $title, $author);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($title !== '') {
|
||||||
|
return $title;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Featured artwork';
|
||||||
|
}
|
||||||
|
|
||||||
// Relations
|
// Relations
|
||||||
public function user(): BelongsTo
|
public function user(): BelongsTo
|
||||||
{
|
{
|
||||||
@@ -544,6 +632,7 @@ class Artwork extends Model
|
|||||||
{
|
{
|
||||||
// Compose approved() so behavior is consistent and composable
|
// Compose approved() so behavior is consistent and composable
|
||||||
$table = $this->getTable();
|
$table = $this->getTable();
|
||||||
|
|
||||||
return $query->approved()->where("{$table}.is_public", true);
|
return $query->approved()->where("{$table}.is_public", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -551,6 +640,7 @@ class Artwork extends Model
|
|||||||
{
|
{
|
||||||
// Respect soft deletes and mark approved content
|
// Respect soft deletes and mark approved content
|
||||||
$table = $this->getTable();
|
$table = $this->getTable();
|
||||||
|
|
||||||
return $query->whereNull("{$table}.deleted_at")->where("{$table}.is_approved", true);
|
return $query->whereNull("{$table}.deleted_at")->where("{$table}.is_approved", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -558,6 +648,7 @@ class Artwork extends Model
|
|||||||
{
|
{
|
||||||
// Respect soft deletes and only include published items up to now
|
// Respect soft deletes and only include published items up to now
|
||||||
$table = $this->getTable();
|
$table = $this->getTable();
|
||||||
|
|
||||||
return $query->whereNull("{$table}.deleted_at")
|
return $query->whereNull("{$table}.deleted_at")
|
||||||
->whereNotNull("{$table}.published_at")
|
->whereNotNull("{$table}.published_at")
|
||||||
->where("{$table}.published_at", '<=', now());
|
->where("{$table}.published_at", '<=', now());
|
||||||
@@ -583,7 +674,7 @@ class Artwork extends Model
|
|||||||
|
|
||||||
return $query
|
return $query
|
||||||
->whereRaw('COALESCE('.$table.'.is_mature, 0) = 0')
|
->whereRaw('COALESCE('.$table.'.is_mature, 0) = 0')
|
||||||
->whereRaw("COALESCE(" . $table . ".maturity_status, 'clear') != ?", ['suspected']);
|
->whereRaw('COALESCE('.$table.".maturity_status, 'clear') != ?", ['suspected']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function scopeWithoutMissingThumbnails(Builder $query): Builder
|
public function scopeWithoutMissingThumbnails(Builder $query): Builder
|
||||||
|
|||||||
Reference in New Issue
Block a user