*/ protected $fillable = [ 'seo_title', 'seo_description', 'title', 'subtitle', 'slug', 'picture', 'picture_alt', 'picture_title', 'thumbnail', 'thumbnail_alt', 'thumbnail_title', 'news_category_id', 'content', 'highlight', 'published', 'publish_at', 'unpublish_at', ]; /** * The attributes that should be cast. * * @var array */ protected $casts = [ 'publish_at' => 'datetime', 'unpublish_at' => 'datetime', 'published' => 'boolean', 'highlight' => 'boolean', ]; /** * Get model's setting to apply on scout search (meilisearch). * * @var array */ public static $searchSettings = [ 'filterableAttributes' => ['title', 'content', 'published'], 'sortableAttributes' => ['title', 'content'], ]; /** * Perform any actions required after the model boots. */ protected static function booted(): void { static::creating(function (self $news) { self::makeStatusSense($news); self::checkHighlightField($news); self::assertTitleIsUnique($news->title); self::manageFields($news); }); static::created(function () { self::resetCache(); }); static::updating(function (self $news) { self::makeStatusSense($news); self::checkHighlightField($news); self::manageFields($news); }); static::updated(function (self $news) { FileStorageHelper::removeOldFile($news, 'picture'); FileStorageHelper::removeOldFile($news, 'thumbnail'); FileStorageHelper::removeOldModelFilesFromHtml($news->content, $news->getOriginal('content')); self::resetCache(); }); static::deleted(function (self $news) { FileStorageHelper::removeFile($news, 'picture'); FileStorageHelper::removeFile($news, 'thumbnail'); FileStorageHelper::removeOldModelFilesFromHtml($news->content, $news->getOriginal('content')); self::resetCache(); }); } // * METHODS /** * Adjusts news status using current status and publish dates. */ private static function makeStatusSense(self $news): void { /** @var \Carbon\Carbon|null */ $publish_at = $news->publish_at; /** @var \Carbon\Carbon|null */ $unpublish_at = $news->unpublish_at; $wantedStatus = $news->published; if ($publish_at or $unpublish_at) { $news->published = $news->planificated_status; } if ($wantedStatus !== $news->published) { session()->flash('bo.info', \trans( 'La publication est modifiée en concordance avec les dates enregistrées. Pour modifier l\'état de publication, veuillez modifier ou enlever les dates correspondantes.' )); } } /** * Adjusts news status using current status and publish dates. */ private static function checkHighlightField(self $news): void { $hotNewsModels = News::query() ->where('published', true) ->where('highlight', true) ->where('id', '!=', $news->getKey()) ->orderBy('publish_at', 'ASC') ->get(); if ($hotNewsModels->count() >= 2 && $news->highlight) { $hotNewsModel = $hotNewsModels->first(); $hotNewsModel->highlight = false; $hotNewsModel->saveOrFail(); Session::flash('warning', trans( 'Maximum 2 :model1 peuvent être mise en avant, la plus ancienne a donc été enlevée des :model2', [ 'model1' => Str::of(trans('models.classes.news'))->plural(), 'model2' => trans('models.classes.hot_news'), ] )); } } /** * Getter the current status following planification dates planificated_status. */ public function getPlanificatedStatusAttribute(): bool { /** @var \Carbon\Carbon|null */ $publish_at = $this->publish_at; /** @var \Carbon\Carbon|null */ $unpublish_at = $this->unpublish_at; if ($publish_at and $unpublish_at) { return $unpublish_at->greaterThanOrEqualTo(now()) and $publish_at->lessThan(now()); } if ($publish_at) { return $publish_at->lessThan(now()); } if ($unpublish_at) { return $unpublish_at->greaterThanOrEqualTo(now()); } return false; } /** * Manage model fields. */ private static function manageFields(self $model): void { self::assertTitleIsUnique($model->title, $model->getKey()); $model->slug = Str::slug($model->title); $model->thumbnail = FileStorageHelper::storeFile($model, $model->thumbnail, true); $model->picture = FileStorageHelper::storeFile($model, $model->picture, true); } /** * Asserts using validation that the title is unique. * * @throws \Illuminate\Validation\ValidationException If an actu title already exists. */ private static function assertTitleIsUnique(?string $title, ?int $id = null): void { $table = (new self())->getTable(); ToolboxHelper::assertFieldIsUnique($table, 'title', $title, $id); ToolboxHelper::assertFieldIsUnique($table, 'slug', Str::slug($title), $id); } /** * Reset all cache elements related to this model. */ public static function resetCache(): void { Cache::forget('publishedNewsWithCategories'); self::getPublishedWithNewsCategory(); } /** * Get all published news with categories relations. */ public static function getPublishedWithNewsCategory(): Collection { return Cache::remember('publishedNewsWithCategories', 86400, function () { return self::query() ->where('published', true) ->with('newsCategory') ->whereHas('newsCategory', function (Builder $q) { $q->where('published', true); }) ->orderBy('updated_at', 'desc') ->get(); }); } /** * Get News views by News category. */ public static function getNewsViewsByNewsCategory( ?string $slug = null ): LengthAwarePaginator { $detect = new MobileDetect(); /** @var \App\Models\NewsCategory|null */ $newsCategoryModel = ! is_null($slug) && ! empty($slug) ? NewsCategory::query()->where('slug', $slug)->first() : null; return News::query() ->with('newsCategory') ->when(! is_null($newsCategoryModel), function (Builder $q) use ($newsCategoryModel) { $q->whereHas('newsCategory', function (Builder $q) use ($newsCategoryModel) { $q->where('id', $newsCategoryModel->getKey()); }); }) ->whereHas('newsCategory', function (Builder $q) { $q->where('published', true); }) ->where('published', true) ->orderByDesc('updated_at') ->paginate($detect->isMobile() ? 3 : 6) ->through(function ($newsModel) { return view('fo.partials.news-card', ['model' => $newsModel])->render(); }); } /** * Get Social news views by filter social category. */ public static function getSocialViewsByFilter( ?string $slug = null ): LengthAwarePaginator { $detect = new MobileDetect(); /** @var \App\Models\NewsCategory|null */ $socialCategory = ! is_null($slug) && ! empty($slug) ? NewsCategory::query()->where('slug', $slug)->first() : null; return News::query() ->with('newsCategory') ->when(! is_null($socialCategory), function (Builder $q) use ($socialCategory) { $q->whereHas('newsCategory', function (Builder $q) use ($socialCategory) { $q->where('id', $socialCategory->getKey()); }); }) ->where('published', true) ->orderByDesc('created_at') ->paginate($detect->isMobile() ? 4 : 12) ->through(function ($socialModel) { return view('fo.partials.news-card', ['model' => $socialModel])->render(); }); } /** * To sitemap tag . */ public function toSitemapTag(): Url | string | array { $model = $this; $route = 'fo.news.show'; $params = ['slug' => $model->slug]; return SitemapHelper::generateUrl($route, $params, $model); } /** * Create a SchemaOrg. */ public function toSchemaOrg(): NewsArticle { return Schema::NewsArticle() ->headline($this->seo_title) ->description($this->seo_description) ->name($this->title) ->abstract($this->subtitle) ->dateCreated($this->created_at) ->dateModified($this->updated_at) ->datePublished($this->publish_at) ->articleBody(strip_tags($this->content)) ->wordCount(str_word_count(strip_tags($this->content))) ->image( Schema::imageObject() ->url(asset($this->picture)) ->headline($this->picture_title) ) ->thumbnail( Schema::imageObject() ->url(asset($this->thumbnail)) ->headline($this->thumbnail_title) ) ->url(route('fo.news.show', $this)) ->author($this->getOrganizationSchema()) ->creator($this->getOrganizationSchema()) ->copyrightYear($this->created_at->year) ->subjectOf($this->newsCategory->toSchemaOrg()) ->isPartOf($this->getWebsiteSchema()); } /** * Determine if the model should be searchable. */ public function shouldBeSearchable(): bool { return $this->published === true; } // * REDIRECTIONS. /** * Get the front url of this model. * Should return the model.show route, with all binded elements if needed. */ public function getFrontUrl(string $modelKey): string { return route('fo.news.show', $modelKey); } // * RELATIONS /** * Get the category that owns the news. */ public function newsCategory(): BelongsTo { return $this->belongsTo(NewsCategory::class); } }