From 2d24e341dc87c9a408d0db05b62af4d75d996b11 Mon Sep 17 00:00:00 2001 From: reliq Date: Sun, 7 Mar 2021 01:04:48 +0100 Subject: [PATCH] remote upload feature --- composer.json | 3 +- phpunit.xml | 78 ++++---- src/Contract/FileHelper.php | 52 +++++ src/Contract/ImageUploader.php | 11 +- src/Exception/UrlUploadFailed.php | 32 +++ src/Helper/FileHelper.php | 242 +++++++++++++++++++++++ src/Service/ImageDispenser.php | 13 +- src/Service/ImageUploader.php | 65 +++++- tests/Unit/Service/ImageUploaderTest.php | 119 ++++++++++- 9 files changed, 552 insertions(+), 63 deletions(-) create mode 100644 src/Exception/UrlUploadFailed.php diff --git a/composer.json b/composer.json index a7b1a1c..ca3ae78 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,8 @@ "intervention/image": "^2.4", "intervention/imagecache": "^2.3", "reliqarts/laravel-common": "^5.4", - "ext-json": "*" + "ext-json": "*", + "ext-fileinfo": "*" }, "require-dev": { "orchestra/testbench": "4 - 6", diff --git a/phpunit.xml b/phpunit.xml index e1043a8..997326e 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,47 +1,35 @@ - - - - - tests - - - tests/Feature - - - tests/Unit - - - - - src - - src/Concerns - - - - - - - - - - - - - - - - + + + + src + + + src/Concerns + + + + + + + + + tests + + + tests/Feature + + + tests/Unit + + + + + + + + + + + diff --git a/src/Contract/FileHelper.php b/src/Contract/FileHelper.php index a1cc4f7..ac18ade 100644 --- a/src/Contract/FileHelper.php +++ b/src/Contract/FileHelper.php @@ -4,6 +4,10 @@ namespace ReliqArts\GuidedImage\Contract; +use Illuminate\Http\UploadedFile; +use Symfony\Component\HttpFoundation\File\Exception\FileException; +use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; + interface FileHelper { public function hashFile(string $filePath): string; @@ -12,4 +16,52 @@ public function hashFile(string $filePath): string; * @return array|false an array with 7 elements, false on failure */ public function getImageSize(string $filename, array &$imageInfo = []); + + /** + * @param mixed $data + * + * @see file_put_contents() + */ + public function putContents(string $filename, $data): ?int; + + /** + * @see file_get_contents() + */ + public function getContents(string $filename, bool $useIncludePath = false): ?string; + + /** + * @param resource|null $context + * + * @see unlink() + */ + public function unlink(string $filename, $context = null): bool; + + /** + * Returns directory path used for temporary files + * + * @see sys_get_temp_dir() + */ + public function getSystemTempDirectory(): string; + + /** + * @return string|null the new temporary file name or null on failure + * @see tmpnam() + */ + public function tempName(string $directory, string $prefix): ?string; + + /** + * @see mime_content_type() + */ + public function getMimeType(string $filename): ?string; + + /** + * Get associated extension for particular mime type. + */ + public function mime2Ext(string $mime): ?string; + + /** + * @throws FileException + * @throws FileNotFoundException + */ + public function createUploadedFile(string $tempFile, string $originalName, string $mimeType): UploadedFile; } diff --git a/src/Contract/ImageUploader.php b/src/Contract/ImageUploader.php index 11a019d..d5dd462 100644 --- a/src/Contract/ImageUploader.php +++ b/src/Contract/ImageUploader.php @@ -5,15 +5,20 @@ namespace ReliqArts\GuidedImage\Contract; use Illuminate\Http\UploadedFile; +use ReliqArts\GuidedImage\Exception\UrlUploadFailed; use ReliqArts\GuidedImage\Result; interface ImageUploader { /** * Upload and save image. + */ + public function upload(UploadedFile $imageFile, bool $isUrlUpload = false): Result; + + /** + * Upload an image from remote. (allow_url_fopen must be enabled). * - * @param UploadedFile $imageFile File from request - * .e.g. request->file('image'); + * @throws UrlUploadFailed */ - public function upload(UploadedFile $imageFile): Result; + public function uploadFromUrl(string $url): Result; } diff --git a/src/Exception/UrlUploadFailed.php b/src/Exception/UrlUploadFailed.php new file mode 100644 index 0000000..6d9cb44 --- /dev/null +++ b/src/Exception/UrlUploadFailed.php @@ -0,0 +1,32 @@ +getMessage()), + $previousException->getCode(), + $previousException + ); + $instance->url = $url; + + return $instance; + } + + public function getUrl(): string + { + return $this->url; + } +} diff --git a/src/Helper/FileHelper.php b/src/Helper/FileHelper.php index 4e01fde..7249b04 100644 --- a/src/Helper/FileHelper.php +++ b/src/Helper/FileHelper.php @@ -4,7 +4,10 @@ namespace ReliqArts\GuidedImage\Helper; +use Illuminate\Http\UploadedFile; use ReliqArts\GuidedImage\Contract\FileHelper as FileHelperContract; +use Symfony\Component\HttpFoundation\File\Exception\FileException; +use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; /** * @codeCoverageIgnore @@ -23,4 +26,243 @@ public function getImageSize(string $filename, array &$imageInfo = []) { return getimagesize($filename, $imageInfo); } + + public function putContents(string $filename, $data): ?int + { + $bytesWritten = file_put_contents($filename, $data); + + return $bytesWritten ?: null; + } + + public function getContents(string $filename, bool $useIncludePath = false): ?string + { + $content = file_get_contents($filename, $useIncludePath); + + return $content ?: null; + } + + public function unlink(string $filename, $context = null): bool + { + return unlink($filename, $context); + } + + public function getSystemTempDirectory(): string + { + return sys_get_temp_dir(); + } + + public function tempName(string $directory, string $prefix): ?string + { + $filename = tempnam($directory, $prefix); + + return $filename ?: null; + } + + public function getMimeType(string $filename): ?string + { + $mimeType = mime_content_type($filename); + + return $mimeType ?: null; + } + + public function mime2Ext(string $mime): ?string + { + $mimeMap = [ + 'video/3gpp2' => '3g2', + 'video/3gp' => '3gp', + 'video/3gpp' => '3gp', + 'application/x-compressed' => '7zip', + 'audio/x-acc' => 'aac', + 'audio/ac3' => 'ac3', + 'application/postscript' => 'ai', + 'audio/x-aiff' => 'aif', + 'audio/aiff' => 'aif', + 'audio/x-au' => 'au', + 'video/x-msvideo' => 'avi', + 'video/msvideo' => 'avi', + 'video/avi' => 'avi', + 'application/x-troff-msvideo' => 'avi', + 'application/macbinary' => 'bin', + 'application/mac-binary' => 'bin', + 'application/x-binary' => 'bin', + 'application/x-macbinary' => 'bin', + 'image/bmp' => 'bmp', + 'image/x-bmp' => 'bmp', + 'image/x-bitmap' => 'bmp', + 'image/x-xbitmap' => 'bmp', + 'image/x-win-bitmap' => 'bmp', + 'image/x-windows-bmp' => 'bmp', + 'image/ms-bmp' => 'bmp', + 'image/x-ms-bmp' => 'bmp', + 'application/bmp' => 'bmp', + 'application/x-bmp' => 'bmp', + 'application/x-win-bitmap' => 'bmp', + 'application/cdr' => 'cdr', + 'application/coreldraw' => 'cdr', + 'application/x-cdr' => 'cdr', + 'application/x-coreldraw' => 'cdr', + 'image/cdr' => 'cdr', + 'image/x-cdr' => 'cdr', + 'zz-application/zz-winassoc-cdr' => 'cdr', + 'application/mac-compactpro' => 'cpt', + 'application/pkix-crl' => 'crl', + 'application/pkcs-crl' => 'crl', + 'application/x-x509-ca-cert' => 'crt', + 'application/pkix-cert' => 'crt', + 'text/css' => 'css', + 'text/x-comma-separated-values' => 'csv', + 'text/comma-separated-values' => 'csv', + 'application/vnd.msexcel' => 'csv', + 'application/x-director' => 'dcr', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx', + 'application/x-dvi' => 'dvi', + 'message/rfc822' => 'eml', + 'application/x-msdownload' => 'exe', + 'video/x-f4v' => 'f4v', + 'audio/x-flac' => 'flac', + 'video/x-flv' => 'flv', + 'image/gif' => 'gif', + 'application/gpg-keys' => 'gpg', + 'application/x-gtar' => 'gtar', + 'application/x-gzip' => 'gzip', + 'application/mac-binhex40' => 'hqx', + 'application/mac-binhex' => 'hqx', + 'application/x-binhex40' => 'hqx', + 'application/x-mac-binhex40' => 'hqx', + 'text/html' => 'html', + 'image/x-icon' => 'ico', + 'image/x-ico' => 'ico', + 'image/vnd.microsoft.icon' => 'ico', + 'text/calendar' => 'ics', + 'application/java-archive' => 'jar', + 'application/x-java-application' => 'jar', + 'application/x-jar' => 'jar', + 'image/jp2' => 'jp2', + 'video/mj2' => 'jp2', + 'image/jpx' => 'jp2', + 'image/jpm' => 'jp2', + 'image/jpeg' => 'jpeg', + 'image/pjpeg' => 'jpeg', + 'application/x-javascript' => 'js', + 'application/json' => 'json', + 'text/json' => 'json', + 'application/vnd.google-earth.kml+xml' => 'kml', + 'application/vnd.google-earth.kmz' => 'kmz', + 'text/x-log' => 'log', + 'audio/x-m4a' => 'm4a', + 'audio/mp4' => 'm4a', + 'application/vnd.mpegurl' => 'm4u', + 'audio/midi' => 'mid', + 'application/vnd.mif' => 'mif', + 'video/quicktime' => 'mov', + 'video/x-sgi-movie' => 'movie', + 'audio/mpeg' => 'mp3', + 'audio/mpg' => 'mp3', + 'audio/mpeg3' => 'mp3', + 'audio/mp3' => 'mp3', + 'video/mp4' => 'mp4', + 'video/mpeg' => 'mpeg', + 'application/oda' => 'oda', + 'audio/ogg' => 'ogg', + 'video/ogg' => 'ogg', + 'application/ogg' => 'ogg', + 'font/otf' => 'otf', + 'application/x-pkcs10' => 'p10', + 'application/pkcs10' => 'p10', + 'application/x-pkcs12' => 'p12', + 'application/x-pkcs7-signature' => 'p7a', + 'application/pkcs7-mime' => 'p7c', + 'application/x-pkcs7-mime' => 'p7c', + 'application/x-pkcs7-certreqresp' => 'p7r', + 'application/pkcs7-signature' => 'p7s', + 'application/pdf' => 'pdf', + 'application/octet-stream' => 'pdf', + 'application/x-x509-user-cert' => 'pem', + 'application/x-pem-file' => 'pem', + 'application/pgp' => 'pgp', + 'application/x-httpd-php' => 'php', + 'application/php' => 'php', + 'application/x-php' => 'php', + 'text/php' => 'php', + 'text/x-php' => 'php', + 'application/x-httpd-php-source' => 'php', + 'image/png' => 'png', + 'image/x-png' => 'png', + 'application/powerpoint' => 'ppt', + 'application/vnd.ms-powerpoint' => 'ppt', + 'application/vnd.ms-office' => 'ppt', + 'application/msword' => 'doc', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx', + 'application/x-photoshop' => 'psd', + 'image/vnd.adobe.photoshop' => 'psd', + 'audio/x-realaudio' => 'ra', + 'audio/x-pn-realaudio' => 'ram', + 'application/x-rar' => 'rar', + 'application/rar' => 'rar', + 'application/x-rar-compressed' => 'rar', + 'audio/x-pn-realaudio-plugin' => 'rpm', + 'application/x-pkcs7' => 'rsa', + 'text/rtf' => 'rtf', + 'text/richtext' => 'rtx', + 'video/vnd.rn-realvideo' => 'rv', + 'application/x-stuffit' => 'sit', + 'application/smil' => 'smil', + 'text/srt' => 'srt', + 'image/svg+xml' => 'svg', + 'application/x-shockwave-flash' => 'swf', + 'application/x-tar' => 'tar', + 'application/x-gzip-compressed' => 'tgz', + 'image/tiff' => 'tiff', + 'font/ttf' => 'ttf', + 'text/plain' => 'txt', + 'text/x-vcard' => 'vcf', + 'application/videolan' => 'vlc', + 'text/vtt' => 'vtt', + 'audio/x-wav' => 'wav', + 'audio/wave' => 'wav', + 'audio/wav' => 'wav', + 'application/wbxml' => 'wbxml', + 'video/webm' => 'webm', + 'image/webp' => 'webp', + 'audio/x-ms-wma' => 'wma', + 'application/wmlc' => 'wmlc', + 'video/x-ms-wmv' => 'wmv', + 'video/x-ms-asf' => 'wmv', + 'font/woff' => 'woff', + 'font/woff2' => 'woff2', + 'application/xhtml+xml' => 'xhtml', + 'application/excel' => 'xl', + 'application/msexcel' => 'xls', + 'application/x-msexcel' => 'xls', + 'application/x-ms-excel' => 'xls', + 'application/x-excel' => 'xls', + 'application/x-dos_ms_excel' => 'xls', + 'application/xls' => 'xls', + 'application/x-xls' => 'xls', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx', + 'application/vnd.ms-excel' => 'xlsx', + 'application/xml' => 'xml', + 'text/xml' => 'xml', + 'text/xsl' => 'xsl', + 'application/xspf+xml' => 'xspf', + 'application/x-compress' => 'z', + 'application/x-zip' => 'zip', + 'application/zip' => 'zip', + 'application/x-zip-compressed' => 'zip', + 'application/s-compressed' => 'zip', + 'multipart/x-zip' => 'zip', + 'text/x-scriptzsh' => 'zsh', + ]; + + return $mimeMap[$mime] ?? null; + } + + /** + * @throws FileException + * @throws FileNotFoundException + */ + public function createUploadedFile(string $tempFile, string $originalName, string $mimeType): UploadedFile + { + return new UploadedFile($tempFile, $originalName, $mimeType); + } } diff --git a/src/Service/ImageDispenser.php b/src/Service/ImageDispenser.php index ce96f1f..857e79a 100644 --- a/src/Service/ImageDispenser.php +++ b/src/Service/ImageDispenser.php @@ -13,6 +13,7 @@ use Intervention\Image\Exception\NotReadableException; use Intervention\Image\Image; use Intervention\Image\ImageManager; +use InvalidArgumentException; use ReliqArts\GuidedImage\Contract\ConfigProvider; use ReliqArts\GuidedImage\Contract\FileHelper; use ReliqArts\GuidedImage\Contract\GuidedImage; @@ -21,6 +22,7 @@ use ReliqArts\GuidedImage\Demand\Dummy; use ReliqArts\GuidedImage\Demand\Resize; use ReliqArts\GuidedImage\Demand\Thumbnail; +use RuntimeException; final class ImageDispenser implements ImageDispenserContract { @@ -89,6 +91,8 @@ public function getDummyImage(Dummy $demand) * {@inheritdoc} * * @return Image|Response|void + * @throws InvalidArgumentException + * @throws RuntimeException */ public function getResizedImage(Resize $demand) { @@ -134,7 +138,7 @@ static function (Constraint $constraint) use ($demand) { self::RESPONSE_HTTP_OK, $this->getImageHeaders($cacheFilePath, $demand->getRequest(), $image) ?: [] ); - } catch (NotReadableException | FileNotFoundException $exception) { + } catch (FileNotFoundException $exception) { $this->logger->error( sprintf( 'Exception was encountered while building resized image; %s', @@ -154,6 +158,10 @@ static function (Constraint $constraint) use ($demand) { * {@inheritdoc} * * @return Image|Response|void + * @throws InvalidArgumentException + * @throws RuntimeException + * + * @noinspection PhpVoidFunctionResultUsedInspection */ public function getImageThumbnail(Thumbnail $demand) { @@ -305,6 +313,9 @@ private function makeImageWithEncoding($data, ...$encoding): Image ->encode(...$encoding); } + /** + * @throws RuntimeException + */ private function getImageFullUrl(GuidedImage $guidedImage): string { return $this->uploadDisk->url($guidedImage->getUrl(true)); diff --git a/src/Service/ImageUploader.php b/src/Service/ImageUploader.php index c719ad1..347d729 100644 --- a/src/Service/ImageUploader.php +++ b/src/Service/ImageUploader.php @@ -14,8 +14,11 @@ use ReliqArts\GuidedImage\Contract\GuidedImage; use ReliqArts\GuidedImage\Contract\ImageUploader as ImageUploaderContract; use ReliqArts\GuidedImage\Contract\Logger; +use ReliqArts\GuidedImage\Exception\UrlUploadFailed; use ReliqArts\GuidedImage\Model\UploadedImage; use ReliqArts\GuidedImage\Result; +use Symfony\Component\HttpFoundation\File\Exception\FileException; +use Symfony\Component\HttpFoundation\File\Exception\FileNotFoundException; final class ImageUploader implements ImageUploaderContract { @@ -24,6 +27,7 @@ final class ImageUploader implements ImageUploaderContract private const MESSAGE_IMAGE_REUSED = 'Image reused.'; private const UPLOAD_DATE_SUB_DIRECTORIES_PATTERN = 'Y/m/d/H/i'; private const UPLOAD_VISIBILITY = Filesystem::VISIBILITY_PUBLIC; + private const TEMP_FILE_PREFIX = 'LGI_'; private ConfigProvider $configProvider; private Filesystem $uploadDisk; @@ -56,13 +60,14 @@ public function __construct( * * @noinspection StaticInvocationViaThisInspection */ - public function upload(UploadedFile $imageFile): Result + public function upload(UploadedFile $imageFile, bool $isUrlUpload = false): Result { - if (!$this->validate($imageFile)) { + if (!$this->validate($imageFile, $isUrlUpload)) { return new Result(false, self::ERROR_INVALID_IMAGE); } $uploadedImage = new UploadedImage($this->fileHelper, $imageFile, $this->getUploadDestination()); + $existing = $this->guidedImage ->where(UploadedImage::KEY_NAME, $uploadedImage->getFilename()) ->where(UploadedImage::KEY_SIZE, $uploadedImage->getSize()) @@ -105,16 +110,64 @@ public function upload(UploadedFile $imageFile): Result return $result; } - private function validate(UploadedFile $imageFile): bool + /** + * @throws UrlUploadFailed + */ + public function uploadFromUrl(string $url): Result + { + try { + $filenameForbiddenChars = ['?', '&', '%', '=']; + $tempFile = $this->fileHelper->tempName( + $this->fileHelper->getSystemTempDirectory(), + self::TEMP_FILE_PREFIX + ); + $imageContents = $this->fileHelper->getContents($url); + + $this->fileHelper->putContents($tempFile, $imageContents); + + $mimeType = $this->fileHelper->getMimeType($tempFile); + $originalName = sprintf( + '%s.%s', + str_replace($filenameForbiddenChars, '', basename($url)), + $this->fileHelper->mime2Ext($mimeType) + ); + $uploadedFile = $this->fileHelper->createUploadedFile($tempFile, $originalName, $mimeType); + $result = $this->upload($uploadedFile, true); + + $this->fileHelper->unlink($tempFile); + + return $result; + } catch (FileNotFoundException | FileException $exception) { + throw UrlUploadFailed::forUrl($url, $exception); + } + } + + private function validate(UploadedFile $imageFile, bool $isUrlUpload): bool + { + if ($isUrlUpload) { + return $this->validateFileExtension($imageFile); + } + + return $this->validatePostUpload($imageFile); + } + + private function validatePostUpload(UploadedFile $imageFile): bool { - $allowedExtensions = $this->configProvider->getAllowedExtensions(); $validator = $this->validationFactory->make( [self::KEY_FILE => $imageFile], [self::KEY_FILE => $this->configProvider->getImageRules()] ); - return !($validator->fails() - || !in_array(strtolower($imageFile->getClientOriginalExtension()), $allowedExtensions, true)); + return $this->validateFileExtension($imageFile) && !$validator->fails(); + } + + private function validateFileExtension(UploadedFile $imageFile): bool + { + return in_array( + strtolower($imageFile->getClientOriginalExtension()), + $this->configProvider->getAllowedExtensions(), + true + ); } private function getUploadDestination(): string diff --git a/tests/Unit/Service/ImageUploaderTest.php b/tests/Unit/Service/ImageUploaderTest.php index 42c2da8..fff2008 100644 --- a/tests/Unit/Service/ImageUploaderTest.php +++ b/tests/Unit/Service/ImageUploaderTest.php @@ -45,6 +45,7 @@ final class ImageUploaderTest extends TestCase private const UPLOAD_DIRECTORY = 'uploads/images'; private const UPLOAD_DISK_NAME = 'public'; private const UPLOADED_IMAGE_SIZE = [100, 200]; + private const TEMP_FILE_PREFIX = 'LGI_'; /** * @var ConfigProvider|ObjectProphecy @@ -87,17 +88,20 @@ final class ImageUploaderTest extends TestCase private MockInterface $uploadedFile; /** - * @var Filesystem|FilesystemAdapter|ObjectProphecy + * @var ObjectProphecy|FileHelper */ - private $uploadDisk; + private ObjectProphecy $fileHelper; /** - * @var FileHelper|ObjectProphecy + * @var Filesystem|FilesystemAdapter|ObjectProphecy */ - private $fileHelper; + private $uploadDisk; private ImageUploaderContract $subject; + /** + * @throws Exception + */ protected function setUp(): void { parent::setUp(); @@ -191,18 +195,25 @@ protected function setUp(): void * @covers ::getUploadDestination * @covers ::upload * @covers ::validate + * @covers ::validateFileExtension * @covers \ReliqArts\GuidedImage\Model\UploadedImage::toArray * @covers \ReliqArts\GuidedImage\Model\UploadedImage::getDestination * @covers \ReliqArts\GuidedImage\Model\UploadedImage::getFile * @covers \ReliqArts\GuidedImage\Model\UploadedImage::getFilename * @covers \ReliqArts\GuidedImage\Model\UploadedImage::getSize + * + * @throws Exception */ public function testUpload(): void { $this->guidedImage - ->create(Argument::that(function ($argument) { - return in_array($this->uploadedFile->getFilename(), $argument, true); - })) + ->create( + Argument::that( + function ($argument) { + return in_array($this->uploadedFile->getFilename(), $argument, true); + } + ) + ) ->shouldBeCalledTimes(1); $this->uploadDisk @@ -225,6 +236,8 @@ public function testUpload(): void * @covers \ReliqArts\GuidedImage\Model\UploadedImage::getFile * @covers \ReliqArts\GuidedImage\Model\UploadedImage::getFilename * @covers \ReliqArts\GuidedImage\Model\UploadedImage::getSize + * + * @throws Exception */ public function testUploadWhenFileShouldBeReused(): void { @@ -266,6 +279,8 @@ public function testUploadWhenFileShouldBeReused(): void * @covers ::__construct * @covers ::upload * @covers ::validate + * + * @throws Exception */ public function testUploadWhenValidationFails(): void { @@ -325,6 +340,8 @@ public function testUploadWhenValidationFails(): void * @covers \ReliqArts\GuidedImage\Model\UploadedImage::getFile * @covers \ReliqArts\GuidedImage\Model\UploadedImage::getFilename * @covers \ReliqArts\GuidedImage\Model\UploadedImage::getSize + * + * @throws Exception */ public function testUploadWhenFileUploadFails(): void { @@ -353,6 +370,94 @@ public function testUploadWhenFileUploadFails(): void self::assertFalse($result->isSuccess()); } + /** + * @covers ::__construct + * @covers ::uploadFromUrl + * @covers ::getUploadDestination + * @covers ::upload + * @covers ::validate + * @covers ::validateFileExtension + * @covers \ReliqArts\GuidedImage\Model\UploadedImage::toArray + * @covers \ReliqArts\GuidedImage\Model\UploadedImage::getDestination + * @covers \ReliqArts\GuidedImage\Model\UploadedImage::getFile + * @covers \ReliqArts\GuidedImage\Model\UploadedImage::getFilename + * @covers \ReliqArts\GuidedImage\Model\UploadedImage::getSize + * + * @throws Exception + */ + public function testUploadFromUrl(): void + { + $url = '//url'; + $imageContent = 'foo'; + $tempName = 'tmp.name'; + $systemTempDir = 'sys.temp'; + $mimeType = 'img/jpeg'; + + $this->fileHelper + ->getSystemTempDirectory() + ->shouldBeCalledTimes(1) + ->willReturn($systemTempDir); + $this->fileHelper + ->tempName($systemTempDir, self::TEMP_FILE_PREFIX) + ->shouldBeCalledTimes(1) + ->willReturn($tempName); + $this->fileHelper + ->getContents($url) + ->shouldBeCalledTimes(1) + ->willReturn($imageContent); + $this->fileHelper + ->putContents($tempName, $imageContent) + ->shouldBeCalledTimes(1) + ->willReturn(1234); + $this->fileHelper + ->getMimeType($tempName) + ->shouldBeCalledTimes(1) + ->willReturn($mimeType); + $this->fileHelper + ->mime2Ext($mimeType) + ->shouldBeCalledTimes(1) + ->willReturn('jpeg'); + $this->fileHelper + ->createUploadedFile($tempName, Argument::type('string'), $mimeType) + ->shouldBeCalledTimes(1) + ->willReturn($this->uploadedFile); + $this->fileHelper + ->unlink($tempName) + ->shouldBeCalledTimes(1) + ->willReturn(true); + + $this->configProvider + ->getImageRules() + ->shouldNotBeCalled(); + + $this->validationFactory + ->make(Argument::cetera()) + ->shouldNotBeCalled(); + + $this->validator + ->fails() + ->shouldNotBeCalled(); + + $this->guidedImage + ->create( + Argument::that( + function ($argument) { + return in_array($this->uploadedFile->getFilename(), $argument, true); + } + ) + ) + ->shouldBeCalledTimes(1); + + $this->uploadDisk + ->putFileAs(Argument::cetera()) + ->shouldBeCalled(); + + $result = $this->subject->uploadFromUrl($url); + + self::assertInstanceOf(Result::class, $result); + self::assertTrue($result->isSuccess()); + } + /** * @return MockInterface|UploadedFile */