load(); 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); } } $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) { $originalData = $this->getAttachmentData($ref); $variantData = $this->getVariantData($ref, $filter); $variantFilepath = $this->getVariantFullPath($ref, $filter); 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)); $original = $this->getAttachmentFullPath($ref); $target = $this->getVariantFullPath($ref, $filter); $contents = $this->disk->get($original); $image = Image::read($contents); $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(); $this->manager->set(sprintf('variants.%s.%s', $ref, $filter), $variantData); } }