248 lines
7.4 KiB
PHP
248 lines
7.4 KiB
PHP
<?php
|
|
|
|
namespace App\Classes\Traits\Repairs;
|
|
|
|
use App\Classes\AttachmentsManager;
|
|
use App\Exceptions\AttachmentNotFound;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Illuminate\Support\Str;
|
|
use Intervention\Image\Exceptions\DecoderException;
|
|
use Intervention\Image\Laravel\Facades\Image;
|
|
use League\Flysystem\StorageAttributes;
|
|
|
|
/**
|
|
* Trait for AttachmentsManager
|
|
*/
|
|
trait RepairsImages
|
|
{
|
|
/**
|
|
* Repair all attached images
|
|
*/
|
|
private function repairImages()
|
|
{
|
|
$this->load();
|
|
|
|
$files = $this->disk->listContents(
|
|
sprintf(
|
|
'%s/%s/%s',
|
|
$this->bundle->getDataDir(),
|
|
$this->attachmentsDir,
|
|
AttachmentsManager::Images
|
|
),
|
|
false
|
|
)
|
|
->filter(fn (StorageAttributes $attributes) => $attributes->isFile())
|
|
->map(fn (StorageAttributes $attributes) => $attributes->path())
|
|
->toArray();
|
|
|
|
foreach ($files as $file) {
|
|
$content = $this->disk->get($file);
|
|
$filename = sprintf('%s/%s', AttachmentsManager::Images, pathinfo($file, PATHINFO_BASENAME));
|
|
|
|
$data = [
|
|
'contents' => $content,
|
|
'filename' => $filename,
|
|
];
|
|
|
|
$this->disk->delete($file);
|
|
|
|
$this->add($data);
|
|
}
|
|
|
|
foreach ($this->manager->get('files', []) as $ref => $data) {
|
|
$this->repairKnownImage($ref, $data);
|
|
}
|
|
|
|
$this->save();
|
|
}
|
|
|
|
/**
|
|
* Repair a single image. Move it to appropriate directory and create or
|
|
* update variations.
|
|
*/
|
|
private function repairKnownImage(string $ref, array $data)
|
|
{
|
|
if (!empty($data['url'])) {
|
|
$data['filename'] = $data['url'];
|
|
|
|
unset($data['url']);
|
|
|
|
$this->manager->set(sprintf('files.%s', $ref), $data);
|
|
}
|
|
|
|
$extension = pathinfo($data['filename'], PATHINFO_EXTENSION);
|
|
$currentFullPath = sprintf('%s%s', $this->bundle->getDataDir(), $data['filename']);
|
|
$expectedFullPath = sprintf(
|
|
'%s%s/%s/%s/original.%s',
|
|
$this->bundle->getDataDir(),
|
|
$this->attachmentsDir,
|
|
$this->kind,
|
|
$ref,
|
|
$extension
|
|
);
|
|
|
|
if (!$this->disk->exists($currentFullPath)) {
|
|
$this->deleteAttachment($ref);
|
|
|
|
return;
|
|
}
|
|
|
|
if (empty($data['last_modified'])) {
|
|
$data['last_modified'] = Carbon::parse($this->disk->lastModified($currentFullPath))->toIso8601String();
|
|
}
|
|
|
|
if ($currentFullPath !== $expectedFullPath) {
|
|
$filename = pathinfo($currentFullPath, PATHINFO_BASENAME);
|
|
$tempname = Str::random(6);
|
|
$tempPath = str_replace($filename, $tempname, $currentFullPath);
|
|
|
|
$this->disk->move($currentFullPath, $tempPath);
|
|
$this->disk->move($tempPath, $expectedFullPath);
|
|
|
|
$data['last_modified'] = Carbon::parse($this->disk->lastModified($expectedFullPath))->toIso8601String();
|
|
$data['filename'] = sprintf(
|
|
'%s/%s/%s/original.%s',
|
|
$this->attachmentsDir,
|
|
$this->kind,
|
|
$ref,
|
|
$extension
|
|
);
|
|
|
|
$parentDir = dirname($currentFullPath);
|
|
|
|
if (empty($this->disk->listContents($parentDir, true))) {
|
|
$this->disk->delete($parentDir);
|
|
}
|
|
}
|
|
|
|
$checksum = $this->disk->checksum($expectedFullPath);
|
|
|
|
if (!empty($data['checksum']) && $data['checksum'] !== $checksum) {
|
|
Log::warning('File checksum has changed', [
|
|
'file' => $expectedFullPath,
|
|
'oldChecsum' => $data['checksum'],
|
|
'newChecksum' => $checksum,
|
|
]);
|
|
}
|
|
|
|
try {
|
|
Image::read($this->disk->get($expectedFullPath));
|
|
} catch (DecoderException $ex) {
|
|
Log::error('Corrupted file detected!', [
|
|
'file' => $expectedFullPath,
|
|
]);
|
|
}
|
|
|
|
$data['checksum'] = $checksum;
|
|
|
|
$this->manager->set(sprintf('files.%s', $ref), $data);
|
|
|
|
$this->syncImageVariants($ref);
|
|
}
|
|
|
|
/**
|
|
* Synchronize image variants (apply pre-defined filters)
|
|
*/
|
|
private function syncImageVariants(string $ref)
|
|
{
|
|
$currentVariants = $this->manager->get(sprintf('variants.%s', $ref), []);
|
|
$configVariants = array_keys(config('imagefilters'));
|
|
|
|
// Synchronize or delete currently declared variants
|
|
foreach ($currentVariants as $filter => $variantData) {
|
|
if (!in_array($filter, $configVariants)) {
|
|
$this->deleteVariant($ref, $filter);
|
|
|
|
continue;
|
|
}
|
|
|
|
$this->syncImageVariant($ref, $filter);
|
|
}
|
|
|
|
// Synchronize variants with the ones expected by configuration
|
|
foreach ($configVariants as $filter) {
|
|
$this->syncImageVariant($ref, $filter);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Synchronize specific image variant
|
|
*/
|
|
private function syncImageVariant(string $ref, string $filter)
|
|
{
|
|
try {
|
|
$originalData = $this->getAttachmentData($ref);
|
|
} catch (AttachmentNotFound $ex) {
|
|
$this->deleteAttachment($ref);
|
|
|
|
return;
|
|
}
|
|
|
|
$variantData = $this->getVariantData($ref, $filter);
|
|
$variantFilepath = $this->getVariantFullPath($ref, $filter);
|
|
|
|
if (empty($variantData['checksum'])) {
|
|
$variantData['checksum'] = $this->disk->checksum($variantFilepath);
|
|
|
|
$this->manager->set(sprintf('variants.%s.%s', $ref, $filter), $variantData);
|
|
}
|
|
|
|
if (!$this->disk->exists($variantFilepath)) {
|
|
$this->createImageVariant($ref, $filter);
|
|
} else {
|
|
$originalLastModified = Carbon::parse($originalData['last_modified']);
|
|
$variantLastModified = Carbon::parse($variantData['last_modified'] ?? $this->disk->lastModified($variantFilepath));
|
|
|
|
if ($originalLastModified->gt($variantLastModified)) {
|
|
$this->createImageVariant($ref, $filter);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create the variant for specified image and filter
|
|
*/
|
|
private function createImageVariant(string $ref, string $filter)
|
|
{
|
|
$filterClass = config(sprintf('imagefilters.%s', $filter));
|
|
|
|
try {
|
|
$original = $this->getAttachmentFullPath($ref);
|
|
} catch (AttachmentNotFound $ex) {
|
|
$this->deleteAttachment($ref);
|
|
|
|
return;
|
|
}
|
|
|
|
$target = $this->getVariantFullPath($ref, $filter);
|
|
$contents = $this->disk->get($original);
|
|
|
|
try {
|
|
$image = Image::read($contents);
|
|
} catch (DecoderException $ex) {
|
|
$this->deleteAttachment($ref);
|
|
|
|
return;
|
|
}
|
|
|
|
$variantData = $this->getVariantData($ref, $filter);
|
|
|
|
$image->modify(new $filterClass());
|
|
|
|
if ($image->isAnimated()) {
|
|
$contents = (string) $image->toGif();
|
|
} else {
|
|
$contents = (string) $image->toWebp();
|
|
}
|
|
|
|
$this->disk->put($target, $contents);
|
|
|
|
$variantData['filename'] = $this->getVariantRelativePath($ref, $filter);
|
|
$variantData['last_modified'] = now();
|
|
$variantData['checksum'] = $this->disk->checksum($target);
|
|
|
|
$this->manager->set(sprintf('variants.%s.%s', $ref, $filter), $variantData);
|
|
}
|
|
}
|