ldc-dam/app/Models/Media.php
Jérémy Munsch 9767e62c72
Update NPM deps and webpack.mix.js
composer require ldc/web-status-checker-laravel remove Old static status checker

Fix ImportMedia

WIP commands OK

many fixes

Fix and try to optimize folder component

Fix front icons + actions based on icons

Favico

front fix alert

Fix Media upload on depot

fix folder component buttons

Full front fix with perf due tu vuejs

Download medias

Navigate within medias superfix

Move media + loader

isLocked boolean and remove media fix

Folder scroll fix

arcadia link color red

Fix panier show external

Tons of fixes back as front

REwrited all
2022-07-22 17:00:48 +02:00

771 lines
23 KiB
PHP

<?php
namespace App\Models;
use App\Enums\MediaExtension;
use App\Events\MediaChangedLabel;
use App\Events\MediaCreated;
use App\Events\MediaDestroyed;
use App\Events\MediaUpdated;
use App\Lib\ToolboxHelper;
use App\Traits\Models\BindedToElasticSearch;
use App\Models\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Prunable;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\ValidationException;
class Media extends Model
{
use BindedToElasticSearch;
use Prunable;
use SoftDeletes;
/**
* The relations to eager load on every query.
*
* @var array
*/
protected $with = [
'owner', 'tags', 'etiquette', 'articles', 'sujet',
'typeMedia', 'societes', 'marques'
];
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
// ! id est obligatoire ici pour Jobs/XmlArcadiaMediasIn
'id',
'libelle',
'type_vue_id',
'variante_id',
'copyright_id',
'etiquette_id',
'langue_id',
'commentaire',
'version',
'statut',
];
/**
* The attributes that should be cast.
*
* @var array
*/
protected $casts = [
'isDefault' => 'bool',
'isLocked' => 'bool',
'extension' => MediaExtension::class
];
/**
* ES The elasticsearch settings.
*
* @var array
*/
protected $indexSettings = [
'analysis' => [
'char_filter' => [
'replace' => [
'type' => 'mapping',
'mappings' => [
'&=> and '
],
],
],
'filter' => [
'word_delimiter' => [
'type' => 'word_delimiter',
'split_on_numerics' => false,
'split_on_case_change' => true,
'generate_word_parts' => true,
'generate_number_parts' => true,
'catenate_all' => true,
'preserve_original' => true,
'catenate_numbers' => true,
]
],
'analyzer' => [
'default' => [
'type' => 'custom',
'char_filter' => [
'html_strip',
'replace',
],
'tokenizer' => 'whitespace',
'filter' => [
'lowercase',
'word_delimiter',
],
],
],
'normalizer' => [
'my_normalizer' => [
'type' => 'custom',
'char_filter' => [],
'filter' => [
'lowercase'
]
]
]
],
];
/**
* ES Mapping Properties
*
* @var array
*/
protected $mappingProperties = array(
'libelle' => array(
'type' => 'text',
'analyzer' => 'default',
'fields' => [
'keyword' => [
'type' => 'keyword',
'normalizer' => 'my_normalizer'
]
],
'fielddata' => true
),
'commentaire' => array(
'type' => 'text',
'analyzer' => 'default',
'fields' => [
'keyword' => [
'type' => 'keyword',
'normalizer' => 'my_normalizer'
]
],
'fielddata' => true
),
'created_at' => [
'type' => 'date',
"format" => "yyyy-MM-dd HH:mm:ss"
],
'updated_at' => [
'type' => 'date',
"format" => "yyyy-MM-dd HH:mm:ss"
],
'version' => [
'type' => 'text',
'fielddata' => true
],
'arcadia_id' => [
'type' => 'text',
'analyzer' => 'standard'
],
'articles.noArt' => [
'type' => 'text',
'analyzer' => 'standard'
],
'articles.libCcial' => [
'type' => 'text',
'analyzer' => 'default',
'fields' => [
'keyword' => [
'type' => 'keyword',
'normalizer' => 'my_normalizer'
]
],
'fielddata' => true
]
);
/**
* Perform any actions required after the model boots.
*
* @return void
*/
protected static function booted()
{
static::created(function (self $model) {
/** @var \App\Models\User */
$user = auth()->user();
MediaCreated::dispatch($user, $model);
/** @throws \RuntimeException */
$model->addToElasticSearch();
});
static::updating(function (self $model) {
if ($model->statut === 0 and $model->arcadia_id) {
throw ValidationException::withMessages([
'statut' => trans('Impossible de changer le statut en brouillon quand la fiche est liée à arcadia')
]);
}
});
static::updated(function (self $model) {
/** @var \App\Models\User */
$user = auth()->user();
// * Prevent soft delete trigerring update event
if (!\array_key_exists('deleted_at', $model->getChanges())) {
MediaUpdated::dispatch($user, $model);
}
if ($model->getRawOriginal('etiquette_id') != $model->etiquette_id) {
/** @var \App\Models\Etiquette|null */
$oldLabel = Etiquette::where('etiquette_id', $model->getRawOriginal('etiquette_id'))->first();
/** @var \App\Models\Etiquette|null */
$newLabel = Etiquette::where('etiquette_id', $model->etiquette_id)->first();
MediaChangedLabel::dispatch($user, $model, $oldLabel, $newLabel);
}
/** @throws \RuntimeException */
$model->addToElasticSearch();
});
static::deleted(function (self $model) {
/** @var \App\Models\User */
$user = auth()->user();
MediaDestroyed::dispatch($user, $model);
/** @throws \RuntimeException */
$model->removeFromElasticSearch();
$model->deleteFilesOnAllPath();
});
}
// * Laravel Behaviors
/**
* Get the prunable model query.
*
* @return \Illuminate\Database\Eloquent\Builder
*/
public function prunable()
{
return static::query();
}
/**
* Prepare the model for pruning.
*
* @return void
* @throws \RuntimeException If pruning the model fails.
*/
protected function pruning()
{
$storage = Storage::disk('media');
if ($storage->exists($this->relative_path) and !$storage->delete($this->relative_path)) {
throw new \RuntimeException("Could not delete $this->relative_path from storage.");
}
if ($storage->exists($this->preview_relative_path) and !$storage->delete($this->preview_relative_path)) {
throw new \RuntimeException("Could not delete $this->preview_relative_path from storage.");
}
if ($storage->exists($this->thumb_relative_path) and !$storage->delete($this->thumb_relative_path)) {
throw new \RuntimeException("Could not delete $this->thumb_relative_path from storage.");
}
}
// * RELATIONSHIPS
/**
* BelongsTo dossier relation
* Get the dossier that owns the media.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function dossier()
{
return $this->belongsTo(Dossier::class, 'dossier_id');
}
/**
* BelongsTo sujet relation
* Get the sujet that owns the media.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function sujet()
{
return $this->belongsTo(Sujet::class, 'sujet_id');
}
/**
* BelongsTo typeMedia relation
* Get the typeMedia that owns the media.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function typeMedia()
{
return $this->belongsTo(TypeMedia::class, 'type_media_id');
}
/**
* BelongsTo typevue relation
* Get the typevue that owns the media.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function typevue()
{
return $this->belongsTo(TypeVue::class);
}
/**
* BelongsTo langue relation
* Get the langue that owns the media.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function langue()
{
return $this->belongsTo(Langue::class, 'langue_id');
}
/**
* BelongsTo copyright relation
* Get the copyright that owns the media.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function copyright()
{
return $this->belongsTo(Copyright::class, 'copyright_id');
}
/**
* BelongsTo etiquette relation
* Get the etiquette that owns the media.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function etiquette()
{
return $this->belongsTo(Etiquette::class, 'etiquette_id');
}
/**
* BelongsToMany tags relation
* The tags that belong to the media.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function tags()
{
return $this->belongsToMany(Tag::class, 'media_tag');
}
/**
* BelongsToMany articles relation
* The articles that belong to the media.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function articles()
{
return $this->belongsToMany(Article::class, 'media_article');
}
/**
* BelongsToMany societes relation
* The societes that belong to the media.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function societes()
{
return $this->belongsToMany(Societe::class, 'media_societe');
}
/**
* BelongsToMany marques relation
* The marques that belong to the media.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function marques()
{
return $this->belongsToMany(Marque::class, 'media_marque', 'media_id', 'marque_id');
}
/**
* BelongsTo user relation
* Get the user that owns the media.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function owner()
{
return $this->belongsTo(User::class, 'created_by');
}
/**
* HasMany historiques relation
* Get the historiques for the media.
*
* @return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function historiques()
{
return $this->hasMany(Historique::class);
}
/**
* BelongsToMany paniers relation
* The paniers that belong to the media.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function paniers()
{
return $this->belongsToMany(Panier::class);
}
// * METHODS
/**
* Get basename
*
* @return string
*/
public function getBasenameAttribute(): string
{
return "{$this->uid}.{$this->extension->value}";
}
/**
* Get preview_basename
*
* @return string
*/
public function getPreviewBasenameAttribute(): string
{
$extension = match ($this->extension) {
MediaExtension::pdf, MediaExtension::ai, MediaExtension::psd => MediaExtension::png,
default => $this->extension,
};
return "{$this->uid}_preview.{$extension->value}";
}
/**
* Get thumb_basename
*
* @return string
*/
public function getThumbBasenameAttribute(): string
{
$extension = match ($this->extension) {
MediaExtension::pdf, MediaExtension::ai, MediaExtension::psd => MediaExtension::png,
default => $this->extension,
};
return "{$this->uid}_thumb.{$extension->value}";
}
/**
* Get relative_path
*
* @return string
*/
public function getRelativePathAttribute(): string
{
return "{$this->folder}/{$this->basename}";
}
/**
* Get preview_relative_path
*
* @return string
*/
public function getPreviewRelativePathAttribute(): string
{
return "{$this->folder}/{$this->preview_basename}";
}
/**
* Get thumb_relative_path
*
* @return string
*/
public function getThumbRelativePathAttribute(): string
{
return "{$this->folder}/{$this->thumb_basename}";
}
/**
* Get full_path
*
* @return string
*/
public function getFullPathAttribute(): string
{
/** @var \Illuminate\Filesystem\FilesystemAdapter */
$storage = Storage::disk('media');
return $storage->path($this->relative_path);
}
/**
* Get preview_full_path
*
* @return string
*/
public function getPreviewFullPathAttribute(): string
{
/** @var \Illuminate\Filesystem\FilesystemAdapter */
$storage = Storage::disk('media');
return $storage->path($this->preview_relative_path);
}
/**
* Get thumb_full_path
*
* @return string
*/
public function getThumbFullPathAttribute(): string
{
/** @var \Illuminate\Filesystem\FilesystemAdapter */
$storage = Storage::disk('media');
return $storage->path($this->thumb_relative_path);
}
/**
* Delete files on path
*
* @return void
*/
public function deleteFilesOnAllPath()
{
/** @var \Illuminate\Filesystem\FilesystemAdapter */
$storage = Storage::disk('media');
$storage->delete($this->relative_path);
$storage->delete($this->thumb_relative_path);
$storage->delete($this->preview_relative_path);
// TODO: Add delete forl all sujet formats.
}
/**
* Add this media into Elastic Search
*
* @return void
* @throws \RuntimeException If adding to ES failed.
*/
public function addToElasticSearch()
{
$this->removeFromElasticSearch();
try {
$this->refresh();
$this->addToIndex();
} catch (\Exception $e) {
report($e);
throw new \RuntimeException("Failed to add media '$this->arcadia_id' to ES", 0, $e);
}
}
/**
* Remove this media from ElasticSearch
*
* @return void
* @throws \RuntimeException If someting goes wrong.
*/
public function removeFromElasticSearch()
{
$esExist = self::searchByQuery(['match' => ['arcadia_id' => strval($this->arcadia_id)]]);
// * Remove all from elastic if exists
if ($esExist->totalHits() > 0) {
try {
$this->removeFromIndex();
} catch (\Exception $e) {
report($e);
throw new \RuntimeException("Failed to add media '$this->arcadia_id' to ES", 0, $e);
}
}
}
/**
* Create thumbnail image for a Media Subject on very of its formats.
* - Creation des vignettes en lien avec le sujet du média
*
* @return void
* @throws \ImagickException On errors.
* @throws \RuntimeException If media file is missing.
* @phpcs:ignore Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
*/
public function createSubjectThumbnails()
{
/** @var \Illuminate\Filesystem\FilesystemAdapter */
$storage = Storage::disk('media');
if (!$storage->exists($this->relative_path)) {
throw new \RuntimeException("Missing Media file at path '$this->relative_full_path'");
}
$im = new \Imagick();
$im->readImageBlob(
$storage->get($this->relative_path),
$this->basename
);
$im->setImageBackgroundColor('#ffffff');
$im->setImageAlphaChannel(\Imagick::ALPHACHANNEL_REMOVE);
$im->mergeImageLayers(\Imagick::LAYERMETHOD_FLATTEN);
$im->stripImage();
if (in_array($this->extension, [MediaExtension::pdf, MediaExtension::ai, MediaExtension::psd])) {
$im->setIteratorIndex(0);
}
/** @var \Illuminate\Support\Collection<\App\Models\Format> $formats */
$formats = optional($this->sujet)->formats ?? collect();
foreach ($formats as $format) {
$targetWidth = \floatval($format->largeur);
if (\floatval($format->hauteur) == 0) {
$targetHeight = \floatval(floor(($this->hauteur * $format->largeur) / $this->largeur));
} else {
$targetHeight = \floatval($format->hauteur);
}
$im->setImageFormat($format->extension);
if ($this->largeur >= $targetWidth || $this->hauteur >= $targetHeight) {
$im->thumbnailImage($targetWidth, $targetHeight, true);
}
$storage->put(
"{$this->folder}/{$this->uid}_{$format->libelle}.{$format->extension}",
$im->getImageBlob()
);
}
$im->destroy();
unset($im);
}
/**
* Create thumbnail image for a Media.
* - Creation des vignettes de base
*
* @return void
* @throws \ImagickException On errors.
* @throws \RuntimeException If media file is missing.
* @phpcs:ignore Squiz.Commenting.FunctionCommentThrowTag.WrongNumber
*/
public function createThumbnail()
{
/** @var \Illuminate\Filesystem\FilesystemAdapter */
$storage = Storage::disk('media');
if (!$storage->exists($this->relative_path)) {
throw new \RuntimeException("Missing Media file at path '$this->relative_full_path'");
}
$im = new \Imagick();
$im->readImageBlob(
$storage->get($this->relative_path),
$this->basename
);
if (in_array($this->extension, [MediaExtension::pdf, MediaExtension::ai, MediaExtension::psd])) {
$im->setIteratorIndex(0);
}
/*
Handle CMJN - DOESN'T WORK
if ($im->getImageColorspace() === \Imagick::COLORSPACE_CMYK){
$im->transformImageColorspace(\Imagick::COLORSPACE_RGB);
}
*/
// * create preview
/** @var \App\Models\Format $format */
$format = Format::withoutGlobalScope('App\Scopes\SystemScope')
->where('libelle', '=', 'preview')->firstOrFail();
if ($this->extension == MediaExtension::gif) {
if ($this->largeur >= $format->largeur || $this->hauteur >= $format->hauteur || $this->largeur == "") {
$im = $im->coalesceImages();
foreach ($im as $frame) {
$frame->thumbnailImage($format->largeur, $format->hauteur);
$frame->setImagePage($format->largeur, $format->hauteur, 0, 0);
}
$im = $im->deconstructImages();
}
$storage->put(
$this->preview_relative_path,
$im->getImagesBlob()
);
} else {
if ($this->largeur >= $format->largeur || $this->hauteur >= $format->hauteur || $this->largeur == "") {
$im->thumbnailImage($format->largeur, $format->hauteur, true);
}
$storage->put(
$this->preview_relative_path,
$im->getImageBlob()
);
} //end if
// * create thumb
/** @var \App\Models\Format $format */
$format = Format::withoutGlobalScope('App\Scopes\SystemScope')
->where('libelle', '=', 'thumb')->firstOrFail();
if ($this->extension != MediaExtension::png) {
$im->setImageBackgroundColor('#ffffff');
$im->setImageAlphaChannel(\Imagick::ALPHACHANNEL_REMOVE);
$im->mergeImageLayers(\Imagick::LAYERMETHOD_FLATTEN);
$im->stripImage();
}
$im->thumbnailImage($format->largeur, $format->hauteur, true);
$storage->put(
$this->thumb_relative_path,
$im->getImageBlob()
);
$im->destroy();
unset($im);
}
/**
* Renvoi le chemin attendu d'un média selon le format demandé
*
* @param \App\Models\Format $format
* @return string
* @throws \RuntimeException If Format is not handled.
*/
public function getPath(Format $format): string
{
/** @var \Illuminate\Filesystem\FilesystemAdapter */
$storage = Storage::disk('media');
// * format spécifique
if ($format->system) {
if (in_array($this->extension, ['gif', 'jpeg', 'jpg', 'png', 'pdf', 'ai', 'psd'])) {
return $storage->path("/{$this->folder}/{$this->uid}_{$format->libelle}.{$format->extension->value}");
}
return $storage->path($this->relative_path);
}
// * format système (thumb et preview)
if ($format->system) {
if (in_array($this->extension, ['gif', 'jpeg', 'jpg', 'png'])) {
// * Gestion du cas de format qui fixe l'extension.
$file_extension = $format->extension != '' ? $format->extension : $this->extension;
return $storage->path("/{$this->folder}/{$this->uid}_{$format->libelle}.{$file_extension}");
} elseif (in_array($this->extension, ['pdf', 'ai', 'psd'])) {
return $storage->path("/{$this->folder}/{$this->uid}_{$format->libelle}.png");
}
return $storage->path("/{$this->folder}/{$this->uid}.{$this->extension}");
}
throw new \RuntimeException("Unhandled Format $format->id $format->libelle");
}
/**
* Convert the model instance to an array.
*
* @return array
*/
public function toArray()
{
/** @var \Illuminate\Filesystem\FilesystemAdapter */
$storage = Storage::disk('media');
return array_merge(parent::toArray(), [
'size' => ToolboxHelper::formatBytes($this->taille),
'url' => $storage->url($this->relative_path),
'preview_url' => $storage->url($this->preview_relative_path),
'thumb_url' => $storage->url($this->thumb_relative_path),
'jpeg_url' => $storage->url($this->jpeg_relative_path),
'created_at' => $this->created_at->formatLocalized('%Y-%m-%d %H:%M:%S'),
'updated_at' => $this->updated_at->formatLocalized('%Y-%m-%d %H:%M:%S')
]);
}
}