ldc-startcoq/app/Models/Product.php
Jérémy Munsch 615263f233
Refactor and fixes
Fixes #33
Fixes #34
2023-09-08 16:02:21 +02:00

450 lines
16 KiB
PHP

<?php
namespace App\Models;
use App\Enums\Products\CertificationEnum;
use App\Enums\Products\ClaimEnum;
use App\Enums\Products\DistributionNetworkEnum;
use App\Enums\Products\GeoAreaEnum;
use App\Enums\Products\OriginEnum;
use App\Enums\Products\RangeEnum;
use App\Enums\Products\RayEnum;
use App\Enums\Products\SourceEnum;
use App\Http\Resources\ProductResource;
use App\Lib\Helpers\FileStorageHelper;
use App\Lib\Helpers\ToolboxHelper;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Storage;
use Mccarlosen\LaravelMpdf\LaravelMpdf;
use Symfony\Component\HttpFoundation\StreamedResponse;
/**
* FIXME: using migration, do not set nullable props without a real need.
*
* @property integer $id
* @property integer $user_id
* @property integer $brand_id
* @property string $name
* @property \Carbon\Carbon $seen_at
* @property \App\Enums\Products\SourceEnum $source
* @property string|null $fair
* @property string|null $shop
* @property string|null $catalogue
* @property string|null $gencod
* @property string|null $picture
* @property string $picture_alt
* @property string $picture_title
* @property array|null $docs
* @property string|null $url
* @property \App\Enums\Products\CertificationEnum|null $certification
* @property array<integer>|null $origins
* @property \Illuminate\Support\Collection<\App\Enums\Products\OriginEnum> $origins_enum
* @property array<integer>|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) {
$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<integer> $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);
}
// * 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');
}
}