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); } }