From 7396466df3d05d4c5f35262520bbfebed1d49e4d Mon Sep 17 00:00:00 2001 From: Marcel Thole Date: Thu, 9 Jan 2025 10:08:54 +0100 Subject: [PATCH] Refactor RenameUpload filter to implement FlterInterface Signed-off-by: Marcel Thole --- docs/book/v3/file.md | 12 +- docs/book/v3/migration/v2-to-v3.md | 22 ++ psalm-baseline.xml | 118 +--------- src/File/RenameUpload.php | 358 +++++++++-------------------- test/File/RenameUploadMock.php | 26 +-- test/File/RenameUploadTest.php | 141 +++++++----- 6 files changed, 230 insertions(+), 447 deletions(-) diff --git a/docs/book/v3/file.md b/docs/book/v3/file.md index caad89a8..ce02472a 100644 --- a/docs/book/v3/file.md +++ b/docs/book/v3/file.md @@ -208,7 +208,7 @@ The following set of options are supported: > It is generally a better idea to supply an internal filename to avoid > security risks. -`RenameUpload` does not support an array of options like the`Rename` filter. +`RenameUpload` does not support an array of options like the `Rename` filter. When filtering HTML5 file uploads with the `multiple` attribute set, all files will be filtered with the same option settings. @@ -224,13 +224,15 @@ $files = $request->getFiles(); // i.e. $files['my-upload']['tmp_name'] === '/tmp/php5Wx0aJ' // i.e. $files['my-upload']['name'] === 'myfile.txt' -// 'target' option is assumed if param is a string -$filter = new \Laminas\Filter\File\RenameUpload('./data/uploads/'); +$filter = new \Laminas\Filter\File\RenameUpload(['target' => './data/uploads/']); echo $filter->filter($files['my-upload']); // File has been moved to './data/uploads/php5Wx0aJ' // ... or retain the uploaded file name -$filter->setUseUploadName(true); +$filter = new \Laminas\Filter\File\RenameUpload([ + 'target' => './data/uploads/', + 'use_upload_name' => true, +]); echo $filter->filter($files['my-upload']); // File has been moved to './data/uploads/myfile.txt' ``` @@ -244,7 +246,7 @@ $request = new Request(); $files = $request->getFiles(); // i.e. $files['my-upload']['tmp_name'] === '/tmp/php5Wx0aJ' -$filter = new \Laminas\Filter\File\RenameUpload('./data/uploads/newfile.txt'); +$filter = new \Laminas\Filter\File\RenameUpload(['target' => './data/uploads/newfile.txt']); echo $filter->filter($files['my-upload']); // File has been renamed to './data/uploads/newfile.txt' ``` diff --git a/docs/book/v3/migration/v2-to-v3.md b/docs/book/v3/migration/v2-to-v3.md index 64ea5528..d3749b07 100644 --- a/docs/book/v3/migration/v2-to-v3.md +++ b/docs/book/v3/migration/v2-to-v3.md @@ -247,6 +247,28 @@ So, to check if the path exists, ensure a validator (such as `Laminas\Validator\ Windows support has been dropped. Which in some cases may now need a custom filter to handle Windows specific issues. + +#### `RenameUpload` + +The following methods have been removed: + +- `setStreamFactory` +- `getStreamFactory` +- `setTarget` +- `getTarget` +- `setUploadFileFactory` +- `getUploadFileFactory` +- `setUseUploadName` +- `getUseUploadName` +- `setUseUploadExtension` +- `getUseUploadExtension` +- `setOverwrite` +- `getOverwrite` +- `setRandomize` +- `getRandomize` + +The constructor now only accepts an associative array of [documented options](../file.md#renameupload). + #### `SeparatorToCamelCase` The constructor now only accepts an associative array of [documented options](../word.md#separatortocamelcase). diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 9f9b753f..47494916 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -14,6 +14,7 @@ + @@ -80,105 +81,6 @@ - - - - - - - - - - - - - options]]> - options]]> - options]]> - options]]> - options]]> - options]]> - options]]> - null, - 'use_upload_name' => false, - 'use_upload_extension' => false, - 'overwrite' => false, - 'randomize' => false, - 'stream_factory' => null, - 'upload_file_factory' => null, - ]]]> - - - - - - - - - - - - - - alreadyFiltered[$sourceFile]]]> - alreadyFiltered[$sourceFile]]]> - alreadyFiltered[$sourceFile]]]> - - - - - - - - - ]]> - - - - alreadyFiltered[$alreadyFilteredKey]]]> - alreadyFiltered[$fileName]]]> - alreadyFiltered[$sourceFile]]]> - - - - alreadyFiltered[$sourceFile]]]> - - - - - - options['target']]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -214,20 +116,10 @@ - - - - - - sourceFile)]]> - sourceFile)]]> - sourceFile)]]> - sourceFile)]]> - - - - - + + + + diff --git a/src/File/RenameUpload.php b/src/File/RenameUpload.php index b08218c5..f40aa407 100644 --- a/src/File/RenameUpload.php +++ b/src/File/RenameUpload.php @@ -4,13 +4,14 @@ namespace Laminas\Filter\File; -use Laminas\Filter\AbstractFilter; use Laminas\Filter\Exception; +use Laminas\Filter\FilterInterface; use Laminas\Stdlib\ErrorHandler; use Psr\Http\Message\StreamFactoryInterface; use Psr\Http\Message\UploadedFileFactoryInterface; use Psr\Http\Message\UploadedFileInterface; +use function array_key_exists; use function basename; use function file_exists; use function filesize; @@ -27,207 +28,76 @@ use function unlink; use const DIRECTORY_SEPARATOR; +use const PATHINFO_DIRNAME; +use const PATHINFO_EXTENSION; +use const PATHINFO_FILENAME; use const UPLOAD_ERR_OK; /** + * @psalm-type UploadedFile = array{ + * name:string, + * tmp_name: string + * } * @psalm-type Options = array{ - * target: string|null, - * use_upload_name: bool, - * use_upload_extension: bool, - * overwrite: bool, - * randomize: bool, - * stream_factory: StreamFactoryInterface|null, - * upload_file_factory: UploadedFileFactoryInterface|null, - * ... + * target?: string, + * use_upload_name?: bool, + * use_upload_extension?: bool, + * overwrite?: bool, + * randomize?: bool, + * stream_factory?: StreamFactoryInterface, + * upload_file_factory?: UploadedFileFactoryInterface, * } - * @template TOptions of Options - * @extends AbstractFilter + * @implements FilterInterface + * @final */ -class RenameUpload extends AbstractFilter +class RenameUpload implements FilterInterface { - /** @var TOptions */ - protected $options = [ - 'target' => null, - 'use_upload_name' => false, - 'use_upload_extension' => false, - 'overwrite' => false, - 'randomize' => false, - 'stream_factory' => null, - 'upload_file_factory' => null, - ]; + private readonly string|null $target; + private readonly bool $useUploadName; + private readonly bool $useUploadExtension; + private readonly bool $overwrite; + private readonly bool $randomize; + private readonly StreamFactoryInterface|null $streamFactory; + private readonly UploadedFileFactoryInterface|null $uploadedFileFactory; /** * Store already filtered values, so we can filter multiple * times the same file without being block by move_uploaded_file * internal checks * - * @var array - */ - protected $alreadyFiltered = []; - - /** - * Constructor - * - * @param array|string $targetOrOptions The target file path or an options array - */ - public function __construct($targetOrOptions = []) - { - if (is_array($targetOrOptions)) { - $this->setOptions($targetOrOptions); - } else { - $this->setTarget($targetOrOptions); - } - } - - /** - * @param StreamFactoryInterface $factory Factory to use to produce a PSR-7 - * stream with which to seed a PSR-7 UploadedFileInterface. - * @return self - */ - public function setStreamFactory(StreamFactoryInterface $factory) - { - $this->options['stream_factory'] = $factory; - return $this; - } - - /** - * @return null|StreamFactoryInterface - */ - public function getStreamFactory() - { - return $this->options['stream_factory']; - } - - /** - * @param string $target Target file path or directory - * @return self - */ - public function setTarget($target) - { - if (! is_string($target)) { - throw new Exception\InvalidArgumentException( - 'Invalid target, must be a string' - ); - } - $this->options['target'] = $target; - return $this; - } - - /** - * @return string Target file path or directory - */ - public function getTarget() - { - return $this->options['target']; - } - - /** - * @param UploadedFileFactoryInterface $factory Factory to use to produce - * filtered PSR-7 UploadedFileInterface instances. - * @return self - */ - public function setUploadFileFactory(UploadedFileFactoryInterface $factory) - { - $this->options['upload_file_factory'] = $factory; - return $this; - } - - /** - * @return null|UploadedFileFactoryInterface - */ - public function getUploadFileFactory() - { - return $this->options['upload_file_factory']; - } - - /** - * @param bool $flag When true, this filter will use the $_FILES['name'] - * as the target filename. - * Otherwise, it uses the default 'target' rules. - * @return self - */ - public function setUseUploadName($flag = true) - { - $this->options['use_upload_name'] = (bool) $flag; - return $this; - } - - /** - * @return bool - */ - public function getUseUploadName() - { - return $this->options['use_upload_name']; - } - - /** - * @param bool $flag When true, this filter will use the original file - * extension for the target filename - * @return self - */ - public function setUseUploadExtension($flag = true) - { - $this->options['use_upload_extension'] = (bool) $flag; - return $this; - } - - /** - * @return bool - */ - public function getUseUploadExtension() - { - return $this->options['use_upload_extension']; - } - - /** - * @param bool $flag Shall existing files be overwritten? - * @return self - */ - public function setOverwrite($flag = true) - { - $this->options['overwrite'] = (bool) $flag; - return $this; - } - - /** - * @return bool - */ - public function getOverwrite() - { - return $this->options['overwrite']; - } - - /** - * @param bool $flag Shall target files have a random postfix attached? - * @return self + * @var array{ + * psr7: array, + * upload: array, + * string: array + * } */ - public function setRandomize($flag = true) - { - $this->options['randomize'] = (bool) $flag; - return $this; - } + private array $alreadyFilteredByType = [ + 'psr7' => [], + 'upload' => [], + 'string' => [], + ]; /** - * @return bool + * @param Options $options */ - public function getRandomize() + public function __construct(array $options = []) { - return $this->options['randomize']; + $this->target = $options['target'] ?? null; + $this->useUploadName = $options['use_upload_name'] ?? false; + $this->useUploadExtension = $options['use_upload_extension'] ?? false; + $this->overwrite = $options['overwrite'] ?? false; + $this->randomize = $options['randomize'] ?? false; + $this->streamFactory = $options['stream_factory'] ?? null; + $this->uploadedFileFactory = $options['upload_file_factory'] ?? null; } /** - * Defined by Laminas\Filter\Filter - * - * Renames the file $value to the new name set before - * Returns the file $value, removing all but digit characters - * - * @param string|array|UploadedFileInterface $value Full path of file to - * change; $_FILES data array; or UploadedFileInterface instance. - * @return string|array|UploadedFileInterface Returns one of the following: - * - New filename, for string $value - * - Array with tmp_name and name keys for array $value - * - UploadedFileInterface for UploadedFileInterface $value - * @throws Exception\RuntimeException + * @template T + * @param T $value + * @return (T is UploadedFileInterface + * ? UploadedFileInterface + * : (T is array ? array : (T is string ? string : mixed)) + * ) */ public function filter(mixed $value): mixed { @@ -237,8 +107,14 @@ public function filter(mixed $value): mixed } // File upload via traditional SAPI - if (is_array($value) && isset($value['tmp_name'])) { - return $this->filterSapiUploadedFile($value); + if ( + is_array($value) + && array_key_exists('tmp_name', $value) + && array_key_exists('name', $value) + ) { + /** @var UploadedFile $uploadedFile */ + $uploadedFile = $value; + return $this->filterSapiUploadedFile($uploadedFile); } // String filename @@ -246,17 +122,27 @@ public function filter(mixed $value): mixed return $this->filterStringFilename($value); } - // Unrecognized; return verbatim return $value; } /** - * @param string $sourceFile Source file path - * @param string $targetFile Target file path + * @psalm-suppress MixedReturnStatement + * @template T + * @param T $value + * @return (T is UploadedFileInterface + * ? UploadedFileInterface + * : (T is array ? array : (T is string ? string : mixed)) + * ) + */ + public function __invoke(mixed $value): mixed + { + return $this->filter($value); + } + + /** * @throws Exception\RuntimeException - * @return bool */ - protected function moveUploadedFile($sourceFile, $targetFile) + protected function moveUploadedFile(string $sourceFile, string $targetFile): void { ErrorHandler::start(); $result = move_uploaded_file($sourceFile, $targetFile); @@ -268,22 +154,18 @@ protected function moveUploadedFile($sourceFile, $targetFile) $warningException ); } - - return $result; } /** - * @param string $targetFile Target file path - * @return void * @throws Exception\InvalidArgumentException */ - protected function checkFileExists($targetFile) + private function checkFileExists(string $targetFile): void { if (! file_exists($targetFile)) { return; } - if (! $this->getOverwrite()) { + if (! $this->overwrite) { throw new Exception\InvalidArgumentException( sprintf("File '%s' could not be renamed. It already exists.", $targetFile) ); @@ -292,14 +174,9 @@ protected function checkFileExists($targetFile) unlink($targetFile); } - /** - * @param string $source - * @param string|null $clientFileName - * @return string - */ - protected function getFinalTarget($source, $clientFileName) + private function getFinalTarget(string $source, string|null $clientFileName): string { - $target = $this->getTarget(); + $target = $this->target; if ($target === null || $target === '*') { $target = $source; } @@ -312,47 +189,41 @@ protected function getFinalTarget($source, $clientFileName) $targetDir .= DIRECTORY_SEPARATOR; } } else { - $info = pathinfo($target); - $targetDir = $info['dirname'] . DIRECTORY_SEPARATOR; + $targetDir = pathinfo($target, PATHINFO_DIRNAME) . DIRECTORY_SEPARATOR; } // Get the target filename - if ($this->getUseUploadName()) { + if ($this->useUploadName && $clientFileName !== null) { $targetFile = basename($clientFileName); } elseif (! is_dir($target)) { $targetFile = basename($target); - if ($this->getUseUploadExtension() && ! $this->getRandomize()) { - $targetInfo = pathinfo($targetFile); - $sourceinfo = pathinfo($clientFileName); - if (isset($sourceinfo['extension'])) { - $targetFile = $targetInfo['filename'] . '.' . $sourceinfo['extension']; + if ($this->useUploadExtension && ! $this->randomize && $clientFileName !== null) { + $targetFilename = pathinfo($targetFile, PATHINFO_FILENAME); + $sourceExtension = pathinfo($clientFileName, PATHINFO_EXTENSION); + if ($sourceExtension !== '') { + $targetFile = $targetFilename . '.' . $sourceExtension; } } } else { $targetFile = basename($source); } - if ($this->getRandomize()) { + if ($this->randomize) { $targetFile = $this->applyRandomToFilename($clientFileName, $targetFile); } return $targetDir . $targetFile; } - /** - * @param string $source - * @param string $filename - * @return string - */ - protected function applyRandomToFilename($source, $filename) + private function applyRandomToFilename(string|null $source, string $filename): string { $info = pathinfo($filename); $filename = $info['filename'] . str_replace('.', '_', uniqid('_', true)); - $sourceinfo = pathinfo($source); + $sourceinfo = $source === null ? [] : pathinfo($source); $extension = ''; - if ($this->getUseUploadExtension() === true && isset($sourceinfo['extension'])) { + if ($this->useUploadExtension && isset($sourceinfo['extension'])) { $extension .= '.' . $sourceinfo['extension']; } elseif (isset($info['extension'])) { $extension .= '.' . $info['extension']; @@ -361,14 +232,10 @@ protected function applyRandomToFilename($source, $filename) return $filename . $extension; } - /** - * @param string $fileName - * @return string - */ - private function filterStringFilename($fileName) + private function filterStringFilename(string $fileName): string { - if (isset($this->alreadyFiltered[$fileName])) { - return $this->alreadyFiltered[$fileName]; + if (isset($this->alreadyFilteredByType['string'][$fileName])) { + return $this->alreadyFilteredByType['string'][$fileName]; } $targetFile = $this->getFinalTarget($fileName, $fileName); @@ -378,21 +245,21 @@ private function filterStringFilename($fileName) $this->checkFileExists($targetFile); $this->moveUploadedFile($fileName, $targetFile); - $this->alreadyFiltered[$fileName] = $targetFile; + $this->alreadyFilteredByType['string'][$fileName] = $targetFile; - return $this->alreadyFiltered[$fileName]; + return $this->alreadyFilteredByType['string'][$fileName]; } /** - * @param array $fileData - * @return array + * @param UploadedFile $fileData + * @return UploadedFile */ - private function filterSapiUploadedFile(array $fileData) + private function filterSapiUploadedFile(array $fileData): array { $sourceFile = $fileData['tmp_name']; - if (isset($this->alreadyFiltered[$sourceFile])) { - return $this->alreadyFiltered[$sourceFile]; + if (isset($this->alreadyFilteredByType['upload'][$sourceFile])) { + return $this->alreadyFilteredByType['upload'][$sourceFile]; } $clientFilename = $fileData['name']; @@ -405,26 +272,29 @@ private function filterSapiUploadedFile(array $fileData) $this->checkFileExists($targetFile); $this->moveUploadedFile($sourceFile, $targetFile); - $this->alreadyFiltered[$sourceFile] = $fileData; - $this->alreadyFiltered[$sourceFile]['tmp_name'] = $targetFile; + $this->alreadyFilteredByType['upload'][$sourceFile] = $fileData; + $this->alreadyFilteredByType['upload'][$sourceFile]['tmp_name'] = $targetFile; - return $this->alreadyFiltered[$sourceFile]; + return $this->alreadyFilteredByType['upload'][$sourceFile]; } /** - * @return UploadedFileInterface * @throws Exception\RuntimeException If no stream factory is composed in the filter. * @throws Exception\RuntimeException If no uploaded file factory is composed in the filter. */ - private function filterPsr7UploadedFile(UploadedFileInterface $uploadedFile) + private function filterPsr7UploadedFile(UploadedFileInterface $uploadedFile): UploadedFileInterface { $alreadyFilteredKey = spl_object_hash($uploadedFile); - if (isset($this->alreadyFiltered[$alreadyFilteredKey])) { - return $this->alreadyFiltered[$alreadyFilteredKey]; + if (isset($this->alreadyFilteredByType['psr7'][$alreadyFilteredKey])) { + return $this->alreadyFilteredByType['psr7'][$alreadyFilteredKey]; + } + + $sourceFile = $uploadedFile->getStream()->getMetadata('uri'); + if (! is_string($sourceFile)) { + throw new Exception\RuntimeException('UploadedFile doesn\'t contains the uri metadata'); } - $sourceFile = $uploadedFile->getStream()->getMetadata('uri'); $clientFilename = $uploadedFile->getClientFilename(); $targetFile = $this->getFinalTarget($sourceFile, $clientFilename); @@ -435,7 +305,7 @@ private function filterPsr7UploadedFile(UploadedFileInterface $uploadedFile) $this->checkFileExists($targetFile); $uploadedFile->moveTo($targetFile); - $streamFactory = $this->getStreamFactory(); + $streamFactory = $this->streamFactory; if (! $streamFactory) { throw new Exception\RuntimeException(sprintf( 'No PSR-17 %s present; cannot filter file. Please pass the stream_factory' @@ -447,7 +317,7 @@ private function filterPsr7UploadedFile(UploadedFileInterface $uploadedFile) $stream = $streamFactory->createStreamFromFile($targetFile); - $uploadedFileFactory = $this->getUploadFileFactory(); + $uploadedFileFactory = $this->uploadedFileFactory; if (! $uploadedFileFactory) { throw new Exception\RuntimeException(sprintf( 'No PSR-17 %s present; cannot filter file. Please pass the upload_file_factory' @@ -457,7 +327,7 @@ private function filterPsr7UploadedFile(UploadedFileInterface $uploadedFile) )); } - $this->alreadyFiltered[$alreadyFilteredKey] = $uploadedFileFactory->createUploadedFile( + $this->alreadyFilteredByType['psr7'][$alreadyFilteredKey] = $uploadedFileFactory->createUploadedFile( $stream, filesize($targetFile), UPLOAD_ERR_OK, @@ -465,6 +335,6 @@ private function filterPsr7UploadedFile(UploadedFileInterface $uploadedFile) $uploadedFile->getClientMediaType() ); - return $this->alreadyFiltered[$alreadyFilteredKey]; + return $this->alreadyFilteredByType['psr7'][$alreadyFilteredKey]; } } diff --git a/test/File/RenameUploadMock.php b/test/File/RenameUploadMock.php index 00cc9c8c..d35fc3c0 100644 --- a/test/File/RenameUploadMock.php +++ b/test/File/RenameUploadMock.php @@ -5,36 +5,16 @@ namespace LaminasTest\Filter\File; use Laminas\Filter\File\RenameUpload; -use Psr\Http\Message\StreamFactoryInterface; -use Psr\Http\Message\UploadedFileFactoryInterface; use function rename; /** - * @see StreamFactoryInterface - * @see UploadedFileFactoryInterface - * - * @psalm-type Options = array{ - * target: string|null, - * use_upload_name: bool, - * use_upload_extension: bool, - * overwrite: bool, - * randomize: bool, - * stream_factory: StreamFactoryInterface|null, - * upload_file_factory: UploadedFileFactoryInterface|null, - * ... - * } - * @template TOptions of Options - * @extends RenameUpload + * @psalm-suppress InvalidExtendClass */ class RenameUploadMock extends RenameUpload { - /** - * @param string $sourceFile Source file path - * @param string $targetFile Target file path - */ - protected function moveUploadedFile($sourceFile, $targetFile): bool + protected function moveUploadedFile(string $sourceFile, string $targetFile): void { - return rename($sourceFile, $targetFile); + rename($sourceFile, $targetFile); } } diff --git a/test/File/RenameUploadTest.php b/test/File/RenameUploadTest.php index 339eb65f..e9831ea9 100644 --- a/test/File/RenameUploadTest.php +++ b/test/File/RenameUploadTest.php @@ -109,46 +109,17 @@ private function removeDir(string $dir): void */ public function testThrowsExceptionWithNonUploadedFile(): void { - $filter = new FileRenameUpload($this->targetFile); - self::assertSame($this->targetFile, $filter->getTarget()); + $filter = new FileRenameUpload(['target' => $this->targetFile]); self::assertSame('falsefile', $filter('falsefile')); + $this->expectException(Exception\RuntimeException::class); $this->expectExceptionMessage('could not be renamed'); self::assertSame($this->targetFile, $filter($this->sourceFile)); } - public function testOptions(): void - { - $filter = new FileRenameUpload($this->targetFile); - self::assertSame($this->targetFile, $filter->getTarget()); - self::assertFalse($filter->getUseUploadName()); - self::assertFalse($filter->getOverwrite()); - self::assertFalse($filter->getRandomize()); - - $filter = new FileRenameUpload([ - 'target' => $this->sourceFile, - 'use_upload_name' => true, - 'overwrite' => true, - 'randomize' => true, - ]); - self::assertSame($this->sourceFile, $filter->getTarget()); - self::assertTrue($filter->getUseUploadName()); - self::assertTrue($filter->getOverwrite()); - self::assertTrue($filter->getRandomize()); - } - - public function testStringConstructorParam(): void - { - $filter = new RenameUploadMock($this->targetFile); - self::assertSame($this->targetFile, $filter->getTarget()); - self::assertSame($this->targetFile, $filter($this->sourceFile)); - self::assertSame('falsefile', $filter('falsefile')); - } - public function testStringConstructorWithFilesArray(): void { - $filter = new RenameUploadMock($this->targetFile); - self::assertSame($this->targetFile, $filter->getTarget()); + $filter = new RenameUploadMock(['target' => $this->targetFile]); self::assertSame( [ 'tmp_name' => $this->targetFile, @@ -162,9 +133,6 @@ public function testStringConstructorWithFilesArray(): void self::assertSame('falsefile', $filter('falsefile')); } - /** - * @requires PHP 7 - */ public function testStringConstructorWithPsrFile(): void { $originalStream = $this->createMock(StreamInterface::class); @@ -216,11 +184,11 @@ public function testStringConstructorWithPsrFile(): void ) ->willReturn($renamedFile); - $filter = new RenameUploadMock($this->targetFile); - self::assertSame($this->targetFile, $filter->getTarget()); - - $filter->setStreamFactory($streamFactory); - $filter->setUploadFileFactory($fileFactory); + $filter = new RenameUploadMock([ + 'target' => $this->targetFile, + 'stream_factory' => $streamFactory, + 'upload_file_factory' => $fileFactory, + ]); $moved = $filter($originalFile); @@ -231,32 +199,90 @@ public function testStringConstructorWithPsrFile(): void self::assertSame($moved, $secondResult); } + public function testWithPsrFileWillFailWithMissingUri(): void + { + $originalFile = $this->createMock(UploadedFileInterface::class); + $filter = new RenameUploadMock(); + self::expectException(Exception\RuntimeException::class); + self::expectExceptionMessage('UploadedFile doesn\'t contains the uri metadata'); + $filter($originalFile); + } + + public function testWithPsrFileWillFailWithMissingStreamFactory(): void + { + $originalStream = $this->createMock(StreamInterface::class); + $originalStream->expects(self::once()) + ->method('getMetadata') + ->with('uri') + ->willReturn($this->sourceFile); + + $originalFile = $this->createMock(UploadedFileInterface::class); + $originalFile->expects(self::once()) + ->method('getStream') + ->willReturn($originalStream); + + $originalFile->expects(self::atLeast(1)) + ->method('getClientFilename') + ->willReturn($this->targetFile); + + $filter = new RenameUploadMock([ + 'target' => $this->targetFile, + ]); + self::expectException(Exception\RuntimeException::class); + self::expectExceptionMessage('pass the stream_factory option'); + $filter($originalFile); + } + + public function testWithPsrFileWillFailWithMissingUploadedFileFactory(): void + { + $originalStream = $this->createMock(StreamInterface::class); + $originalStream->expects(self::once()) + ->method('getMetadata') + ->with('uri') + ->willReturn($this->sourceFile); + + $originalFile = $this->createMock(UploadedFileInterface::class); + $originalFile->expects(self::once()) + ->method('getStream') + ->willReturn($originalStream); + + $originalFile->expects(self::atLeast(1)) + ->method('getClientFilename') + ->willReturn($this->targetFile); + + $streamFactory = $this->createMock(StreamFactoryInterface::class); + $streamFactory->expects(self::once()) + ->method('createStreamFromFile') + ->with($this->targetFile) + ->willReturn($this->createMock(StreamInterface::class)); + + $filter = new RenameUploadMock([ + 'target' => $this->targetFile, + 'stream_factory' => $streamFactory, + ]); + self::expectException(Exception\RuntimeException::class); + self::expectExceptionMessage('pass the upload_file_factory option'); + $filter($originalFile); + } + public function testArrayConstructorParam(): void { $filter = new RenameUploadMock([ 'target' => $this->targetFile, ]); - self::assertSame($this->targetFile, $filter->getTarget()); self::assertSame($this->targetFile, $filter($this->sourceFile)); + self::assertSame($this->targetFile, $filter->filter($this->sourceFile)); + self::assertSame($this->targetFile, $filter->__invoke($this->sourceFile)); self::assertSame('falsefile', $filter('falsefile')); } public function testConstructTruncatedTarget(): void { - $filter = new FileRenameUpload('*'); - self::assertSame('*', $filter->getTarget()); + $filter = new RenameUploadMock(['target' => '*']); self::assertSame($this->sourceFile, $filter($this->sourceFile)); self::assertSame('falsefile', $filter('falsefile')); } - public function testTargetDirectory(): void - { - $filter = new RenameUploadMock($this->targetPath); - self::assertSame($this->targetPath, $filter->getTarget()); - self::assertSame($this->targetPathFile, $filter($this->sourceFile)); - self::assertSame('falsefile', $filter('falsefile')); - } - public function testOverwriteWithExistingFile(): void { $filter = new RenameUploadMock([ @@ -266,7 +292,6 @@ public function testOverwriteWithExistingFile(): void copy($this->sourceFile, $this->targetFile); - self::assertSame($this->targetFile, $filter->getTarget()); self::assertSame($this->targetFile, $filter($this->sourceFile)); } @@ -279,8 +304,6 @@ public function testCannotOverwriteExistingFile(): void copy($this->sourceFile, $this->targetFile); - self::assertSame($this->targetFile, $filter->getTarget()); - self::assertFalse($filter->getOverwrite()); $this->expectException(Exception\InvalidArgumentException::class); $this->expectExceptionMessage('already exists'); self::assertSame($this->targetFile, $filter($this->sourceFile)); @@ -350,13 +373,6 @@ public function testGetRandomizedFileWithoutExtension(): void ); } - public function testInvalidConstruction(): void - { - $this->expectException(Exception\InvalidArgumentException::class); - $this->expectExceptionMessage('Invalid target'); - new FileRenameUpload(1234); - } - public function testCanFilterMultipleTimesWithSameResult(): void { $filter = new RenameUploadMock([ @@ -379,6 +395,7 @@ public static function returnUnfilteredDataProvider(): array return [ [null], [new stdClass()], + [false], [ [ '/some-file', @@ -404,7 +421,7 @@ public function testReturnUnfiltered(mixed $input): void */ public function testFilterDoesNotAlterUnknownFileDataAndCachesResultsOfFilteringSAPIUploads(): void { - $filter = new RenameUploadMock($this->targetPath); + $filter = new RenameUploadMock(['target' => $this->targetPath]); // Emulate the output of \Laminas\Http\Request::getFiles()->toArray() $sapiSource = [