'bool', 'isLocked' => 'bool', 'statut' => MediaStatut::class, '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) { // * Flush Eloquent cache. self::flushQueryCache(); /** @var \App\Models\User */ $user = auth()->user(); MediaCreated::dispatch($user, $model); if (empty($model->isDuplicate)) { /** @throws \RuntimeException */ $model->addToElasticSearch(); } }); static::updating(function (self $model) { if ($model->statut === MediaStatut::brouillon 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) { // * Flush Eloquent cache. self::flushQueryCache(); /** @var \App\Models\User */ $user = auth()->user(); // * If update is not running from console, we prevent soft delete before trigerring update event if ($user and !\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) { // * Flush Eloquent cache. self::flushQueryCache(); /** @var \App\Models\User */ $user = auth()->user(); MediaDestroyed::dispatch($user, $model); /** @throws \RuntimeException */ $model->removeFromElasticSearch(); }); } // * Laravel Behaviors /** * Get the route key for the model. * * @return string */ public function getRouteKeyName() { return 'uid'; } /** * Set the base cache tags that will be present * on all queries. * * ! This is used to flush caching * * @return array */ protected function getCacheBaseTags(): array { return [ 'dam_media', ]; } /** * 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, 'panier_media'); } // * 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 * @throws \RuntimeException If the media does not have a subject. */ 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); if (!$this->sujet) { throw new \RuntimeException('This medias does not have any subject wich is forbidden'); } /** @var \Illuminate\Filesystem\FilesystemAdapter */ $storage = Storage::disk('media'); $this->sujet->formats->map(function (Format $format) use ($storage) { $filePath = "{$this->folder}/{$this->uid}_{$format->libelle}.{$format->extension}"; if ($storage->exists($filePath)) { $storage->delete($filePath); } }); } /** * 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(); $formats->map(function (Format $format) use ($storage, $im) { $targetWidth = \floatval($format->largeur); $targetHeight = \floatval($format->hauteur) == 0 ? \floatval(floor(($this->hauteur * $format->largeur) / $this->largeur)) : \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(); } $im->setImageFormat(MediaExtension::gif); $storage->put( $this->preview_relative_path, $im->getImagesBlob() ); } else { $im->setImageFormat(\pathinfo($this->preview_relative_path, \PATHINFO_EXTENSION)); 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->value}"); } 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') ]); } }