|null $origins * @property \Illuminate\Support\Collection<\App\Enums\Products\OriginEnum> $origins_enum * @property array|null $claims * @property \Illuminate\Support\Collection<\App\Enums\Products\OriginEnum> $claims_enum * @property string|null $specified_claim * @property \App\Enums\Products\DistributionNetworkEnum $distribution_network * @property \App\Enums\Products\RayEnum $ray * @property \App\Enums\Products\RangeEnum $product_range * @property \App\Enums\Products\GeoAreaEnum $geo area * @property integer|null $weight * @property integer|null $pvc * @property string|null $comment * @property boolean $is_tasted * @property integer|null $tasters_number * @property \Carbon\Carbon|null $created_at * @property \Carbon\Carbon|null $updated_at * @property \Carbon\Carbon|null $deleted_at * * * RELATIONSHIPS * @property-read \App\Models\Brand $brand * @property-read \App\Models\User $user * @property-read \Illuminate\Database\Eloquent\Collection<\App\Models\ProductComment> $productComments * @property-read \Illuminate\Database\Eloquent\Collection<\App\Models\Keyword> $productKeywords */ class Product extends Model { use HasFactory; /** * @var array */ protected $fillable = [ 'user_id', 'brand_id', 'name', // ? FIXME: ?why is there brand here, column is not in DB. 'brand', 'seen_at', 'source', 'fair', 'shop', 'catalogue', 'gencod', 'picture', 'picture_alt', 'picture_title', 'docs', 'url', 'certification', 'origins', 'claims', 'specified_claim', 'distribution_network', 'ray', 'product_range', 'geo_area', 'weight', 'pvc', 'comment', 'is_tasted', 'tasters_number', 'created_at', 'updated_at', 'deleted_at' ]; /** * The attributes that should be cast. * * @var array */ protected $casts = [ 'source' => SourceEnum::class, 'docs' => 'json', 'certification' => CertificationEnum::class, 'origins' => 'json', 'claims' => 'json', 'distribution_network' => DistributionNetworkEnum::class, 'ray' => RayEnum::class, 'product_range' => RangeEnum::class, 'geo_area' => GeoAreaEnum::class, 'is_tasted' => 'boolean', 'seen_at' => 'datetime', 'created_at' => 'datetime', 'updated_at' => 'datetime', 'deleted_at' => 'datetime', ]; // * EVENTS /** * The "booted" method of the model. * * @return void */ protected static function booted(): void { static::creating(function (self $model) { self::assertFieldIsUnique('name', $model->name); $model->picture = FileStorageHelper::storeFile($model, $model->picture, true); self::updateSource($model); // FIXME: lier le produit à l'utilisateur connecté lors de l'ajout du système de connexion. $model->user_id = 1; }); static::updating(function (self $model) { self::deleteOldDocuments($model, true); $model->picture = FileStorageHelper::storeFile($model, $model->picture, true); self::updateSource($model); self::updateSpecifiedClaim($model); }); static::updated(function (self $model) { FileStorageHelper::removeOldFile($model, 'picture'); }); static::deleted(function (self $model) { self::deleteOldDocuments($model); FileStorageHelper::removeFile($model, 'picture'); }); } // * METHODS /** * Asserts using validation that the field is unique. * * @param string $field * @param string $value * @param integer|null $id * @return void * @throws \Illuminate\Validation\ValidationException If field already exists. */ private static function assertFieldIsUnique(string $field, string $value, ?int $id = null) { $table = (new self())->getTable(); ToolboxHelper::assertFieldIsUnique($table, $field, $value, $id); } /** * Update data specified claim. * * @param \App\Models\Product $model * @return void */ private static function updateSpecifiedClaim(Product $model) { $check = false; foreach ($model->claims as $claimValue) { if (!$check) { $check = (intval($claimValue) === \App\Enums\Products\ClaimEnum::others->value) ? true : false; } } $model->specified_claim = ($check) ? $model->specified_claim : null; } /** * Update data source. * * @param self $model * @return void */ private static function updateSource(self $model) { if ( $model->source === \App\Enums\Products\SourceEnum::showConsumer || $model->source === \App\Enums\Products\SourceEnum::showCustomer || $model->source === \App\Enums\Products\SourceEnum::showTrade ) { $model->shop = null; $model->catalogue = null; } elseif ($model->source === \App\Enums\Products\SourceEnum::magazin) { $model->fair = null; $model->catalogue = null; } elseif ($model->source === \App\Enums\Products\SourceEnum::catalogArgumentary) { $model->fair = null; $model->shop = null; } else { $model->fair = null; $model->shop = null; $model->catalogue = null; }; } /** * Delete old documents. * * @param self $model * @param boolean $onlyIfUpdated * @return void */ public static function deleteOldDocuments(self $model, bool $onlyIfUpdated = false) { $original = \collect($model->getOriginal('docs'))->map(fn ($document) => strval($document)); $new = \collect($model->docs)->map(fn ($document) => strval($document))->flip(); foreach ($original as $document) { // * Ignore if document is to keep. if ($onlyIfUpdated and $new->has($document)) { continue; } $originalPath = \ltrim(\urldecode($document), '/'); if (\strpos($originalPath, 'storage') !== 0) { continue; } $originalPath = ToolboxHelper::mbReplace('storage/', '', $originalPath); if (Storage::disk('public')->exists($originalPath)) { Storage::disk('public')->delete($originalPath); } } } /** * Generate product sheet PDF. * * @param self $model * @param string $title * @return \Mccarlosen\LaravelMpdf\LaravelMpdf */ public static function generateProductPdf(self $model, string $title): LaravelMpdf { /** @var \Mccarlosen\LaravelMpdf\LaravelMpdf */ $mpdf = app('laravel-mpdf'); /** @var \Mccarlosen\LaravelMpdf\LaravelMpdf */ $pdf = $mpdf->loadView( 'documents.product', // * Dirty convert to object. [ 'title' => $title, 'product' => \json_decode(\json_encode(new ProductResource($model))) ], [], [ 'mode' => 'utf-8', 'format' => 'A4', 'default_font_size' => '12', 'default_font' => 'sans-serif', 'mirrorMargins' => true, 'margin_left' => 10, 'margin_right' => 10, 'margin_top' => 10, 'margin_bottom' => 10, 'margin_header' => 10, 'margin_footer' => 10, 'margin_top' => 10, 'orientation' => 'L', 'title' => $title, 'author' => \config('app.name', 'Startcoq'), 'watermark' => '', 'show_watermark' => false, 'watermark_font' => 'sans-serif', 'display_mode' => 'fullpage', 'watermark_text_alpha' => 0.1, 'custom_font_dir' => '', 'custom_font_data' => [], 'auto_language_detection' => false, 'temp_dir' => rtrim( ini_get('upload_tmp_dir') ? ini_get('upload_tmp_dir') : sys_get_temp_dir(), DIRECTORY_SEPARATOR ), 'pdfa' => false, 'pdfaauto' => false, ] ); return $pdf; } /** * Generate a pdf with multiple products. * * @param array $printMultiProduct * @param string $title * @return \Mccarlosen\LaravelMpdf\LaravelMpdf */ public static function generateMultipleProductPdf(array $printMultiProduct, string $title) { /** @var \Mccarlosen\LaravelMpdf\LaravelMpdf|null */ $pdf = null; // * Loop all Model ids. foreach ($printMultiProduct as $productId) { /** @var \App\Models\Product */ $product = Product::query()->where('id', $productId)->firstOrFail(); // * If not yet defined, set the first page. if (is_null($pdf)) { $pdf = Product::generateProductPdf($product, $title); } else { // * Add another page and add HTML from view to this. $pdf->getMpdf()->AddPage(); $pdf->getMpdf()->WriteHTML((string)view( 'documents.product-body', ['product' => (object)(new ProductResource($product))->toArray()], )); } } return $pdf; } /** * Fetch and stream export file. * * @param string $filepath * @return \Symfony\Component\HttpFoundation\StreamedResponse * @throws \Error If file storage config is wrong. */ public static function downloadPrivateExportFile(string $filepath): StreamedResponse { /** @var \Illuminate\Filesystem\FilesystemAdapter */ $disk = Storage::disk('export'); if (!($disk instanceof \Illuminate\Filesystem\FilesystemAdapter)) { throw new \Error('Le disque de stockage doit être un système de fichiers local'); } if (!$disk->exists($filepath)) { \abort(400, trans('Le fichier demandé est périmé et n\'existe plus')); } return \response()->stream(function () use ($disk, $filepath) { echo $disk->get($filepath); }, 200, [ 'Cache-Control' => 'public, must-revalidate, max-age=0', 'Content-Description' => 'File Transfer', 'Content-Type' => $disk->mimeType($filepath), 'Expires' => 'Sat, 26 Jul 1997 05:00:00 GMT', 'Last-Modified' => gmdate('D, d M Y H:i:s') . ' GMT' ])->send(); } // * GETTERS /** * Get origins_enum attribute * * @return \Illuminate\Support\Collection<\App\Enums\Ingredients\OriginEnum> */ public function getOriginsEnumAttribute(): Collection { return \collect($this->getAttribute('origins'))->filter() ->map(fn (int $originEnumValue) => OriginEnum::make($originEnumValue)); } /** * Set origins_enum attribute * * @param mixed $value * @return void */ public function setOriginsEnumAttribute(mixed $value): void { $this->setAttribute('origins', \collect($value)->filter() ->map(fn ($originEnumValue) => OriginEnum::make($originEnumValue)->value()) ->all()); } /** * Get claims_enum attribute * * @return \Illuminate\Support\Collection<\App\Enums\Ingredients\ClaimEnum> */ public function getClaimsEnumAttribute(): Collection { return \collect($this->getAttribute('claims'))->filter() ->map(fn (int $originEnumValue) => ClaimEnum::make($originEnumValue)); } /** * Set claims_enum attribute * * @param mixed $value * @return void */ public function setClaimsEnumAttribute(mixed $value): void { $this->setAttribute('claims', \collect($value)->filter() ->map(fn ($claimEnumValue) => OriginEnum::make($claimEnumValue)->value()) ->all()); } // * RELATIONSHIPS /** * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function productComments() { return $this->hasMany(ProductComment::class); } /** * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ public function user() { return $this->belongsTo(User::class); } /** * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ public function brand() { return $this->belongsTo(Brand::class); } /** * MorphToMany keywords relation. * * @return \Illuminate\Database\Eloquent\Relations\MorphToMany */ public function productKeywords() { return $this->morphToMany(Keyword::class, 'keywordable'); } }