JezK
Edit File: Article.php
<?php namespace App\Models; use App\Enums\ArticleStatus; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; class Article extends Model { use HasFactory, SoftDeletes; protected $fillable = [ 'user_id', 'title', 'slug', 'excerpt', 'content', 'featured_image', 'status', 'published_at', 'scheduled_at', 'is_featured', 'view_count', 'seo_title', 'seo_description', 'seo_keywords', 'og_image', ]; protected function casts(): array { return [ 'status' => ArticleStatus::class, 'published_at' => 'datetime', 'scheduled_at' => 'datetime', 'is_featured' => 'boolean', 'view_count' => 'integer', ]; } // ── Relationships ── public function author(): BelongsTo { return $this->belongsTo(User::class, 'user_id'); } public function categories(): BelongsToMany { return $this->belongsToMany(Category::class, 'article_category'); } public function tags(): BelongsToMany { return $this->belongsToMany(Tag::class, 'article_tag'); } public function images(): HasMany { return $this->hasMany(ArticleImage::class)->orderBy('sort_order'); } public function comments(): HasMany { return $this->hasMany(Comment::class); } // ── Scopes ── public function scopePublished($query) { return $query->where('status', ArticleStatus::PUBLISHED) ->where('published_at', '<=', now()); } public function scopeDraft($query) { return $query->where('status', ArticleStatus::DRAFT); } public function scopeScheduled($query) { return $query->where('status', ArticleStatus::SCHEDULED) ->whereNotNull('scheduled_at'); } public function scopeFeatured($query) { return $query->where('is_featured', true); } public function scopeSearch($query, ?string $search) { if (empty($search)) return $query; return $query->where(function ($q) use ($search) { $q->where('title', 'LIKE', "%{$search}%") ->orWhere('excerpt', 'LIKE', "%{$search}%") ->orWhere('content', 'LIKE', "%{$search}%"); }); } // ── Accessors ── public function getFeaturedImageUrlAttribute(): ?string { if ($this->featured_image) { return asset('storage/' . $this->featured_image); } return null; } public function getOgImageUrlAttribute(): ?string { if ($this->og_image) { return asset('storage/' . $this->og_image); } return $this->featured_image_url; } public function getReadingTimeAttribute(): int { $wordCount = str_word_count(strip_tags($this->content ?? '')); return max(1, (int) ceil($wordCount / 200)); } // ── Helpers ── public function isPublished(): bool { return $this->status === ArticleStatus::PUBLISHED && $this->published_at?->isPast(); } public function isDraft(): bool { return $this->status === ArticleStatus::DRAFT; } public function publish(): void { $this->update([ 'status' => ArticleStatus::PUBLISHED, 'published_at' => now(), ]); } public function unpublish(): void { $this->update([ 'status' => ArticleStatus::DRAFT, 'published_at' => null, ]); } }