diff --git a/.gitignore b/.gitignore index 59a7578..6829e70 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ composer.lock /node_modules /yarn.lock .idea +.phpunit.result.cache diff --git a/README.md b/README.md index c8d91f2..8230575 100755 --- a/README.md +++ b/README.md @@ -65,12 +65,13 @@ Thus, if you have ``, the returned #### Src images on S3, local crops -This is a good solution for a load balanced enviornment. Each app server will end up with it’s own cache of cropped images, so there is some wasted space. But the web server (Apache, etc) can still serve the crops directly on subsequent crop requests. +This is a good solution for a load balanced environment. Each app server will end up with it’s own cache of cropped images, so there is some wasted space. But the web server (Apache, etc) can still serve the crops directly on subsequent crop requests. A tmp_disk must also be defined to temporarily store the source image. ```php // Croppa config.php return [ 'src_disk' => 's3', + 'tmp_disk' => 'temp', 'crops_disk' => 'public', 'path' => 'storage/(.*)$', ]; diff --git a/config/config.php b/config/config.php index 62f551a..62f7a8c 100755 --- a/config/config.php +++ b/config/config.php @@ -18,6 +18,15 @@ */ 'crops_disk' => 'public', + /* + * If using a remote src_disk, a local disk must be specified where remote + * source images are temporarily copied. + * (The Intervention Image library can't download remote images - + * see: https://image.intervention.io/v3/introduction/upgrade) + * Set to false/null/empty-string to disable. + */ + 'tmp_disk' => 'public', + /* * Maximum number of sizes to allow for a particular source file. This is to * limit scripts from filling up your hard drive with images. Set to false or diff --git a/src/Storage.php b/src/Storage.php index 0a782f4..a1b6223 100644 --- a/src/Storage.php +++ b/src/Storage.php @@ -28,6 +28,16 @@ final class Storage */ private $srcDisk; + /** + * @var ?FilesystemAdapter + */ + private $tmpDisk; + + /** + * @var bool + */ + private $tmpPath = ''; + /** * Inject dependencies. */ @@ -84,6 +94,26 @@ public function getSrcDisk(): FilesystemAdapter return $this->srcDisk; } + /** + * Set the tmp disk. + */ + public function setTmpDisk(FilesystemAdapter $disk): void + { + $this->tmpDisk = $disk; + } + + /** + * Get the tmp disk or make via the config. + */ + public function getTmpDisk(): FilesystemAdapter + { + if (empty($this->tmpDisk)) { + $this->setTmpDisk($this->makeDisk($this->config['tmp_disk'])); + } + + return $this->tmpDisk; + } + /** * "Mount" disks given the config. */ @@ -124,13 +154,23 @@ public function cropExists(string $path): bool */ public function path(string $path): string { - $disk = $this->getSrcDisk(); - if ($disk->fileExists($path)) { - if ($disk->getAdapter() instanceof LocalFilesystemAdapter) { - return $disk->path($path); + $srcDisk = $this->getSrcDisk(); + if ($srcDisk->fileExists($path)) { + if ($srcDisk->getAdapter() instanceof LocalFilesystemAdapter) { + return $srcDisk->path($path); + } + + // If a tmp_disk has been configured, copy file from remote srcDisk to tmpDisk + if ($this->config['tmp_disk']) { + $tmpDisk = $this->getTmpDisk(); + $tmpDisk->writeStream($path, $srcDisk->readStream($path)); + $this->tmpPath = $path; + return $tmpDisk->path($path); } - return $disk->url($path); + // With Intervention 3, this will lead to a DecoderException ("Unable to decode input") + // We should probably throw an exception here to inform the developer that a tmp_disk is required. + return $srcDisk->url($path); } throw new NotFoundHttpException('Croppa: Src image is missing'); @@ -148,6 +188,18 @@ public function writeCrop(string $path, string $contents): void } catch (FilesystemException $e) { // don't throw exception anymore as mentioned in PR #164 } + $this->cleanup(); + } + + /** + * Cleanup: delete tmp file if required. + */ + public function cleanup(): void + { + if ($this->tmpPath <> '') { + $this->getTmpDisk()->delete($this->tmpPath); + $this->tmpPath = ''; + } } /**