179 lines
4.3 KiB
PHP
179 lines
4.3 KiB
PHP
<?php
|
|
namespace App\Models;
|
|
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
use Illuminate\Database\Eloquent\Relations\HasOne;
|
|
|
|
/**
|
|
* App\Models\Artwork
|
|
*
|
|
* @property-read User $user
|
|
* @property-read \Illuminate\Database\Eloquent\Collection|ArtworkTranslation[] $translations
|
|
* @property-read ArtworkStats $stats
|
|
* @property-read \Illuminate\Database\Eloquent\Collection|ArtworkComment[] $comments
|
|
* @property-read \Illuminate\Database\Eloquent\Collection|ArtworkDownload[] $downloads
|
|
*/
|
|
class Artwork extends Model
|
|
{
|
|
use HasFactory, SoftDeletes;
|
|
|
|
protected $table = 'artworks';
|
|
|
|
protected $fillable = [
|
|
'user_id',
|
|
'title',
|
|
'slug',
|
|
'description',
|
|
'file_name',
|
|
'file_path',
|
|
'hash',
|
|
'file_ext',
|
|
'thumb_ext',
|
|
'file_size',
|
|
'mime_type',
|
|
'width',
|
|
'height',
|
|
'is_public',
|
|
'is_approved',
|
|
'published_at',
|
|
'hash',
|
|
'thumb_ext',
|
|
'file_ext'
|
|
];
|
|
|
|
protected $casts = [
|
|
'is_public' => 'boolean',
|
|
'is_approved' => 'boolean',
|
|
'published_at' => 'datetime',
|
|
];
|
|
|
|
/**
|
|
* Thumbnail sizes and their options.
|
|
* Keys are the size dir used in the CDN URL.
|
|
*/
|
|
protected const THUMB_SIZES = [
|
|
'sm' => ['height' => 240, 'quality' => 78, 'dir' => 'sm'],
|
|
'md' => ['height' => 360, 'quality' => 82, 'dir' => 'md'],
|
|
'lg' => ['height' => 1200, 'quality' => 85, 'dir' => 'lg'],
|
|
'xl' => ['height' => 2400, 'quality' => 90, 'dir' => 'xl'],
|
|
];
|
|
|
|
/**
|
|
* Build the thumbnail URL for this artwork.
|
|
* Returns null when no hash or thumb_ext is available.
|
|
*/
|
|
public function thumbUrl(string $size = 'md'): ?string
|
|
{
|
|
if (empty($this->hash) || empty($this->thumb_ext)) {
|
|
return null;
|
|
}
|
|
|
|
$size = array_key_exists($size, self::THUMB_SIZES) ? $size : 'md';
|
|
$h = $this->hash;
|
|
$h1 = substr($h, 0, 2);
|
|
$h2 = substr($h, 2, 2);
|
|
$ext = $this->thumb_ext;
|
|
|
|
return "https://files.skinbase.org/{$size}/{$h1}/{$h2}/{$h}.{$ext}";
|
|
}
|
|
|
|
/**
|
|
* Accessor for `$art->thumb` used in legacy views (default medium size).
|
|
*/
|
|
public function getThumbAttribute(): string
|
|
{
|
|
return $this->thumbUrl('md') ?? '/gfx/sb_join.jpg';
|
|
}
|
|
|
|
/**
|
|
* Accessor for `$art->thumb_url` used in some views.
|
|
*/
|
|
public function getThumbUrlAttribute(): ?string
|
|
{
|
|
return $this->thumbUrl('md');
|
|
}
|
|
|
|
/**
|
|
* Provide a responsive `srcset` for legacy views.
|
|
*/
|
|
public function getThumbSrcsetAttribute(): ?string
|
|
{
|
|
if (empty($this->hash) || empty($this->thumb_ext)) return null;
|
|
$sm = $this->thumbUrl('sm');
|
|
$md = $this->thumbUrl('md');
|
|
if (!$sm || !$md) return null;
|
|
return $sm . ' 320w, ' . $md . ' 600w';
|
|
}
|
|
|
|
// Relations
|
|
public function user(): BelongsTo
|
|
{
|
|
return $this->belongsTo(User::class);
|
|
}
|
|
|
|
public function translations(): HasMany
|
|
{
|
|
return $this->hasMany(ArtworkTranslation::class);
|
|
}
|
|
|
|
public function stats(): HasOne
|
|
{
|
|
return $this->hasOne(ArtworkStats::class, 'artwork_id');
|
|
}
|
|
|
|
public function categories(): BelongsToMany
|
|
{
|
|
return $this->belongsToMany(Category::class, 'artwork_category', 'artwork_id', 'category_id');
|
|
}
|
|
|
|
public function comments(): HasMany
|
|
{
|
|
return $this->hasMany(ArtworkComment::class);
|
|
}
|
|
|
|
public function downloads(): HasMany
|
|
{
|
|
return $this->hasMany(ArtworkDownload::class);
|
|
}
|
|
|
|
public function features(): HasMany
|
|
{
|
|
return $this->hasMany(ArtworkFeature::class, 'artwork_id');
|
|
}
|
|
|
|
// Scopes
|
|
public function scopePublic(Builder $query): Builder
|
|
{
|
|
// Compose approved() so behavior is consistent and composable
|
|
$table = $this->getTable();
|
|
return $query->approved()->where("{$table}.is_public", true);
|
|
}
|
|
|
|
public function scopeApproved(Builder $query): Builder
|
|
{
|
|
// Respect soft deletes and mark approved content
|
|
$table = $this->getTable();
|
|
return $query->whereNull("{$table}.deleted_at")->where("{$table}.is_approved", true);
|
|
}
|
|
|
|
public function scopePublished(Builder $query): Builder
|
|
{
|
|
// Respect soft deletes and only include published items up to now
|
|
$table = $this->getTable();
|
|
return $query->whereNull("{$table}.deleted_at")
|
|
->whereNotNull("{$table}.published_at")
|
|
->where("{$table}.published_at", '<=', now());
|
|
}
|
|
|
|
public function getRouteKeyName(): string
|
|
{
|
|
return 'slug';
|
|
}
|
|
}
|