ldc-le-gaulois-pro/app/Models/Product.php
2026-03-25 10:45:26 +01:00

316 lines
11 KiB
PHP

<?php
namespace App\Models;
use Illuminate\Support\Str;
use Spatie\Sitemap\Tags\Url;
use App\Enums\NutriscoreEnum;
use App\Lib\Helpers\ToolboxHelper;
use App\Enums\Advices\AdviceTypeEnum;
use Uwil\Seo\Laravel\Objects\SeoItem;
use Uwil\Seo\Laravel\Traits\SeoTrait;
use App\Lib\Helpers\FileStorageHelper;
use Illuminate\Database\Eloquent\Model;
use Spatie\Sitemap\Contracts\Sitemapable;
use App\Enums\Products\ProductSpeciesEnum;
use App\Traits\Models\Orderable;
use Uwil\SitemapGenerator\Lib\SitemapHelper;
use Uwil\Seo\Laravel\Interfaces\SeoInterface;
use Uwil\Laravel\Redirections\Traits\IsRedirectable;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Uwil\Laravel\Redirections\Interfaces\RedirectableInterface;
/**
* @property integer $id Id.
* @property string $label Title.
* @property string $slug Slug of the title.
* @property integer $subrange_id Foreign key Subrange.
* @property string $seo_title SEO Title.
* @property string $seo_description SEO description.
* @property \App\Enums\Products\ProductSpeciesEnum $species Species of products.
* @property \App\Enums\NutriscoreEnum $nutriscore Nutriscore.
* @property string $sticker_sm Path of a small sticker.
* @property string $sticker_sm_alt small sticker's attribute alt.
* @property string $sticker_sm_title Small sticker's attribute title.
* @property array<string> $slider_pictures Array of paths of pictures.
* @property array<array<string,string>> $packaging Packaging.
* @property array<array<string,string>> $caliber Caliber.
* @property string $ingredients Ingredients.
* @property string $advantage Advantage.
* @property string $preparation Preparation.
* @property boolean $is_frenchpoultry Is frenchpoultry or not.
* @property boolean $is_vip Is vip or not.
* @property boolean $is_egalim Is egalim or not.
* @property boolean $published Status.
* @property integer $order Order.
* @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 \App\Models\Subrange $subrange Subrange Has-One relation.
* @property-read \Illuminate\Database\Eloquent\Collection<\App\Models\Advice> $advices
* Advices Belongs-To-Many relation.
* @property-read \Illuminate\Database\Eloquent\Collection<\App\Models\News> $news
* News Belongs-To-Many relation.
*/
class Product extends Model implements SeoInterface, Sitemapable, RedirectableInterface
{
use HasFactory;
use SeoTrait;
use IsRedirectable;
use Orderable;
/**
* The attributes that are mass assignable.
*
* @var array<int, string>
*/
protected $fillable = [
'label',
'slug',
'seo_title',
'seo_description',
'subrange_id',
'species',
'nutriscore',
'ingredients',
'advantage',
'preparation',
'sticker_sm',
'sticker_sm_alt',
'sticker_sm_title',
'slider_pictures',
'packaging',
'caliber',
'is_frenchpoultry',
'is_vip',
'is_egalim',
'order',
'published',
'priority_order',
];
/**
* The attributes that should be cast.
*
* @var array<string,string>
*/
protected $casts = [
'slider_pictures' => 'array',
'packaging' => 'array',
'caliber' => 'array',
'is_frenchpoultry' => 'bool',
'is_vip' => 'bool',
'is_egalim' => 'bool',
'published' => 'bool',
'species' => ProductSpeciesEnum::class,
'nutriscore' => NutriscoreEnum::class
];
/**
* Perform any actions required after the model boots.
*
* @return void
*/
protected static function booted(): void
{
// * EVENT CLOSURES.
static::creating(function (self $model) {
self::manageFields($model);
self::bootOrderable($model);
});
static::updating(function (self $model) {
self::manageFields($model);
});
static::updated(function (self $model) {
FileStorageHelper::removeOldFile($model, 'sticker_sm');
FileStorageHelper::removeOldFiles(
!is_array($model->getOriginal('slider_pictures')) ? [] : $model->getOriginal('slider_pictures'),
$model->slider_pictures
);
});
static::deleted(function (self $model) {
FileStorageHelper::removeFile($model, 'sticker_sm');
FileStorageHelper::removeFiles(
$model,
!is_array($model->getOriginal('slider_pictures')) ? [] : $model->getOriginal('slider_pictures'),
);
self::setOrder($model);
});
}
// * METHODS
/**
* Common methods function.
*
* @param self $model
* @return void
*/
private static function manageFields(self $model): void
{
// * ASSERTIONS
self::assertLabelIsUnique($model->label, $model->id);
ToolboxHelper::assertNoInternalBoUrls('ingredients', $model->ingredients);
ToolboxHelper::assertNoInternalBoUrls('advantage', $model->advantage);
ToolboxHelper::assertNoInternalBoUrls('preparation', $model->preparation);
foreach ($model->packaging as $pack) {
ToolboxHelper::assertNoInternalBoUrls('packaging', $pack['value']);
}
foreach ($model->caliber as $calib) {
ToolboxHelper::assertNoInternalBoUrls('caliber', $calib['value']);
}
// * TRANSFORMATIONS
$model->slug = Str::slug($model->label);
$model->sticker_sm = FileStorageHelper::storeFile($model, $model->sticker_sm, true);
$model->ingredients = clean($model->ingredients);
$model->advantage = clean($model->advantage);
$model->preparation = clean($model->preparation);
$model->slider_pictures = clean($model->slider_pictures);
$model->packaging = clean($model->packaging);
$model->caliber = clean($model->caliber);
}
/**
* Asserts using validation that the label is unique.
*
* @param string $label
* @param integer|null $id
* @return void
*/
private static function assertLabelIsUnique(string $label, ?int $id = null): void
{
$table = (new self())->getTable();
ToolboxHelper::assertFieldIsUnique($table, 'label', $label, $id);
ToolboxHelper::assertFieldIsUnique($table, 'slug', Str::slug($label), $id);
}
/**
* Get associated tips.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function tipsQuery(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
return $this->advices()->where('type', AdviceTypeEnum::tips);
}
/**
* Get associated recipes.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function recipesQuery(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
return $this->advices()->where('type', AdviceTypeEnum::recipes);
}
/**
* Get associated tips.
*
* @return \Illuminate\Database\Eloquent\Collection<int,\App\Models\Advice>
*/
public function tips(): \Illuminate\Database\Eloquent\Collection
{
// @phpstan-ignore-next-line
return $this->tipsQuery()->get();
}
/**
* Get associated recipes.
*
* @return \Illuminate\Database\Eloquent\Collection<int,\App\Models\Advice>
*/
public function recipes(): \Illuminate\Database\Eloquent\Collection
{
// @phpstan-ignore-next-line
return $this->recipesQuery()->get();
}
// * 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.product.show', $modelKey);
}
// * RELATIONSHIPS
/**
* Subrange Has-One relation.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function subrange(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->belongsTo(Subrange::class);
}
/**
* Advices Belongs-To-Many relation.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function advices(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
return $this->belongsToMany(Advice::class, 'advice_product');
}
/**
* News Belongs-To-Many relation.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function news(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
return $this->belongsToMany(News::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');
}
/**
* toSitemapTag method
*
* @return Url|string|array
*/
public function toSitemapTag(): Url | string | array
{
$model = $this;
$route = 'fo.product.show';
$params = ['publishedProduct' => $model];
return SitemapHelper::generateUrl($route, $params, $model);
}
/**
* Transform 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),
route('fo.product.show', $this)
);
}
}