Files
timebank-cc-public/app/Models/Post.php
T

305 lines
8.2 KiB
PHP

<?php
namespace App\Models;
use App\Helpers\StringHelper;
use App\Models\Category;
use App\Models\Image;
use App\Models\Meeting;
use App\Models\Poll;
use App\Models\PostTranslation;
use Cog\Contracts\Love\Reactable\Models\Reactable as ReactableInterface;
use Cog\Laravel\Love\Reactable\Models\Traits\Reactable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Laravel\Scout\Searchable;
use Spatie\MediaLibrary\HasMedia;
use Spatie\MediaLibrary\InteractsWithMedia;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
use Znck\Eloquent\Traits\BelongsToThrough;
class Post extends Model implements HasMedia, ReactableInterface
{
use HasFactory;
use BelongsToThrough;
use InteractsWithMedia;
use SoftDeletes;
use Searchable { // laravel/scout with ElasticSearch
searchableUsing as scoutSearchableUsing;
}
use Reactable; // cybercog/laravel-love
protected $fillable = ['postable_id', 'postable_type', 'category_id', 'author_id', 'author_model', 'is_locked'];
protected $casts = [
'is_locked' => '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();
}
}