├── .gitignore ├── .php_cs ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── phpunit.xml ├── src └── FileUpload │ ├── File.php │ ├── FileNameGenerator │ ├── Custom.php │ ├── FileNameGenerator.php │ ├── MD5.php │ ├── Random.php │ ├── Simple.php │ └── Slug.php │ ├── FileSystem │ ├── FileSystem.php │ ├── Mock.php │ └── Simple.php │ ├── FileUpload.php │ ├── FileUploadFactory.php │ ├── PathResolver │ ├── PathResolver.php │ └── Simple.php │ ├── Util.php │ └── Validator │ ├── DimensionValidator.php │ ├── MimeTypeValidator.php │ ├── Simple.php │ ├── SizeValidator.php │ └── Validator.php ├── tests ├── FileUpload │ ├── FileNameGenerator │ │ ├── CustomTest.php │ │ ├── MD5Test.php │ │ ├── RandomTest.php │ │ ├── SimpleTest.php │ │ └── SlugTest.php │ ├── FileSystem │ │ └── SimpleTest.php │ ├── FileTest.php │ ├── FileUploadFactoryTest.php │ ├── FileUploadTest.php │ ├── PathResolver │ │ └── SimpleTest.php │ └── Validator │ │ ├── DimensionValidatorTest.php │ │ ├── MimeTypeValidatorTest.php │ │ ├── SimpleTest.php │ │ └── SizeValidatorTest.php ├── fixtures │ ├── blob │ ├── fake-image.jpg │ ├── real-image.jpg │ └── yadda.txt └── playground │ └── yadda.txt └── todo.md /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /.idea 3 | index.php 4 | /uploads 5 | /.phpunit.result.cache 6 | /composer.lock 7 | -------------------------------------------------------------------------------- /.php_cs: -------------------------------------------------------------------------------- 1 | in(__DIR__) 4 | ->name('*.php') 5 | ->ignoreDotFiles(true) 6 | ->ignoreVCS(true) 7 | ; 8 | 9 | return PhpCsFixer\Config::create() 10 | ->setRules(array( 11 | 'binary_operator_spaces' => ['align_double_arrow' => false], 12 | 'array_syntax' => ['syntax' => 'short'], 13 | 'linebreak_after_opening_tag' => true, 14 | 'not_operator_with_successor_space' => true, 15 | 'ordered_imports' => true, 16 | 'phpdoc_order' => true, 17 | )) 18 | ->setFinder($finder) 19 | ; 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 7.2 5 | - 7.3 6 | - 7.4 7 | - 8.0 8 | 9 | before_script: 10 | - composer selfupdate 11 | - composer install 12 | 13 | notifications: 14 | email: false 15 | 16 | script: vendor/bin/phpunit 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 - 2017 Eugen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | FileUpload 2 | ========== 3 | 4 | [![Build Status](https://travis-ci.org/Gargron/fileupload.png?branch=master)](https://travis-ci.org/Gargron/fileupload) 5 | 6 | PHP FileUpload library that supports chunked uploads. Adopted from the 7 | procedural script included with [jQuery-File-Upload][1], designed to work 8 | with that JavaScript plugin, with normal forms, and to be embeddable into 9 | any application/architecture. 10 | 11 | [1]: https://github.com/blueimp/jQuery-File-Upload 12 | 13 | ### Installing 14 | 15 | This package is available via Composer: 16 | 17 | ```json 18 | { 19 | "require": { 20 | "gargron/fileupload": "~1.4.0" 21 | } 22 | } 23 | ``` 24 | 25 | ### Requirements 26 | 27 | * Ensure that the PHP extension "php_fileinfo" is enabled; 28 | 29 | * Your php.ini must have the next directive: 30 | 31 | `file_uploads = On` 32 | 33 | ### Status 34 | 35 | The unit test suite covers simple uploads and the library "works on my machine", as it were. You are welcome to contribute. 36 | 37 | You can grep the source code for `TODO` to find things you could help finishing. 38 | 39 | ### Usage 40 | 41 | ```php 42 | // Simple validation (max file size 2MB and only two allowed mime types) 43 | $validator = new FileUpload\Validator\Simple('2M', ['image/png', 'image/jpg']); 44 | 45 | // Simple path resolver, where uploads will be put 46 | $pathresolver = new FileUpload\PathResolver\Simple('/my/uploads/dir'); 47 | 48 | // The machine's filesystem 49 | $filesystem = new FileUpload\FileSystem\Simple(); 50 | 51 | // FileUploader itself 52 | $fileupload = new FileUpload\FileUpload($_FILES['files'], $_SERVER); 53 | 54 | // Adding it all together. Note that you can use multiple validators or none at all 55 | $fileupload->setPathResolver($pathresolver); 56 | $fileupload->setFileSystem($filesystem); 57 | $fileupload->addValidator($validator); 58 | 59 | // Doing the deed 60 | list($files, $headers) = $fileupload->processAll(); 61 | 62 | // Outputting it, for example like this 63 | foreach($headers as $header => $value) { 64 | header($header . ': ' . $value); 65 | } 66 | 67 | echo json_encode(['files' => $files]); 68 | 69 | foreach($files as $file){ 70 | //Remeber to check if the upload was completed 71 | if ($file->completed) { 72 | echo $file->getRealPath(); 73 | 74 | // Call any method on an SplFileInfo instance 75 | var_dump($file->isFile()); 76 | } 77 | } 78 | ``` 79 | 80 | ### Alternative usage via factory 81 | 82 | ```php 83 | $factory = new FileUploadFactory( 84 | new PathResolver\Simple('/my/uploads/dir'), 85 | new FileSystem\Simple(), 86 | [ 87 | new FileUpload\Validator\MimeTypeValidator(['image/png', 'image/jpg']), 88 | new FileUpload\Validator\SizeValidator('3M', '1M') 89 | // etc 90 | ] 91 | ); 92 | 93 | $instance = $factory->create($_FILES['files'], $_SERVER); 94 | ``` 95 | 96 | ### Validators 97 | 98 | There are currently 4 validators shipped with `FileUpload`: 99 | 100 | - `Simple` 101 | ```php 102 | // Simple validation (max file size 2MB and only two allowed mime types) 103 | $validator = new FileUpload\Validator\Simple('2M', ['image/png', 'image/jpg']); 104 | 105 | ``` 106 | 107 | - `MimeTypeValidator` 108 | ```php 109 | $mimeTypeValidator = new FileUpload\Validator\MimeTypeValidator(['image/png', 'image/jpg']); 110 | ``` 111 | 112 | - `SizeValidator` 113 | ```php 114 | // The 1st parameter is the maximum size while the 2nd is the minimum size 115 | $sizeValidator = new FileUpload\Validator\SizeValidator('3M', '1M'); 116 | ``` 117 | 118 | - `DimensionValidator` 119 | ```php 120 | $config = [ 121 | 'width' => 400, 122 | 'height' => 500 123 | ]; 124 | // Can also contain 'min_width', 'max_width', 'min_height' and 'max_height' 125 | 126 | $dimensionValidator = new FileUpload\Validator\DimensionValidator($config); 127 | ``` 128 | 129 | > Remember to register new validator(s) by `$fileuploadInstance->addValidator($validator);` 130 | 131 | If you want you can use the common human readable format for filesizes like '1M', '1G', just pass the string as the first argument. 132 | 133 | ``` 134 | $validator = new FileUpload\Validator\Simple('10M', ['image/png', 'image/jpg']); 135 | ``` 136 | 137 | Here is a listing of the possible values (B => B; KB => K; MB => M; GB => G). These values are binary convention so basing on 1024. 138 | 139 | ### FileNameGenerator 140 | 141 | With the `FileNameGenerator` you have the possibility to change the filename the uploaded files will be saved as. 142 | 143 | ```php 144 | $fileupload = new FileUpload\FileUpload($_FILES['files'], $_SERVER); 145 | $filenamegenerator = new FileUpload\FileNameGenerator\Simple(); 146 | $fileupload->setFileNameGenerator($filenamegenerator); 147 | ``` 148 | 149 | - `Custom` 150 | ```php 151 | $customGenerator = new FileUpload\FileNameGenerator\Custom($provider); 152 | //$provider can be a string (in which case it is returned as is) 153 | //It can also be a callable or a closure which receives arguments in the other of $source_name, $type, $tmp_name, $index, $content_range, FileUpload $upload 154 | ``` 155 | 156 | - `MD5` 157 | ```php 158 | $md5Generator = new FileUpload\FileNameGenerator\MD5($allowOverride); 159 | //$allowOverride should be a boolean. A true value would overwrite the file if it exists while a false value would not allow the file to be uploaded since it already exists. 160 | ``` 161 | 162 | - `Random` 163 | ```php 164 | $randomGenerator = new FileUpload\FileNameGenerator\Random($length); 165 | //Where $length is the maximum length of the generator random name 166 | 167 | ``` 168 | 169 | - `Simple` 170 | ```php 171 | $simpleGenerator = new FileUpload\FileNameGenerator\Simple(); 172 | //Saves a file by it's original name 173 | 174 | ``` 175 | 176 | - `Slug` 177 | ```php 178 | $slugGenerator = new FileUpload\FileNameGenerator\Slug(); 179 | //This generator slugifies the name of the uploaded file(s) 180 | ``` 181 | > Remember to register new validator(s) by `$fileuploadInstance->setFileNameGenerator($generator);` 182 | 183 | > Every call to `setFileNameGenerator` overrides the currently set `$generator` 184 | 185 | ### Callbacks 186 | 187 | Currently implemented events: 188 | 189 | - `completed` 190 | ```php 191 | $fileupload->addCallback('completed', function(FileUpload\File $file) { 192 | // Whoosh! 193 | }); 194 | ``` 195 | 196 | - `beforeValidation` 197 | ```php 198 | $fileUploader->addCallback('beforeValidation', function (FileUpload\File $file) { 199 | // About to validate the upload; 200 | }); 201 | ``` 202 | 203 | - `afterValidation` 204 | ```php 205 | $fileUploader->addCallback('afterValidation', function (FileUpload\File $file) { 206 | // Yay, we got only valid uploads 207 | }); 208 | ``` 209 | 210 | ### Extending 211 | 212 | The reason why the path resolver, the validators and the file system are 213 | abstracted, is so you can write your own, fitting your own needs (and also, 214 | for unit testing). The library is shipped with a bunch of "simple" 215 | implementations which fit the basic needs. You could write a file system 216 | implementation that works with Amazon S3, for example. 217 | 218 | ### License 219 | 220 | Licensed under the MIT license, see `LICENSE` file. 221 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gargron/fileupload", 3 | "description": "File uploading library capable of handling large/chunked/multiple file uploads", 4 | "license": "MIT", 5 | "autoload": { 6 | "psr-4": { 7 | "FileUpload\\": "src/FileUpload" 8 | } 9 | }, 10 | "require": { 11 | "php": "~7.2|~8.0" 12 | }, 13 | "require-dev": { 14 | "phpunit/phpunit": "^8.0" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ./tests 7 | 8 | 9 | 10 | 11 | 12 | 13 | ./src/FileUpload 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/FileUpload/File.php: -------------------------------------------------------------------------------- 1 | setMimeType($fileName); 39 | $this->clientFileName = $clientFileName; 40 | parent::__construct($fileName); 41 | } 42 | 43 | protected function setMimeType($fileName) 44 | { 45 | if (file_exists($fileName)) { 46 | $this->mimeType = finfo_file(finfo_open(FILEINFO_MIME_TYPE), $fileName); 47 | } 48 | } 49 | 50 | /** 51 | * Returns the "original" name of the file 52 | * @return string 53 | */ 54 | public function getClientFileName() 55 | { 56 | return $this->clientFileName; 57 | } 58 | 59 | public function getMimeType() 60 | { 61 | if ($this->getType() !== 'file') { 62 | throw new \Exception('You cannot get the mimetype for a ' . $this->getType()); 63 | } 64 | 65 | return $this->mimeType; 66 | } 67 | 68 | /** 69 | * Does this file have an image mime type? 70 | * @return boolean 71 | */ 72 | public function isImage() 73 | { 74 | return in_array( 75 | $this->mimeType, 76 | ['image/gif', 'image/jpeg', 'image/pjpeg', 'image/png'] 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/FileUpload/FileNameGenerator/Custom.php: -------------------------------------------------------------------------------- 1 | generator = $nameGenerator; 20 | } 21 | 22 | public function getFileName($source_name, $type, $tmp_name, $index, $content_range, FileUpload $upload) 23 | { 24 | 25 | if (is_string($this->generator) && ! is_callable($this->generator)) { 26 | return $this->generator; 27 | } 28 | 29 | return call_user_func_array( 30 | $this->generator, 31 | func_get_args() 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/FileUpload/FileNameGenerator/FileNameGenerator.php: -------------------------------------------------------------------------------- 1 | allowDuplicate = (bool)$allowDuplicate; 24 | } 25 | 26 | /** 27 | * Generate the md5 name of a file 28 | * @param string $source_name 29 | * @param string $type 30 | * @param string $tmp_name 31 | * @param integer $index 32 | * @param string $content_range 33 | * @param FileUpload $upload 34 | * @return bool|string if $allowDuplicate is set to false and a file with the same Md5'd name exists in the upload 35 | * directory, then a bool is returned. 36 | */ 37 | public function getFileName( 38 | $source_name, 39 | $type, 40 | $tmp_name, 41 | $index, 42 | $content_range, 43 | FileUpload $upload 44 | ) { 45 | $filename = pathinfo($source_name, PATHINFO_FILENAME); 46 | $extension = pathinfo($source_name, PATHINFO_EXTENSION); 47 | 48 | $md5ConcatenatedName = md5($filename) . "." . $extension; 49 | 50 | if ($upload->getFileSystem() 51 | ->doesFileExist( 52 | $upload->getPathResolver()->getUploadPath($md5ConcatenatedName) 53 | ) && 54 | $this->allowDuplicate === false 55 | ) { 56 | $upload->getFileContainer()->error = "File already exist in the upload directory. 57 | Please upload another file or change it's name"; 58 | 59 | return false; 60 | } 61 | 62 | return $md5ConcatenatedName; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/FileUpload/FileNameGenerator/Random.php: -------------------------------------------------------------------------------- 1 | name_length = $name_length; 33 | } 34 | 35 | /** 36 | * Get file_name 37 | * @param string $source_name 38 | * @param string $type 39 | * @param string $tmp_name 40 | * @param integer $index 41 | * @param string $content_range 42 | * @param FileUpload $upload 43 | * @return string 44 | */ 45 | public function getFileName($source_name, $type, $tmp_name, $index, $content_range, FileUpload $upload) 46 | { 47 | $this->pathresolver = $upload->getPathResolver(); 48 | $this->filesystem = $upload->getFileSystem(); 49 | $extension = pathinfo($source_name, PATHINFO_EXTENSION); 50 | 51 | return ($this->getUniqueFilename($source_name, $type, $index, $content_range, $extension)); 52 | } 53 | 54 | /** 55 | * Get unique but consistent name 56 | * @param string $name 57 | * @param string $type 58 | * @param integer $index 59 | * @param array $content_range 60 | * @param string $extension 61 | * @return string 62 | */ 63 | protected function getUniqueFilename($name, $type, $index, $content_range, $extension) 64 | { 65 | $name = $this->generateRandom() . "." . $extension; 66 | while ($this->filesystem->isDir($this->pathresolver->getUploadPath($name))) { 67 | $name = $this->generateRandom() . "." . $extension; 68 | } 69 | 70 | $uploaded_bytes = Util::fixIntegerOverflow(intval($content_range[1])); 71 | 72 | while ($this->filesystem->isFile($this->pathresolver->getUploadPath($name))) { 73 | if ($uploaded_bytes == $this->filesystem->getFilesize($this->pathresolver->getUploadPath($name))) { 74 | break; 75 | } 76 | 77 | $name = $this->generateRandom() . "." . $extension; 78 | } 79 | 80 | return $name; 81 | } 82 | 83 | protected function generateRandom() 84 | { 85 | return substr( 86 | str_shuffle( 87 | "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 88 | ), 89 | 0, 90 | $this->name_length 91 | ); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/FileUpload/FileNameGenerator/Simple.php: -------------------------------------------------------------------------------- 1 | filesystem = $upload->getFileSystem(); 44 | $this->pathresolver = $upload->getPathResolver(); 45 | 46 | return ($this->getUniqueFilename($source_name, $type, $index, $content_range)); 47 | } 48 | 49 | /** 50 | * Get unique but consistent name 51 | * @param string $name 52 | * @param string $type 53 | * @param integer $index 54 | * @param array $content_range 55 | * @return string 56 | */ 57 | protected function getUniqueFilename($name, $type, $index, $content_range) 58 | { 59 | if (! is_array($content_range)) { 60 | $content_range = [0]; 61 | } 62 | 63 | while ($this->filesystem->isDir($this->pathresolver->getUploadPath($name))) { 64 | $name = $this->pathresolver->upcountName($name); 65 | } 66 | 67 | $uploaded_bytes = Util::fixIntegerOverflow(intval($content_range[1] ?? $content_range[0])); 68 | 69 | while ($this->filesystem->isFile($this->pathresolver->getUploadPath($name))) { 70 | if ($uploaded_bytes == $this->filesystem->getFilesize($this->pathresolver->getUploadPath($name))) { 71 | break; 72 | } 73 | 74 | $name = $this->pathresolver->upcountName($name); 75 | } 76 | 77 | return $name; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/FileUpload/FileNameGenerator/Slug.php: -------------------------------------------------------------------------------- 1 | filesystem = $upload->getFileSystem(); 44 | $this->pathresolver = $upload->getPathResolver(); 45 | 46 | $source_name = $this->getSluggedFileName($source_name); 47 | $uniqueFileName = $this->getUniqueFilename($source_name, $type, $index, $content_range); 48 | 49 | return $this->getSluggedFileName($uniqueFileName); 50 | } 51 | 52 | /** 53 | * Get unique but consistent name 54 | * @param string $name 55 | * @param string $type 56 | * @param integer $index 57 | * @param array $content_range 58 | * @return string 59 | */ 60 | protected function getUniqueFilename($name, $type, $index, $content_range) 61 | { 62 | if (! is_array($content_range)) { 63 | $content_range = [0]; 64 | } 65 | 66 | while ($this->filesystem->isDir($this->pathresolver->getUploadPath($this->getSluggedFileName($name)))) { 67 | $name = $this->pathresolver->upcountName($name); 68 | } 69 | 70 | $uploaded_bytes = Util::fixIntegerOverflow(intval($content_range[1] ?? $content_range[0])); 71 | 72 | while ($this->filesystem->isFile($this->pathresolver->getUploadPath($this->getSluggedFileName($name)))) { 73 | if ($uploaded_bytes == $this->filesystem->getFilesize($this->pathresolver->getUploadPath($this->getSluggedFileName($name)))) { 74 | break; 75 | } 76 | 77 | $name = $this->pathresolver->upcountName($name); 78 | } 79 | 80 | return $name; 81 | } 82 | 83 | /** 84 | * @param string $name 85 | * 86 | * @return string 87 | * */ 88 | public function getSluggedFileName($name) 89 | { 90 | $fileNameExploded = explode(".", $name); 91 | $extension = array_pop($fileNameExploded); 92 | $fileNameExploded = implode(".", $fileNameExploded); 93 | 94 | return $this->slugify($fileNameExploded) . "." . $extension; 95 | } 96 | 97 | /** 98 | * @param $text 99 | * 100 | * @return mixed|string 101 | */ 102 | private function slugify($text) 103 | { 104 | // replace non letter or digits by - 105 | $text = preg_replace('~[^\\pL\d]+~u', '-', $text); 106 | // trim 107 | $text = trim($text, '-'); 108 | // transliterate 109 | $text = iconv('utf-8', 'us-ascii//TRANSLIT', $text); 110 | // lowercase 111 | $text = strtolower($text); 112 | // remove unwanted characters 113 | $text = preg_replace('~[^-\w]+~', '', $text); 114 | 115 | if (empty($text)) { 116 | return 'n-a'; 117 | } 118 | 119 | return $text; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/FileUpload/FileSystem/FileSystem.php: -------------------------------------------------------------------------------- 1 | 'The uploaded file exceeds the upload_max_filesize directive in php.ini', 75 | UPLOAD_ERR_FORM_SIZE => 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form', 76 | UPLOAD_ERR_PARTIAL => 'The uploaded file was only partially uploaded', 77 | UPLOAD_ERR_NO_FILE => 'No file was uploaded', 78 | UPLOAD_ERR_NO_TMP_DIR => 'Missing a temporary folder', 79 | UPLOAD_ERR_CANT_WRITE => 'Failed to write file to disk', 80 | UPLOAD_ERR_EXTENSION => 'A PHP extension stopped the file upload', 81 | 82 | // Our own 83 | self::UPLOAD_ERR_PHP_SIZE => 'The upload file exceeds the post_max_size or the upload_max_filesize directives in php.ini', 84 | ]; 85 | 86 | /** 87 | * Construct this mother 88 | * @param array $upload 89 | * @param array $server 90 | * @param FileNameGenerator $generator 91 | */ 92 | public function __construct($upload, $server, FileNameGenerator $generator = null) 93 | { 94 | $this->upload = isset($upload) ? $upload : null; 95 | $this->server = $server; 96 | $this->fileNameGenerator = $generator ?: new Simple(); 97 | $this->prepareMessages(); 98 | } 99 | 100 | /** 101 | * Converts $messages array into a hash with strings as keys 102 | * This allows us to work with the keys and values as if it was a hash 103 | * Which it really should be but, well, arrays in PHP, am I right? 104 | */ 105 | private function prepareMessages() 106 | { 107 | $prepared = []; 108 | 109 | foreach ($this->messages as $key => $msg) { 110 | $prepared[(string)$key] = $msg; 111 | } 112 | 113 | $this->messages = $prepared; 114 | } 115 | 116 | /** 117 | * @return PathResolver 118 | */ 119 | public function getPathResolver() 120 | { 121 | return $this->pathresolver; 122 | } 123 | 124 | /** 125 | * Set path resolver 126 | * @param PathResolver $pr 127 | */ 128 | public function setPathResolver(PathResolver $pr) 129 | { 130 | $this->pathresolver = $pr; 131 | } 132 | 133 | /** 134 | * @return FileNameGenerator 135 | */ 136 | public function getFileNameGenerator() 137 | { 138 | return $this->fileNameGenerator; 139 | } 140 | 141 | /** 142 | * Set filename generator 143 | * @param FileNameGenerator $fng 144 | */ 145 | public function setFileNameGenerator(FileNameGenerator $fng) 146 | { 147 | $this->fileNameGenerator = $fng; 148 | } 149 | 150 | /** 151 | * @return FileSystem 152 | */ 153 | public function getFileSystem() 154 | { 155 | return $this->filesystem; 156 | } 157 | 158 | /** 159 | * Set file system 160 | * @param FileSystem $fs 161 | */ 162 | public function setFileSystem(FileSystem $fs) 163 | { 164 | $this->filesystem = $fs; 165 | } 166 | 167 | /** 168 | * @return LoggerInterface 169 | */ 170 | public function getLogger() 171 | { 172 | return $this->logger; 173 | } 174 | 175 | /** 176 | * Set logger, optionally 177 | * @param LoggerInterface $logger 178 | */ 179 | public function setLogger(LoggerInterface $logger) 180 | { 181 | $this->logger = $logger; 182 | } 183 | 184 | /** 185 | * Register callback for an event 186 | * @param string $event 187 | * @param \Closure $callback 188 | */ 189 | public function addCallback($event, \Closure $callback) 190 | { 191 | $this->callbacks[$event][] = $callback; 192 | } 193 | 194 | /** 195 | * Merge (overwrite) default error messages 196 | * @param array $new_messages 197 | */ 198 | public function setMessages(array $new_messages) 199 | { 200 | $this->messages = array_merge($this->messages, $new_messages); 201 | } 202 | 203 | /** 204 | * Returns an array of all uploaded files 205 | * @return array 206 | */ 207 | public function getFiles() 208 | { 209 | return ($this->files); 210 | } 211 | 212 | /** 213 | * Process entire submitted request 214 | * @return array Files and response headers 215 | */ 216 | public function processAll() 217 | { 218 | $content_range = $this->getContentRange(); 219 | $size = $this->getSize(); 220 | $this->files = []; 221 | $upload = $this->upload; 222 | 223 | if ($this->logger) { 224 | $this->logger->debug('Processing uploads', [ 225 | 'Content-range' => $content_range, 226 | 'Size' => $size, 227 | 'Upload array' => $upload, 228 | 'Server array' => $this->server, 229 | ]); 230 | } 231 | 232 | if ($upload && is_array($upload['tmp_name'])) { 233 | foreach ($upload['tmp_name'] as $index => $tmp_name) { 234 | if (empty($tmp_name)) { 235 | // Discard empty uploads 236 | continue; 237 | } 238 | 239 | $this->files[] = $this->process( 240 | $tmp_name, 241 | $upload['name'][$index], 242 | $size ? $size : $upload['size'][$index], 243 | $upload['type'][$index], 244 | $upload['error'][$index], 245 | $index, 246 | $content_range 247 | ); 248 | } 249 | } else { 250 | if ($upload && ! empty($upload['tmp_name'])) { 251 | $this->files[] = $this->process( 252 | $upload['tmp_name'], 253 | $upload['name'], 254 | $size ? $size : (isset($upload['size']) ? $upload['size'] : $this->getContentLength()), 255 | isset($upload['type']) ? $upload['type'] : $this->getContentType(), 256 | $upload['error'], 257 | 0, 258 | $content_range 259 | ); 260 | } else { 261 | if ($upload && $upload['error'] != 0) { 262 | // $this->fileContainer is empty at this point 263 | // $upload['tmp_name'] is also empty 264 | // So we create a File instance from $upload['name'] 265 | $file = new File($upload['name'], basename($upload['name'])); 266 | $file->error = $this->getMessage($upload['error']); 267 | $file->errorCode = $upload['error']; 268 | $this->files[] = $file; 269 | } 270 | } 271 | } 272 | 273 | return [$this->files, $this->getNewHeaders($this->files, $content_range)]; 274 | } 275 | 276 | /** 277 | * Content-range header 278 | * @return array 279 | */ 280 | protected function getContentRange() 281 | { 282 | return isset($this->server['HTTP_CONTENT_RANGE']) ? 283 | preg_split('/[^0-9]+/', $this->server['HTTP_CONTENT_RANGE']) : null; 284 | } 285 | 286 | /** 287 | * Request size 288 | * @return integer 289 | */ 290 | protected function getSize() 291 | { 292 | $range = $this->getContentRange(); 293 | 294 | return $range ? $range[3] : null; 295 | } 296 | 297 | /** 298 | * Process single submitted file 299 | * @param string $tmp_name 300 | * @param string $name 301 | * @param integer $size 302 | * @param string $type 303 | * @param integer $error 304 | * @param integer $index 305 | * @param array $content_range 306 | * @return File 307 | */ 308 | protected function process($tmp_name, $name, $size, $type, $error, $index = 0, $content_range = null) 309 | { 310 | $this->fileContainer = $file = new File($tmp_name, $name); 311 | $file->name = $this->getFilename($name, $type, $index, $content_range, $tmp_name); 312 | $file->size = $this->fixIntegerOverflow(intval($size)); 313 | $completed = false; 314 | 315 | if ($file->name) { //since the md5 filename generator would return false if it's allowDuplicate property is set to false and the file already exists. 316 | 317 | if ($this->validate($tmp_name, $file, $error, $index)) { 318 | // Now that we passed the validation, we can work with the file 319 | $upload_path = $this->pathresolver->getUploadPath(); 320 | $file_path = $this->pathresolver->getUploadPath($file->name); 321 | $append_file = $content_range && $this->filesystem->isFile($file_path) && $file->size > $this->getFilesize($file_path); 322 | 323 | if ($tmp_name && $this->filesystem->isUploadedFile($tmp_name)) { 324 | // This is a normal upload from temporary file 325 | if ($append_file) { 326 | // Adding to existing file (chunked uploads) 327 | $this->filesystem->writeToFile($file_path, $this->filesystem->getFileStream($tmp_name), true); 328 | } else { 329 | // Upload full file 330 | $this->filesystem->moveUploadedFile($tmp_name, $file_path); 331 | } 332 | } else { 333 | // This is a PUT-type upload 334 | $this->filesystem->writeToFile($file_path, $this->filesystem->getInputStream(), $append_file); 335 | } 336 | 337 | $file_size = $this->getFilesize($file_path, $append_file); 338 | 339 | if ($this->logger) { 340 | $this->logger->debug('Processing ' . $file->name, [ 341 | 'File path' => $file_path, 342 | 'File object' => $file, 343 | 'Append to file?' => $append_file, 344 | 'File exists?' => $this->filesystem->isFile($file_path), 345 | 'File size' => $file_size, 346 | ]); 347 | } 348 | 349 | if ($file->size == $file_size) { 350 | // Yay, upload is complete! 351 | $completed = true; 352 | } else { 353 | if (! $content_range) { 354 | // The file is incomplete and it's not a chunked upload, abort 355 | $this->filesystem->unlink($file_path); 356 | $file->error = 'abort'; 357 | } 358 | } 359 | 360 | $file = new File($file_path, $name); 361 | $file->completed = $completed; 362 | $file->size = $file_size; 363 | 364 | if ($completed) { 365 | $this->processCallbacksFor('completed', $file); 366 | } 367 | } 368 | } 369 | 370 | return $file; 371 | } 372 | 373 | /** 374 | * Get filename for submitted filename 375 | * @param string $name 376 | * @param string $type 377 | * @param integer $index 378 | * @param array $content_range 379 | * @param string $tmp_name 380 | * @return string 381 | */ 382 | protected function getFilename($name, $type, $index, $content_range, $tmp_name) 383 | { 384 | $name = $this->trimFilename($name, $type, $index, $content_range); 385 | 386 | return ($this->fileNameGenerator->getFileName($name, $type, $tmp_name, $index, $content_range, $this)); 387 | } 388 | 389 | /** 390 | * Remove harmful characters from filename 391 | * @param string $name 392 | * @param string $type 393 | * @param integer $index 394 | * @param array $content_range 395 | * @return string 396 | */ 397 | protected function trimFilename($name, $type, $index, $content_range) 398 | { 399 | $name = trim(basename(stripslashes($name)), ".\x00..\x20"); 400 | 401 | if (! $name) { 402 | $name = str_replace('.', '-', microtime(true)); 403 | } 404 | 405 | return $name; 406 | } 407 | 408 | /** 409 | * Ensure correct value for big integers 410 | * @param integer $int 411 | * @return float 412 | */ 413 | protected function fixIntegerOverflow($int) 414 | { 415 | if ($int < 0) { 416 | $int += 2.0 * (PHP_INT_MAX + 1); 417 | } 418 | 419 | return $int; 420 | } 421 | 422 | /** 423 | * Validate upload using some default rules, and custom 424 | * validators added via addValidator. Default rules: 425 | * 426 | * - No PHP errors from $_FILES 427 | * - File size permitted by PHP config 428 | * 429 | * @param string $tmp_name 430 | * @param File $file 431 | * @param integer $error 432 | * @param integer $index 433 | * @return boolean 434 | */ 435 | protected function validate($tmp_name, File $file, $error, $index) 436 | { 437 | $this->processCallbacksFor('beforeValidation', $file); 438 | 439 | if ($error !== 0) { 440 | // PHP error 441 | $file->error = $this->getMessage($error); 442 | $file->errorCode = $error; 443 | 444 | return false; 445 | } 446 | 447 | $content_length = $this->getContentLength(); 448 | $post_max_size = $this->getConfigBytes(ini_get('post_max_size')); 449 | $upload_max_size = $this->getConfigBytes(ini_get('upload_max_filesize')); 450 | 451 | if (($post_max_size && ($content_length > $post_max_size)) || ($upload_max_size && ($content_length > $upload_max_size))) { 452 | // Uploaded file exceeds maximum filesize PHP accepts in the configs 453 | $file->error = $this->getMessage(self::UPLOAD_ERR_PHP_SIZE); 454 | $file->errorCode = self::UPLOAD_ERR_PHP_SIZE; 455 | 456 | return false; 457 | } 458 | 459 | if ($tmp_name && $this->filesystem->isUploadedFile($tmp_name)) { 460 | $current_size = $this->getFilesize($tmp_name); 461 | } else { 462 | $current_size = $content_length; 463 | } 464 | 465 | // Now that we passed basic, implementation-agnostic tests, 466 | // let's do custom validators 467 | foreach ($this->validators as $validator) { 468 | if (! $validator->validate($file, $current_size)) { 469 | return false; 470 | } 471 | } 472 | 473 | $this->processCallbacksFor('afterValidation', $file); 474 | 475 | return true; 476 | } 477 | 478 | /** 479 | * Process callbacks for a given event 480 | * @param string $eventName 481 | * @param File $file 482 | * @return void 483 | */ 484 | protected function processCallbacksFor($eventName, File $file) 485 | { 486 | if (! array_key_exists($eventName, $this->callbacks) || empty($this->callbacks[$eventName])) { 487 | return; 488 | } 489 | 490 | foreach ($this->callbacks[$eventName] as $callback) { 491 | $callback($file); 492 | } 493 | } 494 | 495 | /** 496 | * Get an error message 497 | * @param int $code 498 | * @return string 499 | */ 500 | public function getMessage($code) 501 | { 502 | return $this->messages[((string)$code)]; 503 | } 504 | 505 | /** 506 | * Content-length header 507 | * @return integer 508 | */ 509 | protected function getContentLength() 510 | { 511 | return isset($this->server['CONTENT_LENGTH']) ? $this->server['CONTENT_LENGTH'] : null; 512 | } 513 | 514 | /** 515 | * Convert size format from PHP config into bytes 516 | * @param string $val 517 | * @return float 518 | */ 519 | protected function getConfigBytes($val) 520 | { 521 | $val = trim($val); 522 | $bytes = (int)(substr($val, 0, -1)); 523 | $last = strtolower($val[strlen($val) - 1]); 524 | 525 | switch ($last) { 526 | case 'g': 527 | $bytes *= 1024; 528 | case 'm': 529 | $bytes *= 1024; 530 | case 'k': 531 | $bytes *= 1024; 532 | } 533 | 534 | return $this->fixIntegerOverflow($bytes); 535 | } 536 | 537 | /** 538 | * Get size of file 539 | * @param string $path 540 | * @param boolean $clear_cache 541 | * @return float 542 | */ 543 | protected function getFilesize($path, $clear_cache = false) 544 | { 545 | if ($clear_cache) { 546 | $this->filesystem->clearStatCache($path); 547 | } 548 | 549 | return $this->fixIntegerOverflow($this->filesystem->getFilesize($path)); 550 | } 551 | 552 | /** 553 | * Content-type header 554 | * @return string 555 | */ 556 | protected function getContentType() 557 | { 558 | return isset($this->server['CONTENT_TYPE']) ? $this->server['CONTENT_TYPE'] : null; 559 | } 560 | 561 | /** 562 | * @return File 563 | */ 564 | public function getFileContainer() 565 | { 566 | return $this->fileContainer; 567 | } 568 | 569 | /** 570 | * Generate headers for response 571 | * @param array $files 572 | * @param array $content_range 573 | * @return array 574 | */ 575 | protected function getNewHeaders(array $files, $content_range) 576 | { 577 | $headers = [ 578 | 'pragma' => 'no-cache', 579 | 'cache-control' => 'no-store, no-cache, must-revalidate', 580 | 'content-disposition' => 'inline; filename="files.json"', 581 | 'x-content-type-options' => 'nosniff' 582 | ]; 583 | 584 | if ($content_range && is_object($files[0]) && isset($files[0]->size) && $files[0]->size) { 585 | $headers['range'] = '0-' . ($this->fixIntegerOverflow($files[0]->size) - 1); 586 | } 587 | 588 | return $headers; 589 | } 590 | 591 | /** 592 | * Add another validator 593 | * @param Validator $v 594 | */ 595 | public function addValidator(Validator $v) 596 | { 597 | $this->validators[] = $v; 598 | } 599 | } 600 | -------------------------------------------------------------------------------- /src/FileUpload/FileUploadFactory.php: -------------------------------------------------------------------------------- 1 | pathresolver = $pathresolver; 50 | $this->filesystem = $filesystem; 51 | $this->validators = $validators; 52 | $this->fileNameGenerator = $fileNameGenerator; 53 | } 54 | 55 | /** 56 | * Create new instance of FileUpload with the preset modules 57 | * @param array $upload 58 | * @param array $server 59 | * @return FileUpload 60 | */ 61 | public function create($upload, $server) 62 | { 63 | $fileupload = new FileUpload($upload, $server); 64 | $fileupload->setPathResolver($this->pathresolver); 65 | $fileupload->setFileSystem($this->filesystem); 66 | if (null !== $this->fileNameGenerator) { 67 | $fileupload->setFileNameGenerator($this->fileNameGenerator); 68 | } 69 | 70 | foreach ($this->validators as $validator) { 71 | $fileupload->addValidator($validator); 72 | } 73 | 74 | return $fileupload; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/FileUpload/PathResolver/PathResolver.php: -------------------------------------------------------------------------------- 1 | main_path = $main_path; 20 | } 21 | 22 | /** 23 | * @see PathResolver 24 | */ 25 | public function getUploadPath($name = null) 26 | { 27 | return $this->main_path . '/' . $name; 28 | } 29 | 30 | /** 31 | * @see PathResolver 32 | */ 33 | public function upcountName($name) 34 | { 35 | return preg_replace_callback('/(?:(?: \(([\d]+)\))?(\.[^.]+))?$/', function ($matches) { 36 | $index = isset($matches[1]) ? intval($matches[1]) + 1 : 1; 37 | $ext = isset($matches[2]) ? $matches[2] : ''; 38 | 39 | return ' (' . $index . ')' . $ext; 40 | }, $name, 1); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/FileUpload/Util.php: -------------------------------------------------------------------------------- 1 | 1, 25 | 'k' => 1024, 26 | 'm' => 1048576, 27 | 'g' => 1073741824 28 | ]; 29 | $unit = strtolower(substr($input, -1)); 30 | if (isset($units[$unit])) { 31 | return ($number * $units[$unit]); 32 | } else { 33 | return (null); 34 | } 35 | } 36 | 37 | /** 38 | * Ensure correct value for big integers 39 | * @param integer $int 40 | * @return float 41 | */ 42 | 43 | public static function fixIntegerOverflow($int) 44 | { 45 | 46 | if ($int < 0) { 47 | $int += 2.0 * (PHP_INT_MAX + 1); 48 | } 49 | 50 | return $int; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/FileUpload/Validator/DimensionValidator.php: -------------------------------------------------------------------------------- 1 | "Cannot validate the currently uploaded file by it's dimension as it is not an image", 24 | 25 | self::HEIGHT => "The uploaded file's height is invalid. It should have an height of {value}", 26 | 27 | self::MIN_HEIGHT => "The uploaded file's height is too small. It should have a minimum height of {value}", 28 | 29 | self::MAX_HEIGHT => "The uploaded file's height is too large. It should have a maximum height of {value}", 30 | 31 | self::WIDTH => "The uploaded file's width is invalid. It should have an height of {value}", 32 | 33 | self::MIN_WIDTH => "The uploaded file's width is too small. It should have a minimum height of {value}", 34 | 35 | self::MAX_WIDTH => "The uploaded file's width is too large. It should have a maximum height of {value}" 36 | ]; 37 | 38 | public function __construct(array $config) 39 | { 40 | $this->config = $config; 41 | } 42 | 43 | public function setErrorMessages(array $message) 44 | { 45 | foreach ($message as $key => $value) { 46 | $this->errorMessages[$key] = $value; 47 | } 48 | } 49 | 50 | public function validate(File $file, $currentSize = null) 51 | { 52 | 53 | if (! $file->isImage() || ! list($width, $height) = getimagesize($file->getRealPath())) { 54 | $file->error = $this->errorMessages[self::INVALID_UPLOADED_FILE_TYPE]; 55 | 56 | return false; 57 | } 58 | 59 | return $this->validateDimensions($file, $width, $height); 60 | } 61 | 62 | protected function validateDimensions(File $file, $width, $height) 63 | { 64 | $valid = true; 65 | 66 | //Multiple if/else here so as to allow proper message formatting. 67 | //All can be lumped up in one big if check but the error message would be poor. 68 | //Plus that would defeat the ability of users setting up custom error messages 69 | 70 | if (isset($this->config[self::WIDTH]) && $this->config[self::WIDTH] !== $width) { 71 | $file->error = $this->formatErrorMessage(self::WIDTH, $this->config[self::WIDTH]); 72 | $valid = false; 73 | } 74 | 75 | if (isset($this->config[self::MIN_WIDTH]) && $this->config[self::MIN_WIDTH] > $width) { 76 | $file->error = $this->formatErrorMessage(self::MIN_WIDTH, $this->config[self::MIN_WIDTH]); 77 | $valid = false; 78 | } 79 | 80 | if (isset($this->config[self::MAX_WIDTH]) && $this->config[self::MAX_WIDTH] < $width) { 81 | $file->error = $this->formatErrorMessage(self::MAX_WIDTH, $this->config[self::MAX_WIDTH]); 82 | $valid = false; 83 | } 84 | 85 | if (isset($this->config[self::HEIGHT]) && $this->config[self::HEIGHT] !== $height) { 86 | $file->error = $this->formatErrorMessage(self::HEIGHT, $this->config[self::HEIGHT]); 87 | $valid = false; 88 | } 89 | 90 | if (isset($this->config[self::MIN_HEIGHT]) && $this->config[self::MIN_HEIGHT] > $height) { 91 | $file->error = $this->formatErrorMessage(self::MIN_HEIGHT, $this->config[self::MIN_HEIGHT]); 92 | $valid = false; 93 | } 94 | 95 | if (isset($this->config[self::MAX_HEIGHT]) && $this->config[self::MAX_HEIGHT] < $height) { 96 | $file->error = $this->formatErrorMessage(self::MAX_HEIGHT, $this->config[self::MAX_HEIGHT]); 97 | $valid = false; 98 | } 99 | 100 | return $valid; 101 | } 102 | 103 | protected function formatErrorMessage($key, $heightValue) 104 | { 105 | return $this->errorMessages[$key] = str_replace( 106 | "{value}", 107 | $heightValue, 108 | $this->errorMessages[$key] 109 | ); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/FileUpload/Validator/MimeTypeValidator.php: -------------------------------------------------------------------------------- 1 | "The uploaded filetype (mimetype) is invalid" 30 | ]; 31 | 32 | public function __construct(array $validMimeTypes) 33 | { 34 | $this->mimeTypes = $validMimeTypes; 35 | $this->isValid = true; //Innocent (Valid file) unless proven otherwise :) 36 | } 37 | 38 | 39 | /** 40 | * {@inheritdoc} 41 | */ 42 | public function setErrorMessages(array $messages) 43 | { 44 | foreach ($messages as $key => $value) { 45 | $this->errorMessages[$key] = $value; 46 | } 47 | } 48 | 49 | /** 50 | * {@inheritdoc} 51 | */ 52 | public function validate(File $file, $currentSize = null) 53 | { 54 | if (! in_array($file->getMimeType(), $this->mimeTypes)) { 55 | $this->isValid = false; 56 | $file->error = $this->errorMessages[self::INVALID_MIMETYPE]; 57 | } 58 | 59 | return $this->isValid; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/FileUpload/Validator/Simple.php: -------------------------------------------------------------------------------- 1 | 'Filetype not allowed', 31 | self::UPLOAD_ERR_TOO_LARGE => 'Filesize too large', 32 | ]; 33 | 34 | /** 35 | * @param integer $max_size 36 | * @param array $allowed_types 37 | */ 38 | public function __construct($max_size, array $allowed_types = []) 39 | { 40 | $this->setMaxSize($max_size); 41 | $this->allowed_types = $allowed_types; 42 | } 43 | 44 | /** 45 | * Sets the max file size 46 | * @param mixed $max_size 47 | * @throws \Exception if the max_size value is invalid 48 | */ 49 | public function setMaxSize($max_size) 50 | { 51 | if (is_numeric($max_size)) { 52 | $this->max_size = $max_size; 53 | } else { 54 | $this->max_size = Util::humanReadableToBytes($max_size); 55 | } 56 | 57 | if ($this->max_size < 0 || $this->max_size == null) { 58 | throw new \Exception('invalid max_size value'); 59 | } 60 | 61 | } 62 | 63 | /** 64 | * {@inheritdoc} 65 | */ 66 | public function setErrorMessages(array $new_messages) 67 | { 68 | foreach ($new_messages as $key => $value) { 69 | $this->messages[$key] = $value; 70 | } 71 | } 72 | 73 | /** 74 | * {@inheritdoc} 75 | */ 76 | public function validate(File $file, $current_size = null) 77 | { 78 | if (! empty($this->allowed_types)) { 79 | if (! in_array($file->getMimeType(), $this->allowed_types)) { 80 | $file->error = $this->messages[self::UPLOAD_ERR_BAD_TYPE]; 81 | 82 | return false; 83 | } 84 | } 85 | 86 | if ($file->getSize() > $this->max_size || $current_size > $this->max_size) { 87 | $file->error = $this->messages[self::UPLOAD_ERR_TOO_LARGE]; 88 | 89 | return false; 90 | } 91 | 92 | return true; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/FileUpload/Validator/SizeValidator.php: -------------------------------------------------------------------------------- 1 | "The uploaded file is too large", 31 | self::FILE_SIZE_IS_TOO_SMALL => "The uploaded file is too small" 32 | ]; 33 | 34 | /** 35 | * @param int $maxSize 36 | * @param int $minSize Defaults to 0 37 | */ 38 | public function __construct($maxSize, $minSize = 0) 39 | { 40 | $this->maxSize = $this->setMaxSize($maxSize); 41 | $this->minSize = $this->setMinFile($minSize); 42 | $this->isValid = true; 43 | } 44 | 45 | /** 46 | * @param $maxSize 47 | * @throws \Exception if the max file size is null or equals zero 48 | * @return int|string 49 | */ 50 | public function setMaxSize($maxSize) 51 | { 52 | $max = 0; 53 | 54 | if (is_numeric($maxSize)) { 55 | $max = $maxSize; 56 | } else { 57 | $max = Util::humanReadableToBytes($maxSize); 58 | } 59 | 60 | if ($max < 0 || $max === null) { 61 | throw new \Exception("Invalid File Max_Size"); 62 | } 63 | 64 | return $max; 65 | } 66 | 67 | 68 | /** 69 | * @param $minSize 70 | * @throws \Exception if the file size is lesser than zero or null 71 | * @return int|string 72 | */ 73 | public function setMinFile($minSize) 74 | { 75 | $min = 0; 76 | 77 | if (is_numeric($minSize)) { 78 | $min = $minSize; 79 | } else { 80 | $min = Util::humanReadableToBytes($minSize); 81 | } 82 | 83 | if ($min < 0 || $min === null) { 84 | throw new \Exception("Invalid File Min_Size"); 85 | } 86 | 87 | return $min; 88 | } 89 | 90 | /** 91 | * {@inheritdoc} 92 | */ 93 | public function setErrorMessages(array $messages) 94 | { 95 | foreach ($messages as $key => $value) { 96 | $this->errorMessages[$key] = $value; 97 | } 98 | } 99 | 100 | /** 101 | * {@inheritdoc} 102 | */ 103 | public function validate(File $file, $currentSize = null) 104 | { 105 | if ($file->getSize() < $this->minSize) { 106 | $file->error = $this->errorMessages[self::FILE_SIZE_IS_TOO_SMALL]; 107 | $this->isValid = false; 108 | } 109 | 110 | if ($file->getSize() > $this->maxSize || $currentSize > $this->maxSize) { 111 | $file->error = $this->errorMessages[self::FILE_SIZE_IS_TOO_LARGE]; 112 | $this->isValid = false; 113 | } 114 | 115 | return $this->isValid; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/FileUpload/Validator/Validator.php: -------------------------------------------------------------------------------- 1 | 'image/jpg', 'CONTENT_LENGTH' => 30321 ]; 24 | $file = [ 25 | 'tmp_name' => $playground_path . '/real-image.jpg', 26 | 'name' => 'real-image.jpg', 27 | 'size' => 30321, 28 | 'type' => 'image/jpg', 29 | 'error' => 0 ]; 30 | 31 | $fileUpload = new FileUpload($file, $server, $generator); 32 | 33 | $fileUpload->setFileSystem(new Mock()); 34 | $fileUpload->setPathResolver(new Simple($playground_path . "/uploaded")); 35 | 36 | $this->assertEquals( 37 | $generator->getFileName($filename, "image/jpg", "asdf.jpg", 0, "100", $fileUpload), 38 | $new_filename 39 | ); 40 | } 41 | 42 | public function testClosureGenerator() 43 | { 44 | $customName = function ($source_name, $type, $tmp_name, $index, $content_range, FileUpload $upload) { 45 | return $index . $type . $source_name . $tmp_name; 46 | }; 47 | 48 | $generator = new Custom($customName); 49 | 50 | $playground_path = __DIR__ . '/../playground'; 51 | 52 | $filename = "picture.jpg"; 53 | 54 | $server = [ 'CONTENT_TYPE' => 'image/jpg', 'CONTENT_LENGTH' => 30321 ]; 55 | $file = ['tmp_name' => $playground_path . '/real-image.jpg', 56 | 'name' => 'real-image.jpg', 57 | 'size' => 30321, 58 | 'type' => 'image/jpg', 59 | 'error' => 0 60 | ]; 61 | 62 | $fileUpload = new FileUpload($file, $server, $generator); 63 | 64 | $fileUpload->setFileSystem(new Mock()); 65 | $fileUpload->setPathResolver(new Simple($playground_path . "/uploaded")); 66 | 67 | $new_filename = $customName($filename, "image/jpg", "asdf.jpg", 0, "100", $fileUpload); 68 | 69 | $this->assertEquals( 70 | $generator->getFileName($filename, "image/jpg", "asdf.jpg", 0, "100", $fileUpload), 71 | $new_filename 72 | ); 73 | } 74 | 75 | public function testCallableGenerator() 76 | { 77 | function generateName() 78 | { 79 | return func_get_arg(0); 80 | } 81 | 82 | $generator = new Custom("FileUpload\\FileNameGenerator\\generateName"); 83 | 84 | $playground_path = __DIR__ . '/../playground'; 85 | 86 | $filename = "picture.jpg"; 87 | 88 | $server = ['CONTENT_TYPE' => 'image/jpg', 'CONTENT_LENGTH' => 30321]; 89 | $file = [ 'tmp_name' => $playground_path . '/real-image.jpg', 90 | 'name' => 'real-image.jpg', 91 | 'size' => 30321, 92 | 'type' => 'image/jpg', 93 | 'error' => 0]; 94 | 95 | $fileUpload = new FileUpload($file, $server, $generator); 96 | 97 | $fileUpload->setFileSystem(new Mock()); 98 | $fileUpload->setPathResolver(new Simple($playground_path . "/uploaded")); 99 | 100 | $new_filename = generateName($filename, "image/jpg", "asdf.jpg", 0, "100", $fileUpload); 101 | 102 | $this->assertEquals( 103 | $generator->getFileName($filename, "image/jpg", "asdf.jpg", 0, "100", $fileUpload), 104 | $new_filename 105 | ); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /tests/FileUpload/FileNameGenerator/MD5Test.php: -------------------------------------------------------------------------------- 1 | 'image/jpg', 'CONTENT_LENGTH' => 30321]; 49 | $file = ['tmp_name' => $playground_path . '/real-image.jpg', 50 | 'name' => 'real-image.jpg', 51 | 'size' => 30321, 52 | 'type' => 'image/jpg', 53 | 'error' => 0 54 | ]; 55 | 56 | $fileUpload = new FileUpload($file, $server, $generator); 57 | $fileUpload->setFileSystem(new Mock()); 58 | $fileUpload->setPathResolver(new Path($playground_path . "/uploaded")); 59 | 60 | $this->assertEquals( 61 | $generator->getFileName($filename, "image/jpg", "asdf.jpg", 0, "100", $fileUpload), 62 | $new_filename 63 | ); 64 | } 65 | 66 | public function testUploadFailsBecauseFileAlreadyExistsOnTheFileSystem() 67 | { 68 | $generator = new MD5(); 69 | $playground_path = __DIR__ . '/../../playground'; 70 | 71 | $filename = "real-image.jpg"; 72 | 73 | $server = ['CONTENT_TYPE' => 'image/jpg', 'CONTENT_LENGTH' => 30321]; 74 | $file = ['tmp_name' => $playground_path . '/uploaded/real-image.jpg', 75 | 'name' => 'real-image.jpg', 76 | 'size' => 30321, 77 | 'type' => 'image/jpg', 78 | 'error' => 0 79 | ]; 80 | 81 | $fileUpload = new FileUpload($file, $server, $generator); 82 | $fileUpload->setFileSystem(new Mock()); 83 | $fileUpload->setPathResolver(new Path($playground_path)); 84 | $fileUpload->processAll(); 85 | 86 | $this->assertFalse( 87 | $generator->getFileName($filename, "image/jpg", "asdf.jpg", 0, "100", $fileUpload) 88 | ); 89 | } 90 | 91 | public function testFileIsUploadedDespiteAlreadyExistingOnTheFileSystem() 92 | { 93 | $generator = new MD5(true); //true would override files with the same name 94 | $playground_path = __DIR__ . '/../../playground'; 95 | 96 | $filename = "real-image.jpg"; 97 | $newFileName = md5("real-image") . '.jpg'; 98 | 99 | $server = ['CONTENT_TYPE' => 'image/jpg', 'CONTENT_LENGTH' => 30321]; 100 | $file = ['tmp_name' => $playground_path . '/uploaded/real-image.jpg', 101 | 'name' => 'real-image.jpg', 102 | 'size' => 30321, 103 | 'type' => 'image/jpg', 104 | 'error' => 0 105 | ]; 106 | 107 | $fileUpload = new FileUpload($file, $server, $generator); 108 | $fileUpload->setFileSystem(new Mock()); 109 | $fileUpload->setPathResolver(new Path($playground_path)); 110 | $fileUpload->processAll(); 111 | 112 | $this->assertEquals( 113 | $generator->getFileName($filename, "image/jpg", "asdf.jpg", 0, "100", $fileUpload), 114 | $newFileName 115 | ); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /tests/FileUpload/FileNameGenerator/RandomTest.php: -------------------------------------------------------------------------------- 1 | 'image/jpg', 'CONTENT_LENGTH' => 30321]; 23 | $file = ['tmp_name' => $playground_path . '/real-image.jpg', 'name' => 'real-image.jpg', 'size' => 30321, 'type' => 'image/jpg', 'error' => 0]; 24 | 25 | $fileUpload = new FileUpload($file, $server, $generator); 26 | $fileUpload->setPathResolver($resolver); 27 | $fileUpload->setFileSystem($filesystem); 28 | 29 | $new_name = $generator->getFileName($filename, "image/jpg", "asdf.jpg", 0, "100", $fileUpload); 30 | 31 | $this->assertEquals(32, strrpos($new_name, ".")); 32 | $this->assertEquals(substr($new_name, strrpos($new_name, ".")), ".jpg"); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/FileUpload/FileNameGenerator/SimpleTest.php: -------------------------------------------------------------------------------- 1 | 'image/jpg', 'CONTENT_LENGTH' => 30321]; 22 | $file = ['tmp_name' => $playground_path . '/real-image.jpg', 'name' => 'real-image.jpg', 'size' => 30321, 'type' => 'image/jpg', 'error' => 0]; 23 | 24 | $fileUpload = new FileUpload($file, $server, $generator); 25 | $fileUpload->setPathResolver($resolver); 26 | $fileUpload->setFileSystem($filesystem); 27 | 28 | $filename = "picture.jpg"; 29 | 30 | $this->assertEquals($generator->getFileName($filename, "image/jpg", "asdf.jpg", 0, 100, $fileUpload), $filename); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/FileUpload/FileNameGenerator/SlugTest.php: -------------------------------------------------------------------------------- 1 | 'image/jpg', 'CONTENT_LENGTH' => 30321]; 22 | $file = ['tmp_name' => $playground_path . '/real-image.jpg', 'name' => 'real-image.jpg', 'size' => 30321, 'type' => 'image/jpg', 'error' => 0]; 23 | 24 | $fileUpload = new FileUpload($file, $server, $generator); 25 | $fileUpload->setPathResolver($resolver); 26 | $fileUpload->setFileSystem($filesystem); 27 | 28 | $filename = "Awesome Picture 2.jpg"; 29 | $expectedFilename = "awesome-picture-2.jpg"; 30 | 31 | $this->assertEquals($generator->getFileName($filename, "image/jpg", "asdf.jpg", 0, 100, $fileUpload), $expectedFilename); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/FileUpload/FileSystem/SimpleTest.php: -------------------------------------------------------------------------------- 1 | filesystem = new Simple(); 17 | 18 | if (! is_dir($playground_path)) { 19 | mkdir($playground_path); 20 | } 21 | 22 | if (! is_file($playground_path . '/yadda.txt')) { 23 | copy($fixtures_path . '/yadda.txt', $playground_path . '/yadda.txt'); 24 | } 25 | } 26 | 27 | public function testIsFile() 28 | { 29 | $this->assertTrue($this->filesystem->isFile(__DIR__ . '/../../fixtures/real-image.jpg')); 30 | $this->assertFalse($this->filesystem->isFile(__DIR__ . '/../../fixtures')); 31 | } 32 | 33 | public function testIsDir() 34 | { 35 | $this->assertFalse($this->filesystem->isDir(__DIR__ . '/../../fixtures/real-image.jpg')); 36 | $this->assertTrue($this->filesystem->isDir(__DIR__ . '/../../fixtures')); 37 | } 38 | 39 | public function testWriteToFile() 40 | { 41 | $yadda = __DIR__ . '/../../playground/yadda.txt'; 42 | $path = __DIR__ . '/../../playground/test.1.txt'; 43 | 44 | $this->filesystem->writeToFile($path, $this->filesystem->getFileStream($yadda)); 45 | $this->assertFileEquals($yadda, $path); 46 | 47 | $this->filesystem->unlink($path); 48 | } 49 | 50 | public function testMoveUploadedFile() 51 | { 52 | $yadda = __DIR__ . '/../../playground/yadda.txt'; 53 | $path = __DIR__ . '/../../playground/test.2.txt'; 54 | 55 | $original_yadda = file_get_contents($yadda); 56 | 57 | $this->filesystem->moveUploadedFile($yadda, $path); 58 | $this->assertEquals($original_yadda, file_get_contents($path)); 59 | 60 | $this->filesystem->moveUploadedFile($path, $yadda); 61 | } 62 | 63 | public function testGetFilesize() 64 | { 65 | $this->assertEquals(20 * 1024, $this->filesystem->getFilesize(__DIR__ . '/../../fixtures/blob')); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /tests/FileUpload/FileTest.php: -------------------------------------------------------------------------------- 1 | assertTrue($file->isImage(), 'Detect real image'); 15 | 16 | $this->assertEquals('image/jpeg', $file->getMimeType()); 17 | } 18 | 19 | public function testCannotGetMimetypeForADirectory() 20 | { 21 | $this->expectException(Exception::class); 22 | $file = new File(__DIR__ . '/../fixtures'); 23 | $file->getMimeType(); 24 | } 25 | 26 | public function testImageNegativeDetermination() 27 | { 28 | $file = new File(__DIR__ . '/../fixtures/fake-image.jpg'); 29 | 30 | $this->assertFalse($file->isImage(), 'Detect fake image'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/FileUpload/FileUploadFactoryTest.php: -------------------------------------------------------------------------------- 1 | create([], []); 13 | 14 | $this->assertTrue($instance instanceof FileUpload); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/FileUpload/FileUploadTest.php: -------------------------------------------------------------------------------- 1 | 'image/jpg', 'CONTENT_LENGTH' => 30321]; 36 | $file = ['tmp_name' => $playground_path . '/real-image.jpg', 'name' => 'real-image.jpg', 'size' => 30321, 'type' => 'image/jpg', 'error' => 0]; 37 | 38 | $filesystem = new FileSystem\Mock(); 39 | $resolver = new PathResolver\Simple($playground_path . '/uploaded'); 40 | $uploader = new FileUpload($file, $server); 41 | $test = false; 42 | 43 | $uploader->setPathResolver($resolver); 44 | $uploader->setFileSystem($filesystem); 45 | 46 | $uploader->addCallback('completed', function () use (&$test) { 47 | $test = true; 48 | }); 49 | 50 | list($files, $headers) = $uploader->processAll(); 51 | 52 | $this->assertCount(1, $files, 'Files array should contain one file'); 53 | $this->assertEquals(0, $files[0]->error, 'Uploaded file should not have errors'); 54 | $this->assertTrue($test, 'Complete callback should set $test to true'); 55 | } 56 | 57 | // TODO: Tests for multiple uploads, chunked uploads, aborted uploads 58 | } 59 | -------------------------------------------------------------------------------- /tests/FileUpload/PathResolver/SimpleTest.php: -------------------------------------------------------------------------------- 1 | assertEquals(__DIR__ . '/../../fixtures/real-image.jpg', $resolver->getUploadPath('real-image.jpg')); 13 | } 14 | 15 | public function testUpcountName() 16 | { 17 | $resolver = new Simple(__DIR__ . '/../../fixtures'); 18 | $this->assertEquals('real-image (1).jpg', $resolver->upcountName('real-image.jpg')); 19 | $this->assertEquals('real-image (2).jpg', $resolver->upcountName('real-image (1).jpg')); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/FileUpload/Validator/DimensionValidatorTest.php: -------------------------------------------------------------------------------- 1 | "fake-image.jpg", 16 | "tmp_name" => $this->directory . 'fake-image.jpg', 17 | "size" => 12, 18 | "error" => 0 19 | ]; 20 | 21 | $file = new File($_FILES['file']['tmp_name']); 22 | 23 | $config = [ 24 | 'width' => 100, 25 | 'height' => 200 26 | ]; 27 | 28 | $this->assertFalse( 29 | $this->createValidator($config) 30 | ->validate($file, $_FILES['file']['size']) 31 | ); 32 | 33 | $this->assertEquals( 34 | 'Cannot validate the currently uploaded file by it\'s dimension as it is not an image', 35 | $file->error 36 | ); 37 | } 38 | 39 | protected function createValidator(array $config) 40 | { 41 | return new DimensionValidator($config); 42 | } 43 | 44 | public function testValidatorWorksWithTheWidthConfig() 45 | { 46 | $_FILES['file'] = [ 47 | "name" => "real-image.jpg", 48 | "tmp_name" => $this->directory . 'real-image.jpg', 49 | "size" => 12, 50 | "error" => 0 51 | ]; 52 | 53 | $file = new File($_FILES['file']['tmp_name']); 54 | 55 | $config = [ 56 | 'width' => 300 57 | ]; 58 | 59 | $this->assertTrue( 60 | $this->createValidator($config) 61 | ->validate($file, $_FILES['file']['size']) 62 | ); 63 | 64 | $config = [ 65 | 'width' => 302 66 | ]; 67 | 68 | $this->assertFalse( 69 | $this->createValidator($config) 70 | ->validate($file, $_FILES['file']['size']) 71 | ); 72 | } 73 | 74 | public function testValidatorWorksWithTheMinimumWidthConfig() 75 | { 76 | $_FILES['file'] = [ 77 | "name" => "real-image.jpg", 78 | "tmp_name" => $this->directory . 'real-image.jpg', 79 | "size" => 12, 80 | "error" => 0 81 | ]; 82 | 83 | $file = new File($_FILES['file']['tmp_name']); 84 | 85 | $config = [ 86 | 'min_width' => 200 87 | ]; 88 | 89 | $this->assertTrue( 90 | $this->createValidator($config) 91 | ->validate($file, $_FILES['file']['size']) 92 | ); 93 | 94 | $config = [ 95 | 'min_width' => 301 96 | ]; 97 | 98 | $this->assertFalse( 99 | $this->createValidator($config) 100 | ->validate($file, $_FILES['file']['size']) 101 | ); 102 | } 103 | 104 | public function testValidatorWorksWithTheMaximumWidthConfig() 105 | { 106 | $_FILES['file'] = [ 107 | "name" => "real-image.jpg", 108 | "tmp_name" => $this->directory . 'real-image.jpg', 109 | "size" => 12, 110 | "error" => 0 111 | ]; 112 | 113 | $file = new File($_FILES['file']['tmp_name']); 114 | 115 | $config = [ 116 | 'max_width' => 400 117 | ]; 118 | 119 | $this->assertTrue( 120 | $this->createValidator($config) 121 | ->validate($file, $_FILES['file']['size']) 122 | ); 123 | 124 | $config = [ 125 | 'max_width' => 250 126 | ]; 127 | 128 | $this->assertFalse( 129 | $this->createValidator($config) 130 | ->validate($file, $_FILES['file']['size']) 131 | ); 132 | } 133 | 134 | public function testValidatorWorksExpectedlyWithTheWidthAndHeightValues() 135 | { 136 | $_FILES['file'] = [ 137 | "name" => "real-image.jpg", 138 | "tmp_name" => $this->directory . 'real-image.jpg', 139 | "size" => 12, 140 | "error" => 0 141 | ]; 142 | 143 | $file = new File($_FILES['file']['tmp_name']); 144 | 145 | $config = [ 146 | 'width' => 300, 147 | 'height' => 300 148 | ]; 149 | 150 | $this->assertTrue( 151 | $this->createValidator($config) 152 | ->validate($file, $_FILES['file']['size']) 153 | ); 154 | } 155 | 156 | public function testValidatorWorksWithTheHeightConfig() 157 | { 158 | $_FILES['file'] = [ 159 | "name" => "real-image.jpg", 160 | "tmp_name" => $this->directory . 'real-image.jpg', 161 | "size" => 12, 162 | "error" => 0 163 | ]; 164 | 165 | $file = new File($_FILES['file']['tmp_name']); 166 | 167 | $config = [ 168 | 'height' => 300 169 | ]; 170 | 171 | $this->assertTrue( 172 | $this->createValidator($config) 173 | ->validate($file, $_FILES['file']['size']) 174 | ); 175 | 176 | $config = [ 177 | 'height' => 305 178 | ]; 179 | 180 | $this->assertFalse( 181 | $this->createValidator($config) 182 | ->validate($file, $_FILES['file']['size']) 183 | ); 184 | } 185 | 186 | public function testValidatorWorksWithTheMinimumHeightConfig() 187 | { 188 | $_FILES['file'] = [ 189 | "name" => "real-image.jpg", 190 | "tmp_name" => $this->directory . 'real-image.jpg', 191 | "size" => 12, 192 | "error" => 0 193 | ]; 194 | 195 | $file = new File($_FILES['file']['tmp_name']); 196 | 197 | $config = [ 198 | 'min_height' => 200 199 | ]; 200 | 201 | $this->assertTrue( 202 | $this->createValidator($config) 203 | ->validate($file, $_FILES['file']['size']) 204 | ); 205 | 206 | $config = [ 207 | 'min_height' => 301 208 | ]; 209 | 210 | $this->assertFalse( 211 | $this->createValidator($config) 212 | ->validate($file, $_FILES['file']['size']) 213 | ); 214 | } 215 | 216 | public function testValidatorWorksWithTheMaximumHeightConfig() 217 | { 218 | $_FILES['file'] = [ 219 | "name" => "real-image.jpg", 220 | "tmp_name" => $this->directory . 'real-image.jpg', 221 | "size" => 12, 222 | "error" => 0 223 | ]; 224 | 225 | $file = new File($_FILES['file']['tmp_name']); 226 | 227 | $config = [ 228 | 'max_height' => 400 229 | ]; 230 | 231 | $this->assertTrue( 232 | $this->createValidator($config) 233 | ->validate($file, $_FILES['file']['size']) 234 | ); 235 | 236 | $config = [ 237 | 'max_height' => 209 238 | ]; 239 | 240 | $this->assertFalse( 241 | $this->createValidator($config) 242 | ->validate($file, $_FILES['file']['size']) 243 | ); 244 | } 245 | 246 | public function testValidatorWorksAsExpectedWithAllConfigOption() 247 | { 248 | $_FILES['file'] = [ 249 | "name" => "real-image.jpg", 250 | "tmp_name" => $this->directory . 'real-image.jpg', 251 | "size" => 12, 252 | "error" => 0 253 | ]; 254 | 255 | $file = new File($_FILES['file']['tmp_name']); 256 | 257 | $config = [ 258 | 'width' => 300, 259 | 'height' => 300, 260 | 'max_width' => 310, 261 | 'max_height' => 350 262 | ]; 263 | 264 | $this->assertTrue( 265 | $this->createValidator($config) 266 | ->validate($file, $_FILES['file']['size']) 267 | ); 268 | } 269 | 270 | public function testSetErrorMessages() 271 | { 272 | $_FILES['file'] = [ 273 | "name" => "real-image.jpg", 274 | "tmp_name" => $this->directory . 'real-image.jpg', 275 | "size" => 12, 276 | "error" => 0 277 | ]; 278 | 279 | $file = new File($_FILES['file']['tmp_name']); 280 | 281 | $config = [ 282 | 'width' => 301, 283 | 'height' => 301 284 | ]; 285 | 286 | $validator = $this->createValidator($config); 287 | 288 | $validator->setErrorMessages([ 289 | DimensionValidator::HEIGHT => "Height too large" 290 | ]); 291 | 292 | $validator->validate($file, $_FILES['file']['size']); 293 | 294 | $this->assertEquals( 295 | 'Height too large', 296 | $file->error 297 | ); 298 | } 299 | 300 | protected function setUp(): void 301 | { 302 | $this->directory = __DIR__ . '/../../fixtures/'; 303 | } 304 | } 305 | -------------------------------------------------------------------------------- /tests/FileUpload/Validator/MimeTypeValidatorTest.php: -------------------------------------------------------------------------------- 1 | "real-image.jpg", 21 | "tmp_name" => $this->directory . 'real-image.jpg', 22 | "size" => 12, 23 | "error" => 0 24 | ]; 25 | 26 | $file = new File($_FILES['file']['tmp_name']); 27 | 28 | $this->assertTrue($this->validator->validate($file, $_FILES['file']['size'])); 29 | } 30 | 31 | public function testInvalidMimeType() 32 | { 33 | $_FILES['file'] = [ 34 | "name" => "fake-image.jpg", 35 | "tmp_name" => $this->directory . 'fake-image.jpg', 36 | "size" => 12, 37 | "error" => 0 38 | ]; 39 | 40 | $file = new File($_FILES['file']['tmp_name']); 41 | 42 | $this->assertFalse($this->validator->validate($file, $_FILES['file']['size'])); 43 | } 44 | 45 | protected function setUp(): void 46 | { 47 | $this->directory = __DIR__ . '/../../fixtures/'; 48 | 49 | $this->validator = new MimeTypeValidator(["image/jpeg"]); 50 | } 51 | 52 | public function testSetErrorMessages() 53 | { 54 | $_FILES['file'] = [ 55 | "name" => "fake-image.jpg", 56 | "tmp_name" => $this->directory . 'fake-image.jpg', 57 | "size" => 12, 58 | "error" => 0 59 | ]; 60 | 61 | $file = new File($_FILES['file']['tmp_name']); 62 | 63 | $errorMessage = "Invalid file type"; 64 | 65 | $this->validator->setErrorMessages([ 66 | 0 => $errorMessage 67 | ]); 68 | 69 | $this->validator->validate($file, $_FILES['file']['size']); 70 | 71 | $this->assertEquals($errorMessage, $file->error); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /tests/FileUpload/Validator/SimpleTest.php: -------------------------------------------------------------------------------- 1 | assertFalse($validator->validate($file, $_FILES['file']['size'])); 17 | $this->assertNotEmpty($file->error); 18 | } 19 | 20 | public function testExceedMaxSize() 21 | { 22 | $validator = new Simple("20K"); 23 | 24 | $file = new File($_FILES['file']['tmp_name']); 25 | 26 | $this->assertFalse($validator->validate($file, 10)); 27 | $this->assertNotEmpty($file->error); 28 | } 29 | 30 | public function testFailMaxSize1A() 31 | { 32 | $this->expectException(Exception::class); 33 | $validator = new Simple("1A", []); 34 | } 35 | 36 | public function testWrongMime() 37 | { 38 | $validator = new Simple("1M", ['image/png']); 39 | 40 | $file = new File($_FILES['file']['tmp_name']); 41 | 42 | $this->assertFalse($validator->validate($file, 7)); 43 | $this->assertNotEmpty($file->error); 44 | } 45 | 46 | public function testOk() 47 | { 48 | $validator = new Simple("40K", ['image/jpeg']); 49 | $file = new File($_FILES['file']['tmp_name']); 50 | 51 | $this->assertTrue($validator->validate($file, $_FILES['file']['size'])); 52 | $this->assertEmpty($file->error); 53 | } 54 | 55 | public function testSetErrorMessages() 56 | { 57 | $file = new File($_FILES['file']['tmp_name']); 58 | 59 | $validator = new Simple(10, ['image/png']); 60 | 61 | $errorMessage = "Invalid file size"; 62 | 63 | $validator->setErrorMessages([ 64 | 0 => $errorMessage 65 | ]); 66 | 67 | $validator->validate($file, $_FILES['file']['size']); 68 | 69 | $this->assertEquals($errorMessage, $file->error); 70 | } 71 | 72 | protected function setUp(): void 73 | { 74 | $this->directory = __DIR__ . '/../../fixtures/'; 75 | 76 | $_FILES['file'] = [ 77 | "name" => "real-image.jpg", 78 | "tmp_name" => $this->directory . 'real-image.jpg', 79 | "size" => 12, 80 | "error" => 0 81 | ]; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /tests/FileUpload/Validator/SizeValidatorTest.php: -------------------------------------------------------------------------------- 1 | directory = __DIR__ . '/../../fixtures/'; 18 | 19 | $_FILES['file'] = [ 20 | "name" => "real-image.jpg", 21 | "tmp_name" => $this->directory . 'real-image.jpg', 22 | "size" => 12, 23 | "error" => 0 24 | ]; 25 | } 26 | 27 | public function testNumericMaxSize() 28 | { 29 | $this->validator = new SizeValidator(pow(1024, 3)); 30 | 31 | $file = new File($_FILES['file']['tmp_name']); 32 | 33 | $this->assertTrue($this->validator->validate($file, $_FILES['file']['size'])); 34 | } 35 | 36 | public function testBetweenMinAndMaxSize() 37 | { 38 | $this->validator = new SizeValidator("40K", "10K"); 39 | 40 | $file = new File($_FILES['file']['tmp_name']); 41 | 42 | $this->assertTrue($this->validator->validate($file, $_FILES['file']['size'])); 43 | } 44 | 45 | 46 | public function testFileSizeTooLarge() 47 | { 48 | $this->validator = new SizeValidator("20K", 10); 49 | 50 | $file = new File($_FILES['file']['tmp_name']); 51 | 52 | $this->assertFalse($this->validator->validate($file, $_FILES['file']['size'])); 53 | } 54 | 55 | public function testFileSizeTooSmall() 56 | { 57 | $this->validator = new SizeValidator("1M", "50k"); 58 | 59 | $file = new File($_FILES['file']['tmp_name']); 60 | 61 | $this->assertFalse($this->validator->validate($file, $_FILES['file']['size'])); 62 | } 63 | 64 | public function testSetMaximumErrorMessages() 65 | { 66 | $this->validator = new SizeValidator("29K", "10K"); 67 | 68 | $file = new File($_FILES['file']['tmp_name']); 69 | 70 | $fileTooLarge = "Too Large"; 71 | 72 | $this->validator->setErrorMessages([ 73 | 0 => $fileTooLarge 74 | ]); 75 | 76 | $this->assertFalse($this->validator->validate($file, $_FILES['file']['size'])); 77 | 78 | $this->assertEquals($fileTooLarge, $file->error); 79 | } 80 | 81 | public function testSetMinimumErrorMessages() 82 | { 83 | $this->validator = new SizeValidator("40K", "35K"); 84 | 85 | $file = new File($_FILES['file']['tmp_name']); 86 | 87 | $fileTooSmall = "Too Small"; 88 | 89 | $this->validator->setErrorMessages([ 90 | 1 => $fileTooSmall 91 | ]); 92 | 93 | $this->assertFalse($this->validator->validate($file, $_FILES['file']['size'])); 94 | 95 | $this->assertEquals($fileTooSmall, $file->error); 96 | } 97 | 98 | public function testInvalidMaximumFileSize() 99 | { 100 | $this->expectException(Exception::class); 101 | $this->expectExceptionMessage("Invalid File Max_Size"); 102 | $this->validator = new SizeValidator("40A", 3); 103 | } 104 | 105 | public function testInvalidMinimumFilesSize() 106 | { 107 | $this->expectException(Exception::class); 108 | $this->expectExceptionMessage("Invalid File Min_Size"); 109 | $this->validator = new SizeValidator("40K", "-3"); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /tests/fixtures/blob: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/fake-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gargron/fileupload/bb83899fd86168941d762b2e3bafe76312576649/tests/fixtures/fake-image.jpg -------------------------------------------------------------------------------- /tests/fixtures/real-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gargron/fileupload/bb83899fd86168941d762b2e3bafe76312576649/tests/fixtures/real-image.jpg -------------------------------------------------------------------------------- /tests/fixtures/yadda.txt: -------------------------------------------------------------------------------- 1 | Blah blah blah 2 | -------------------------------------------------------------------------------- /tests/playground/yadda.txt: -------------------------------------------------------------------------------- 1 | Blah blah blah 2 | -------------------------------------------------------------------------------- /todo.md: -------------------------------------------------------------------------------- 1 | Add Filename Generator 2 | Write more validators 3 | Add more callbacks 4 | --------------------------------------------------------------------------------