'boolean', ]; /** * Get the index name for the model. * * @return string */ public function searchableAs() { return 'posts_index'; } /** * Polls are for logged-in profiles only: never in the search index, whatever the * search category settings. The daily scout:import also skips them (PullFromSource). */ public function shouldBeSearchable(): bool { return !$this->isPoll(); } /** * Every way into the index ends in searchableUsing()->update(), but Scout only checks * shouldBeSearchable() in its own model events and imports. The wrapper makes that check * for every update, so a poll is never indexed (and is removed should it be there). */ public function searchableUsing() { return new \App\Polls\SearchEngineWithoutPolls($this->scoutSearchableUsing()); } /** * Whether this post is a poll (poll category or a poll attached). */ public function isPoll(): bool { return $this->category?->type === Poll::class || $this->poll()->exists(); } /** * Convert the this model to a searchable array. * * @return array */ public function toSearchableArray() { return [ 'id' => $this->id, 'category_id' => $this->category_id, 'postable' => [ 'id' => $this->postable ? $this->postable->id : '', 'name' => $this->postable ? $this->postable->name : '', ], 'post_translations' => $this->translations->mapWithKeys(function ($translation) { // mapWithKeys() as translations is a collection return [ 'title_' . $translation->locale => $translation->title, 'excerpt_' . $translation->locale => $translation->excerpt, 'content_' . $translation->locale => $translation->content, // Add publication fields 'from_' . $translation->locale => $translation->from, 'till_' . $translation->locale => $translation->till, 'deleted_at_' . $translation->locale => $translation->deleted_at, 'status_' . $translation->locale => $translation->status, ]; })->toArray(), 'post_category' => $this->category ? [ 'id' => $this->category->id, 'names' => $this->category->translations->mapWithKeys(function ($translation) { // Include the locale in the field name for categories return ['name_' . $translation->locale => StringHelper::DutchTitleCase($translation->name)]; })->toArray(), ] : [], ]; } /** * Get the creator of the post (i.e. user or organization). * * @return void */ public function postable() { return $this->morphTo(); } /** * Get the author of the post (polymorphic: User, Organization, or Bank). * * @return void */ public function author() { return $this->morphTo('author', 'author_model', 'author_id'); } /** * Get the related translations of the post * One-to-many relationship * @return void */ public function translations() { return $this->hasMany(PostTranslation::class); } /** * Get the related category of the post * * @return void */ public function category() { return $this->belongsTo(Category::class); } /** * Get the related images of the posts * * @return void */ public function images() { return $this->morphToMany(Image::class, 'imageable'); } public static function last() { return static::all()->last(); } /** * Spatie Media Library media conversions * When updated you can use: * php artisan media-library:regenerate * to regenerate media disk and database of all media stored (make sure you restart the queue worker first) * * @param mixed $media * @return void */ public function registerMediaConversions(Media $media = null): void { $this->addMediaConversion('favicon') //1:1 ->focalCrop(32, 32, 50, 50) ->optimize() ->format('gif') ->performOnCollections('posts'); $this->addMediaConversion('logo') //1:1 ->focalCrop(160, 160, 50, 50) ->optimize() ->format('webp') ->performOnCollections('posts'); $this->addMediaConversion('thumbnail') // 1:1 ->focalCrop(150, 150, 50, 50) ->optimize() ->format('webp') ->performOnCollections('posts'); $this->addMediaConversion('blog') //3:2 ->focalCrop(1200, 630, 50, 50) ->withResponsiveImages() ->optimize() ->format('webp') ->performOnCollections('posts'); $this->addMediaConversion('hero') //16:9 ->focalCrop(3840, 2160, 50, 50) ->withResponsiveImages() ->optimize() ->format('webp') ->performOnCollections('posts'); $this->addMediaConversion('half_hero') //16:4.5 panoramic ->focalCrop(3840, 1080, 50, 50) ->format('webp') ->performOnCollections('posts'); $this->addMediaConversion('4_3') //4:3 ->focalCrop(3072, 2304, 50, 50) ->withResponsiveImages() ->optimize() ->format('webp') ->performOnCollections('posts'); $this->addMediaConversion('email') // For email newsletters - resizes without cropping ->width(800) ->optimize() ->format('jpg') ->quality(85) ->performOnCollections('posts'); } public function registerMediaCollection(): void { $this->addMediaCollection('posts') ->useDisk('media') ->singleFile(); } /** * Get the related meeting of the post * One-to-one relation * * @return void */ public function meeting() { return $this->hasOne(Meeting::class); } /** * Get the related poll of the post * One-to-one relation * * @return void */ public function poll() { return $this->hasOne(Poll::class); } /** * Get the count of 'like' reactions for this post * * @return int */ public function getLikeCountAttribute() { // Check if post is registered as reactant before getting reactions if (!$this->isRegisteredAsLoveReactant()) { return 0; } // Get the reactant facade $reactantFacade = $this->viaLoveReactant(); // Get the reaction counter for 'Like' type $reactionCounter = $reactantFacade->getReactionCounterOfType('Like'); if (!$reactionCounter) { return 0; } // Return the total count from all users return $reactionCounter->getCount(); } }