> $ingredients Ingredients. * @property integer|null $guests_nb Number of guests. * @property string|null $youtube_title Youtube title. * @property string|null $youtube_video_id Youtube video id. * @property integer|null $cooking_time Time of cooking. * @property integer|null $preparation_time Time of preparation. * @property \App\Enums\Advices\AdviceDifficultyEnum|null $difficulty Difficulty. * @property boolean $published Status. * @property integer $priority_order Priority Order. * @property-read \Illuminate\Support\Carbon $created_at Created date. * @property-read \Illuminate\Support\Carbon $updated_at Updated date. * * @property-read \Illuminate\Database\Eloquent\Collection<\App\Models\Advice> $recipes * HasMany Recipes. * @property-read \Illuminate\Database\Eloquent\Collection<\App\Models\Advice> $tips * HasMany Tips. * @property-read \Illuminate\Database\Eloquent\Collection<\App\Models\Advice> $advices * Advices Belongs-To-Many relationship. * @property-read \Illuminate\Database\Eloquent\Collection<\App\Models\Product> $products * Products Belongs-To-Many relationship. * @property-read \Illuminate\Database\Eloquent\Collection<\App\Models\Tag> $tags * Tags Morph-To-Many relationship. */ class Advice extends Authenticatable implements SeoInterface, Sitemapable, RedirectableInterface, OrderableInterface { use HasFactory; use SeoTrait; use IsRedirectable; use Orderable; /** * The attributes that are mass assignable. * * @var array */ protected $fillable = [ 'title', 'slug', 'seo_title', 'seo_description', 'type', 'picture', 'picture_alt', 'picture_title', 'sticker', 'sticker_alt', 'sticker_title', 'sticker_sm', 'sticker_sm_alt', 'sticker_sm_title', 'preparation', 'advice', 'ingredients', 'guests_nb', 'youtube_title', 'youtube_video_id', 'cooking_time', 'preparation_time', 'difficulty', 'published', 'priority_order' ]; /** * The attributes that should be cast. * * @var array */ protected $casts = [ 'ingredients' => 'array', 'published' => 'bool', 'type' => AdviceTypeEnum::class, 'difficulty' => AdviceDifficultyEnum::class, ]; /** * Perform any actions required after the model boots. * * @return void */ protected static function booted(): void { static::creating(function (self $model) { self::manageFields($model); self::bootOrderable(); }); static::updating(function (self $model) { self::manageFields($model); self::assertLimitProducts($model); }); static::updated(function (self $model) { FileStorageHelper::removeOldFile($model, 'picture'); FileStorageHelper::removeOldFile($model, 'sticker'); FileStorageHelper::removeOldFile($model, 'sticker_sm'); }); static::deleted(function (self $model) { FileStorageHelper::removeFile($model, 'picture'); FileStorageHelper::removeFile($model, 'sticker'); FileStorageHelper::removeFile($model, 'sticker_sm'); self::setOrder($model); }); } // * METHODS /** * Common methods function. * * @param self $model * @return void */ private static function manageFields(self $model): void { self::assertTitleIsUnique($model->title, $model->id); $model->slug = Str::slug($model->title); self::assertLimitAdvices($model->advices()->count()); self::assertLimitTags($model->tags()->count()); self::setCookingtimePreparationtimeValues($model); ToolboxHelper::assertNoInternalBoUrls('preparation', $model->preparation); if ($model->advice != null) { ToolboxHelper::assertNoInternalBoUrls('advice', $model->advice); } if ($model->youtube_video_id) { ToolboxHelper::assertNoInternalBoUrls('youtube_video_id', $model->youtube_video_id); $model->youtube_video_id = clean($model->youtube_video_id); } $model->preparation = clean($model->preparation); $model->advice = clean($model->advice); $model->picture = FileStorageHelper::storeFile($model, $model->picture); $model->sticker = FileStorageHelper::storeFile($model, $model->sticker); $model->sticker_sm = FileStorageHelper::storeFile($model, $model->sticker_sm); } /** * Limit tags associated. * * @param integer $nbTagsInBdd * @return void * @throws \Illuminate\Validation\ValidationException Error exception. */ private static function assertLimitTags(int $nbTagsInBdd): void { if ($nbTagsInBdd > 2) { throw ValidationException::withMessages([ 'Vous devez associer au maximum 2 tags.' ]); } } /** * Limit advices associated. * * @param integer $nbAdvicesInBdd * @return void * @throws \Illuminate\Validation\ValidationException Error exception. */ private static function assertLimitAdvices(int $nbAdvicesInBdd): void { if ($nbAdvicesInBdd > 6) { throw ValidationException::withMessages([ 'Vous devez associer au maximum 6 conseils.' ]); } } /** * Limit products associated. * * @param self $model * @return void * @throws \Illuminate\Validation\ValidationException Error exception. */ private static function assertLimitProducts(self $model): void { $nbProductsInBdd = $model->products()->count(); if ($model->type->value == AdviceTypeEnum::recipes->value()) { if ($nbProductsInBdd < 1 || $nbProductsInBdd > 3) { throw ValidationException::withMessages([ 'Vous devez associer 1 à 3 produits.' ]); } } elseif ($model->type->value == AdviceTypeEnum::tips->value()) { if ($nbProductsInBdd > 3) { throw ValidationException::withMessages([ 'Vous devez associer au maximum 3 produits.' ]); } } } /** * Asserts using validation that the title is unique. * * @param self $model * @return void */ private static function setCookingtimePreparationtimeValues(self $model): void { if ($model->type->value != AdviceTypeEnum::recipes->value()) { $model->cooking_time = null; $model->preparation_time = null; } } /** * Asserts using validation that the title is unique. * * @param string $title * @param integer|null $id * @return void */ 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); } /** * Get associated advices * * @return \Illuminate\Database\Eloquent\Collection<\App\Models\Advice> */ public function recipesQuery(): \Illuminate\Database\Eloquent\Collection { return $this->advices()->where('type', AdviceTypeEnum::recipes)->get(); } /** * Get the advice recipes * * @return \Illuminate\Database\Eloquent\Casts\Attribute */ protected function recipes(): Attribute { return Attribute::make( get: fn() => $this->recipesQuery(), ); } /** * Get associated tips * * @return \Illuminate\Database\Eloquent\Collection<\App\Models\Advice> */ public function tipsQuery(): \Illuminate\Database\Eloquent\Collection { return $this->advices()->where('type', AdviceTypeEnum::tips)->get(); } /** * Get the advice tips * * @return \Illuminate\Database\Eloquent\Casts\Attribute */ protected function tips(): Attribute { return Attribute::make( get: fn() => $this->tipsQuery(), ); } // * REDIRECTIONS. /** * Get the front url of this model. * Should return the model.show route, with all binded elements if needed. * * @param string $modelKey * @return string */ public function getFrontUrl(string $modelKey): string { return route('fo.tips.show', $modelKey); } // * RELATIONSHIPS /** * Advices Belongs-To-Many relationship. * * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ public function advices(): \Illuminate\Database\Eloquent\Relations\BelongsToMany { return $this->belongsToMany(Advice::class, 'advice_advice', 'advice_id', 'advice_related_id'); } /** * Products Belongs-To-Many relationship. * * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ public function products(): \Illuminate\Database\Eloquent\Relations\BelongsToMany { return $this->belongsToMany(Product::class); } /** * Tags Morph-To-Many relationship. * * @return \Illuminate\Database\Eloquent\Relations\MorphToMany */ public function tags(): \Illuminate\Database\Eloquent\Relations\MorphToMany { return $this->morphToMany(Tag::class, 'taggable'); } /** * Transfotrm to seoModel * * @return \Uwil\Seo\Laravel\Objects\SeoItem */ public function toSeoItem(): SeoItem { return new SeoItem( \trans($this->getTable()), $this->seo_title, $this->seo_description, $this->published ?? false, \route(\config('seo.bo_route_prefix') . $this->getTable() . '.edit', $this), $this->type === AdviceTypeEnum::recipes ? route('fo.recipes.show', $this) : route('fo.tips.show', $this) ); } /** * toSitemapTag method * * @return Url|string|array */ public function toSitemapTag(): Url | string | array { $model = $this; $route = $this->type === AdviceTypeEnum::tips ? 'fo.tips.show' : 'fo.recipes.show'; $params = ['publishedAdvice' => $model]; return SitemapHelper::generateUrl($route, $params, $model); } /** * Group ordering by type. * * @return string */ public function getOrderGroupField(): string { return 'type'; } }