290 lines
8.4 KiB
PHP
290 lines
8.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Models;
|
|
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
|
|
|
class World extends Model
|
|
{
|
|
use HasFactory;
|
|
use SoftDeletes;
|
|
|
|
public const STATUS_DRAFT = 'draft';
|
|
public const STATUS_PUBLISHED = 'published';
|
|
public const STATUS_ARCHIVED = 'archived';
|
|
|
|
public const TYPE_SEASONAL = 'seasonal';
|
|
public const TYPE_EVENT = 'event';
|
|
public const TYPE_CAMPAIGN = 'campaign';
|
|
public const TYPE_TRIBUTE = 'tribute';
|
|
|
|
public const PARTICIPATION_MODE_MANUAL_APPROVAL = 'manual_approval';
|
|
public const PARTICIPATION_MODE_AUTO_ADD = 'auto_add';
|
|
public const PARTICIPATION_MODE_CLOSED = 'closed';
|
|
|
|
protected $fillable = [
|
|
'title',
|
|
'slug',
|
|
'tagline',
|
|
'summary',
|
|
'description',
|
|
'cover_path',
|
|
'theme_key',
|
|
'accent_color',
|
|
'accent_color_secondary',
|
|
'background_motif',
|
|
'icon_name',
|
|
'status',
|
|
'type',
|
|
'starts_at',
|
|
'ends_at',
|
|
'submission_starts_at',
|
|
'submission_ends_at',
|
|
'is_featured',
|
|
'accepts_submissions',
|
|
'participation_mode',
|
|
'submission_note_enabled',
|
|
'community_section_enabled',
|
|
'allow_readd_after_removal',
|
|
'is_recurring',
|
|
'recurrence_key',
|
|
'recurrence_rule',
|
|
'edition_year',
|
|
'cta_label',
|
|
'cta_url',
|
|
'badge_label',
|
|
'badge_description',
|
|
'submission_guidelines',
|
|
'badge_url',
|
|
'seo_title',
|
|
'seo_description',
|
|
'og_image_path',
|
|
'related_tags_json',
|
|
'section_order_json',
|
|
'section_visibility_json',
|
|
'parent_world_id',
|
|
'created_by_user_id',
|
|
'published_at',
|
|
];
|
|
|
|
protected $casts = [
|
|
'starts_at' => 'datetime',
|
|
'ends_at' => 'datetime',
|
|
'submission_starts_at' => 'datetime',
|
|
'submission_ends_at' => 'datetime',
|
|
'published_at' => 'datetime',
|
|
'is_featured' => 'boolean',
|
|
'accepts_submissions' => 'boolean',
|
|
'allow_readd_after_removal' => 'boolean',
|
|
'submission_note_enabled' => 'boolean',
|
|
'community_section_enabled' => 'boolean',
|
|
'is_recurring' => 'boolean',
|
|
'edition_year' => 'integer',
|
|
'related_tags_json' => 'array',
|
|
'section_order_json' => 'array',
|
|
'section_visibility_json' => 'array',
|
|
];
|
|
|
|
public function createdBy(): BelongsTo
|
|
{
|
|
return $this->belongsTo(User::class, 'created_by_user_id');
|
|
}
|
|
|
|
public function parentWorld(): BelongsTo
|
|
{
|
|
return $this->belongsTo(self::class, 'parent_world_id');
|
|
}
|
|
|
|
public function archiveEditions(): HasMany
|
|
{
|
|
return $this->hasMany(self::class, 'parent_world_id')->orderByDesc('edition_year')->orderByDesc('starts_at');
|
|
}
|
|
|
|
public function worldRelations(): HasMany
|
|
{
|
|
return $this->hasMany(WorldRelation::class)->orderBy('section_key')->orderBy('sort_order')->orderBy('id');
|
|
}
|
|
|
|
public function worldSubmissions(): HasMany
|
|
{
|
|
return $this->hasMany(WorldSubmission::class)->orderByDesc('reviewed_at')->orderByDesc('created_at');
|
|
}
|
|
|
|
public function scopePublished(Builder $query): Builder
|
|
{
|
|
return $query
|
|
->where('status', self::STATUS_PUBLISHED)
|
|
->where(function (Builder $builder): void {
|
|
$builder->whereNull('published_at')
|
|
->orWhere('published_at', '<=', now());
|
|
});
|
|
}
|
|
|
|
public function scopePubliclyVisible(Builder $query): Builder
|
|
{
|
|
return $query
|
|
->whereIn('status', [self::STATUS_PUBLISHED, self::STATUS_ARCHIVED])
|
|
->where(function (Builder $builder): void {
|
|
$builder->whereNull('published_at')
|
|
->orWhere('published_at', '<=', now());
|
|
});
|
|
}
|
|
|
|
public function scopeCurrent(Builder $query): Builder
|
|
{
|
|
return $query
|
|
->published()
|
|
->where(function (Builder $builder): void {
|
|
$builder->whereNull('starts_at')
|
|
->orWhere('starts_at', '<=', now());
|
|
})
|
|
->where(function (Builder $builder): void {
|
|
$builder->whereNull('ends_at')
|
|
->orWhere('ends_at', '>=', now());
|
|
});
|
|
}
|
|
|
|
public function scopeUpcoming(Builder $query): Builder
|
|
{
|
|
return $query
|
|
->published()
|
|
->whereNotNull('starts_at')
|
|
->where('starts_at', '>', now());
|
|
}
|
|
|
|
public function scopeArchive(Builder $query): Builder
|
|
{
|
|
return $query
|
|
->publiclyVisible()
|
|
->where(function (Builder $builder): void {
|
|
$builder->where('status', self::STATUS_ARCHIVED)
|
|
->orWhere(function (Builder $expired): void {
|
|
$expired->whereNotNull('ends_at')
|
|
->where('ends_at', '<', now());
|
|
});
|
|
});
|
|
}
|
|
|
|
public function isPubliclyVisible(): bool
|
|
{
|
|
if (! in_array($this->status, [self::STATUS_PUBLISHED, self::STATUS_ARCHIVED], true)) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->published_at && $this->published_at->isFuture()) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public function isCurrent(): bool
|
|
{
|
|
if (! $this->isPubliclyVisible()) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->starts_at && $this->starts_at->isFuture()) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->ends_at && $this->ends_at->isPast()) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public function isAcceptingSubmissions(): bool
|
|
{
|
|
if (! $this->isPubliclyVisible() || ! $this->accepts_submissions || ! $this->allowsCreatorParticipation()) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->submission_starts_at && $this->submission_starts_at->isFuture()) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->submission_ends_at && $this->submission_ends_at->isPast()) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public function allowsCreatorParticipation(): bool
|
|
{
|
|
return in_array((string) $this->participation_mode, [
|
|
self::PARTICIPATION_MODE_MANUAL_APPROVAL,
|
|
self::PARTICIPATION_MODE_AUTO_ADD,
|
|
], true);
|
|
}
|
|
|
|
public function submissionStartsAsLive(): bool
|
|
{
|
|
return (string) $this->participation_mode === self::PARTICIPATION_MODE_AUTO_ADD;
|
|
}
|
|
|
|
public function coverUrl(): ?string
|
|
{
|
|
$path = trim((string) $this->cover_path);
|
|
|
|
if ($path === '') {
|
|
return null;
|
|
}
|
|
|
|
if (str_starts_with($path, 'http://') || str_starts_with($path, 'https://')) {
|
|
return $path;
|
|
}
|
|
|
|
return rtrim((string) config('cdn.files_url', 'https://files.skinbase.org'), '/') . '/' . ltrim($path, '/');
|
|
}
|
|
|
|
public function ogImageUrl(): ?string
|
|
{
|
|
$path = trim((string) ($this->og_image_path ?: $this->cover_path ?: ''));
|
|
|
|
if ($path === '') {
|
|
return null;
|
|
}
|
|
|
|
if (str_starts_with($path, 'http://') || str_starts_with($path, 'https://')) {
|
|
return $path;
|
|
}
|
|
|
|
return rtrim((string) config('cdn.files_url', 'https://files.skinbase.org'), '/') . '/' . ltrim($path, '/');
|
|
}
|
|
|
|
public function publicUrl(): string
|
|
{
|
|
return route('worlds.show', ['world' => $this->slug]);
|
|
}
|
|
|
|
public function sectionOrder(): array
|
|
{
|
|
$defaults = array_values(array_filter(config('worlds.default_section_order', []), 'is_string'));
|
|
$custom = array_values(array_filter($this->section_order_json ?? [], 'is_string'));
|
|
|
|
return array_values(array_unique(array_merge($custom, $defaults)));
|
|
}
|
|
|
|
public function sectionVisibility(): array
|
|
{
|
|
$defaults = collect(array_keys((array) config('worlds.sections', [])))
|
|
->mapWithKeys(fn (string $key): array => [$key => true])
|
|
->all();
|
|
|
|
$custom = collect((array) $this->section_visibility_json)
|
|
->mapWithKeys(fn ($value, $key): array => [(string) $key => (bool) $value])
|
|
->all();
|
|
|
|
return array_merge($defaults, $custom);
|
|
}
|
|
} |