*/ protected $fillable = [ 'seo_title', 'seo_description', 'label', 'slug', 'claim_title', 'picture', 'picture_alt', 'picture_title', 'banner', 'banner_alt', 'banner_title', 'packaging', 'packaging_alt', 'packaging_title', 'description', 'details', 'preparation', 'ingredients', 'approach_id', 'range_id', 'subrange_id', 'meat_id', 'submeat_id', 'new', 'discover', 'published', ]; /** * The attributes that should be cast. * * @var array */ protected $casts = [ 'new' => 'boolean', 'discover' => 'boolean', 'published' => 'boolean', ]; /** * Get model's setting to apply on scout search (meilisearch). * * @return array */ public static $searchSettings = [ 'filterableAttributes' => ['label', 'description', 'preparation', 'ingredients', 'published'], 'sortableAttributes' => ['label', 'description', 'preparation', 'ingredients'] ]; /** * Perform any actions required after the model boots. * * @return void */ protected static function booted(): void { static::creating(function (self $product) { self::assertLabelIsUnique($product->label); self::manageFields($product); }); static::created(function () { self::resetCache(); }); static::updating(function (self $product) { self::assertLabelIsUnique($product->label, $product->getKey()); self::manageFields($product); }); static::updated(function (self $product) { FileStorageHelper::removeOldFile($product, 'picture'); FileStorageHelper::removeOldFile($product, 'banner'); FileStorageHelper::removeOldFile($product, 'packaging'); self::resetCache(); }); static::deleted(function (self $product) { FileStorageHelper::removeFile($product, 'picture'); FileStorageHelper::removeFile($product, 'banner'); FileStorageHelper::removeFile($product, 'packaging'); self::resetCache(); }); } // * METHODS /** * Manage model fields. * * @param self $product * @return void */ private static function manageFields(self $product): void { $product->slug = Str::slug($product->label); $product->picture = FileStorageHelper::storeFile($product, $product->picture, true); $product->banner = FileStorageHelper::storeFile($product, $product->banner, true); $product->packaging = FileStorageHelper::storeFile($product, $product->packaging, true); } /** * Asserts using validation that the label is unique * * @param string|null $label * @param integer|null $id * @return void * @throws \Illuminate\Validation\ValidationException If a range label already exists. */ private static function assertLabelIsUnique(string|null $label, ?int $id = null): void { $table = (new self())->getTable(); ToolboxHelper::assertFieldIsUnique($table, 'label', $label, $id); ToolboxHelper::assertFieldIsUnique($table, 'slug', Str::slug($label), $id); } /** * Display a listing of the resource. * * @param string|null $slug * @return array */ public static function getProductsViewsByRange(string|null $slug = null): array { $rangeModel = !is_null($slug) ? Range::query()->where([['slug', $slug], ['published', true]])->first() : Range::query()->where('published', true)->orderBy('slug', 'ASC')->first(); return Product::query() ->whereHas('subrange', function (Builder $q) use ($rangeModel) { $q->where('published', true)->whereHas('range', function (Builder $q) use ($rangeModel) { $q->where('id', $rangeModel->getKey()); }); })->where('published', true) ->orWhereHas('range', function (Builder $q) use ($rangeModel) { $q->where('id', $rangeModel->getKey()); })->where('published', true) ->inRandomOrder() ->limit(6) ->get() ->map(function ($productModel) { return view('fo.partials.product-card', ['model' => $productModel])->render(); })->toArray(); } /** * Transform to seoModel. * * @return \Uwil\Seo\Laravel\Objects\SeoItem */ public function toSeoItem(): \Uwil\Seo\Laravel\Objects\SeoItem { return new SeoItem( 'product', $this->seo_title, $this->seo_description, $this->published ?? false, \route(\config('seo.bo_route_prefix') . $this->getTable() . '.edit', $this), route('fo.products.show', $this->slug) ); } /** * Reset all cache elements related to this model. * * @return void */ private static function resetCache(): void { Cache::forget('publishedDiscoveredProducts'); self::getPublishedDiscovered(); } /** * Get all published products with discover === true. * * @return \Illuminate\Database\Eloquent\Collection */ public static function getPublishedDiscovered(): \Illuminate\Database\Eloquent\Collection { return Cache::rememberForever('publishedDiscoveredProducts', function () { return self::query()->where('published', true)->where('discover', true)->get(); }); } /** * toSitemapTag method * * @return \Spatie\Sitemap\Tags\Url|string|array */ public function toSitemapTag(): \Spatie\Sitemap\Tags\Url|string|array { return SitemapHelper::generateUrl('fo.products.show', ['slug' => $this->slug], $this); } /** * Determine if the model should be searchable. * * @return boolean */ public function shouldBeSearchable(): bool { return $this->published === true; } // * RELATIONSHIPS /** * Get the range that are assigned this product. * * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ public function range(): \Illuminate\Database\Eloquent\Relations\BelongsTo { return $this->belongsTo(Range::class); } /** * Get the subrange that are assigned this product. * * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ public function subrange(): \Illuminate\Database\Eloquent\Relations\BelongsTo { return $this->belongsTo(Subrange::class); } /** * Get the meat that are assigned this product. * * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ public function meat(): \Illuminate\Database\Eloquent\Relations\BelongsTo { return $this->belongsTo(Meat::class); } /** * Get the submeat that are assigned this product. * * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ public function submeat(): \Illuminate\Database\Eloquent\Relations\BelongsTo { return $this->belongsTo(Submeat::class); } /** * Get all of the recipes that are assigned this product. * * @return \Illuminate\Database\Eloquent\Relations\MorphToMany */ public function recipes(): \Illuminate\Database\Eloquent\Relations\MorphToMany { return $this->morphedByMany(Recipe::class, 'productable'); } /** * Get the demarch assigned to this product. * * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ public function approach(): \Illuminate\Database\Eloquent\Relations\BelongsTo { return $this->belongsTo(Approach::class); } /** * Get all labels that are assigned to this product. * * @return \Illuminate\Database\Eloquent\Relations\MorphToMany */ public function labels(): \Illuminate\Database\Eloquent\Relations\MorphToMany { return $this->morphedByMany(Approach::class, 'productable'); } /** * Get all product types that are assigned to this product. * * @return \Illuminate\Database\Eloquent\Relations\MorphToMany */ public function productTypes(): \Illuminate\Database\Eloquent\Relations\MorphToMany { return $this->morphedByMany(ProductType::class, 'productable'); } /** * Get all product types that are assigned to this product. * * @return \Illuminate\Database\Eloquent\Relations\MorphToMany */ public function claims(): \Illuminate\Database\Eloquent\Relations\MorphToMany { return $this->morphedByMany(Claim::class, 'productable'); } /** * Get the videos for the product. * * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function videos(): \Illuminate\Database\Eloquent\Relations\HasMany { return $this->hasMany(Video::class); } /** * Get the nutritionnal values for the product. * * @return \App\Models\IncoNutritionalValue|null */ public function getNutritionnalValue(): \App\Models\IncoNutritionalValue|null { return $this->hasMany(IncoNutritionalValue::class)->first(); } }