├── .gitignore ├── .travis.yml ├── .travis └── travis.sh ├── .vagrant ├── .gitignore └── bootstrap.sh ├── LICENSE ├── README.md ├── Vagrantfile ├── composer.json ├── phpunit.xml ├── src ├── Middleware │ └── RememberFileUploads.php ├── RememberUploadsServiceProvider.php ├── RememberedFile.php ├── RememberedFileBag.php ├── ViewComposers │ └── RememberedFilesComposer.php ├── config.php └── helpers.php └── tests ├── ConfigurationTest.php ├── Stubs ├── TestController.php ├── ValidationTestController.php ├── test.blade.php └── test.jpg └── UploadTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | vendor 3 | composer.lock -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | sudo: false 4 | 5 | matrix: 6 | include: 7 | - php: 5.6 8 | - php: 7.0 9 | - php: 7.1 10 | - php: nightly 11 | allow_failures: 12 | - php: nightly 13 | fast_finish: true 14 | 15 | cache: 16 | directories: 17 | - $HOME/.composer/cache 18 | 19 | before_install: 20 | - source .travis/travis.sh 21 | - xdebug-disable 22 | - travis_retry composer self-update 23 | 24 | install: 25 | - travis_retry composer install --no-interaction --prefer-dist --no-suggest; 26 | 27 | script: 28 | - run-tests -------------------------------------------------------------------------------- /.travis/travis.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # The problem is that we do not want to remove the configuration file, just disable it for a few tasks, then enable it 4 | # 5 | # For reference, see 6 | # 7 | # - https://docs.travis-ci.com/user/languages/php#Disabling-preinstalled-PHP-extensions 8 | # - https://docs.travis-ci.com/user/languages/php#Custom-PHP-configuration 9 | # 10 | # Original Source (this was copied from): 11 | # - https://github.com/codeclimate/php-test-reporter/blob/master/.travis/travis.sh 12 | 13 | config="/home/travis/.phpenv/versions/$(phpenv version-name)/etc/conf.d/xdebug.ini" 14 | 15 | function xdebug-disable() { 16 | if [[ -f $config ]]; then 17 | mv $config "$config.bak" 18 | fi 19 | } 20 | 21 | function xdebug-enable() { 22 | if [[ -f "$config.bak" ]]; then 23 | mv "$config.bak" $config 24 | fi 25 | } 26 | 27 | function run-tests() { 28 | if [[ "$WITH_COVERAGE" == "true" ]]; then 29 | xdebug-enable 30 | vendor/bin/phpunit --coverage-clover=$TRAVIS_BUILD_DIR/build/logs/clover.xml 31 | xdebug-disable 32 | else 33 | vendor/bin/phpunit 34 | fi 35 | } -------------------------------------------------------------------------------- /.vagrant/.gitignore: -------------------------------------------------------------------------------- 1 | machines -------------------------------------------------------------------------------- /.vagrant/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | Update () { 4 | echo "-- Update packages --" 5 | sudo apt-get update 6 | sudo apt-get upgrade 7 | } 8 | Update 9 | 10 | echo "-- Install PPA's --" 11 | sudo add-apt-repository ppa:ondrej/php 12 | Update 13 | 14 | echo "-- Install packages --" 15 | sudo apt-get install -y --force-yes curl git git-core 16 | sudo apt-get install -y --force-yes php7.0-common php7.0-dev php7.0-json php7.0-opcache php7.0-cli php7.0 php7.0-mysql php7.0-fpm php7.0-curl php7.0-gd php7.0-mcrypt php7.0-mbstring php7.0-bcmath php7.0-zip php7.0-xml 17 | 18 | echo "-- Install Composer --" 19 | curl -s https://getcomposer.org/installer | php 20 | sudo mv composer.phar /usr/local/bin/composer 21 | sudo chmod +x /usr/local/bin/composer -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 PhotoGabble 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, 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, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Laravel Remember Uploads

2 |

Middleware Package

3 | 4 |

5 | Build Status 6 | Latest Stable Version 7 | License 8 |

9 | 10 | ## About this package 11 | 12 | This middleware solves the issue of unrelated form validation errors redirecting the user back and loosing the files that had been uploaded. It does this by temporarily caching server-side the file fields that have passed validation so that they may be processed once the whole form has been submitted passing validation. 13 | 14 | ## Install 15 | 16 | Add to your project with composer via `composer require photogabble/laravel-remember-uploads`. 17 | 18 | ### Laravel Version >= 5.5 19 | 20 | This library supports [package auto-discovery](https://medium.com/@taylorotwell/package-auto-discovery-in-laravel-5-5-ea9e3ab20518) in Laravel >= 5.5. 21 | 22 | ### Laravel Versions 5.2 - 5.5 23 | 24 | To enable the package you will need to add its service provider to your app providers configuration in Laravel. 25 | 26 | ```php 27 | 'providers' => [ 28 | // ... 29 | 30 | Photogabble\LaravelRememberUploads\RememberUploadsServiceProvider::class, 31 | 32 | // ... 33 | ], 34 | ``` 35 | 36 | ## Usage 37 | 38 | You need to assign the middleware `remember.files` to routes that process uploaded files; in the case of CRUD terminology that would be the _create_ and _update_ methods. 39 | 40 | So that the middleware is aware of remembered files from the previous request you need to include a reference by way of using a hidden input field with the name `_rememberedFiles`. 41 | 42 | ```php 43 | @if( $oldFile = rememberedFile('file')) 44 | 45 | @else 46 | 47 | @endif 48 | ``` 49 | 50 | Then within your controller code you can obtain the file via the `rememberedFile` helper: 51 | 52 | ```php 53 | function store(Illuminate\Http\Request $request) { 54 | if ($file = $request->file('img', rememberedFile('img')) { 55 | // ... File exists ... 56 | } 57 | } 58 | ``` 59 | 60 | The `$file` variable will equal an instance of `Symfony\Component\HttpFoundation\File\UploadedFile` if the file has been posted during the current request or remembered. 61 | 62 | This example is viewable as a test case [within this libaries tests](https://github.com/photogabble/laravel-remember-uploads/blob/master/tests/UploadTest.php#L192). 63 | 64 | ### Array File Fields 65 | 66 | In the case where you have multiple upload fields sharing the same name for example `image[0]`, `image[1]`; the helper `rememberedFile('image')` will return an array of `Symfony\Component\HttpFoundation\File\UploadedFile`. 67 | 68 | The reference `_rememberedFiles` will also need to match the array syntax of the file inputs it mirrors: 69 | 70 | ```php 71 | @if( $oldFile = rememberedFile('image')) 72 | 73 | 74 | 75 | @else 76 | 77 | 78 | @endif 79 | ``` 80 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | Vagrant.configure("2") do |config| 5 | config.vm.box = "ubuntu/trusty64" 6 | config.vm.network "private_network", ip: "192.168.33.10" 7 | config.vm.synced_folder ".", "/vagrant_data" 8 | config.vm.provider "virtualbox" do |vb| 9 | # Setup VM with 1 CPU and 512MB of RAM, this should be 10 | # more than enough for development. 11 | vb.memory = "512" 12 | end 13 | config.vm.provision "shell", path: "./.vagrant/bootstrap.sh" 14 | end 15 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "photogabble/laravel-remember-uploads", 3 | "type": "library", 4 | "description": "Laravel middleware and helper for remembering uploaded files during form validation.", 5 | "keywords": [ 6 | "laravel", 7 | "file", 8 | "validation", 9 | "middleware" 10 | ], 11 | "support": { 12 | "issues": "https://github.com/photogabble/laravel-remember-uploads/issues", 13 | "source": "https://github.com/photogabble/laravel-remember-uploads" 14 | }, 15 | "license": "MIT", 16 | "authors": [ 17 | { 18 | "name": "Simon Dann", 19 | "email": "simon.dann@gmail.com", 20 | "homepage": "https://www.photogabble.co.uk" 21 | } 22 | ], 23 | "require": { 24 | "php": ">=5.6.0" 25 | }, 26 | "require-dev": { 27 | "phpunit/phpunit": "5.7.*", 28 | "orchestra/testbench": "~3.4" 29 | }, 30 | "autoload": { 31 | "psr-4": { 32 | "Photogabble\\LaravelRememberUploads\\": "src/" 33 | }, 34 | "files": [ 35 | "src/helpers.php" 36 | ] 37 | }, 38 | "autoload-dev": { 39 | "psr-4": { 40 | "Photogabble\\LaravelRememberUploads\\Tests\\": "tests/" 41 | } 42 | }, 43 | "extra": { 44 | "branch-alias": { 45 | "dev-master": "1.3.0-dev" 46 | }, 47 | "laravel": { 48 | "providers": [ 49 | "Photogabble\\LaravelRememberUploads\\RememberUploadsServiceProvider" 50 | ] 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | ./tests/ 23 | 24 | 25 | 26 | 27 | 28 | ./src 29 | 30 | ./tests 31 | ./vendor 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/Middleware/RememberFileUploads.php: -------------------------------------------------------------------------------- 1 | if they are replace cached 24 | * version with fresh uploaded version 25 | * 2.a.ii For Uploaded Files that aren't cached do so 26 | * 3. On validation error user is redirected back to form 27 | * 3.a Form now contains hidden fields for each item returned by rememberedFile helper. 28 | * 4. Controller method can obtain Files via the rememberedFile helper. 29 | * 5. Controller clears remembered files via the clearRememberedFiles helper. 30 | * 31 | * @package Photogabble\LaravelRememberUploads\Middleware 32 | */ 33 | class RememberFileUploads 34 | { 35 | 36 | /** 37 | * @var Store 38 | */ 39 | private $session; 40 | 41 | /** 42 | * @var CacheManager 43 | */ 44 | private $cache; 45 | 46 | /** 47 | * @var string 48 | */ 49 | private $storagePath; 50 | 51 | /** 52 | * Session lifetime, used for caching values. 53 | * @var int 54 | */ 55 | private $cacheTimeout = 0; 56 | 57 | /** 58 | * RememberFileUploads constructor. 59 | * @param Store $store 60 | * @param CacheManager $cache 61 | * @throws \Exception 62 | */ 63 | public function __construct(Store $store, CacheManager $cache) 64 | { 65 | $this->session = $store; 66 | $this->cache = $cache; 67 | $this->storagePath = config('remember-uploads.temporary_storage_path'); 68 | $this->cacheTimeout = config('session.lifetime'); 69 | 70 | if (! file_exists($this->storagePath)) { 71 | if (!mkdir($this->storagePath)) { 72 | throw new \Exception('Could not create directory ['. $this->storagePath .'].'); 73 | } 74 | } 75 | } 76 | 77 | /** 78 | * Handle an incoming request. 79 | * 80 | * @todo write a test to check that adding additional uploaded files to the same session doesn't break things 81 | * 82 | * @param \Illuminate\Http\Request $request 83 | * @param \Closure $next 84 | * @param array $fields 85 | * @return mixed 86 | * @throws \Exception 87 | */ 88 | public function handle($request, Closure $next, $fields = ['*']) 89 | { 90 | $this->session->flash('_remembered_files', new RememberedFileBag(array_merge($this->checkRequestForRemembered($request, $fields), $this->remember($request, $fields)))); 91 | return $next($request); 92 | } 93 | 94 | /** 95 | * Remember all files found in request. 96 | * 97 | * @param \Illuminate\Http\Request $request 98 | * @param array $fields 99 | * @return array|RememberedFile[] 100 | */ 101 | private function checkRequestForRemembered($request, array $fields) 102 | { 103 | $remembered = $request->get('_rememberedFiles', []); 104 | $files = ($fields[0] === '*') ? $remembered : array_filter($remembered, function($k) use ($fields) { return in_array($k, $fields); }, ARRAY_FILTER_USE_KEY); 105 | return $this->rememberFilesFactory($files); 106 | } 107 | 108 | /** 109 | * Remember all files found in request. 110 | * 111 | * @param \Illuminate\Http\Request $request 112 | * @param array $fields 113 | * @return array|RememberedFile[] 114 | */ 115 | private function remember($request, array $fields) 116 | { 117 | $files = ($fields[0] === '*') ? $request->files : $request->only($fields); 118 | return $this->rememberFilesFactory($files); 119 | } 120 | 121 | /** 122 | * Recursive factory method to create RememberedFile from UploadedFile. 123 | * 124 | * @param array|UploadedFile[] $files 125 | * @param string $prefix 126 | * @return array|RememberedFile[] 127 | */ 128 | private function rememberFilesFactory($files, $prefix = '') 129 | { 130 | $result = []; 131 | 132 | foreach ($files as $key => $file) { 133 | $cacheKey = $prefix . (empty($prefix) ? '' : '.') . $key; 134 | if (is_string($file)){ 135 | if (! $this->cache->has('_remembered_files.'.$cacheKey)){ 136 | continue; 137 | } 138 | /** @noinspection Annotator */ 139 | $cached = $this->cache->get('_remembered_files.'.$cacheKey); 140 | if ($cached instanceof RememberedFile){ 141 | $result[$key] = $cached; 142 | } 143 | unset($cached); 144 | continue; 145 | } 146 | if (is_array($file)) { 147 | $result[$key] = $this->rememberFilesFactory($file, $cacheKey); 148 | } else { 149 | $storagePathName = $this->storagePath . DIRECTORY_SEPARATOR . $file->getFilename(); 150 | copy($file->getPathname(), $storagePathName); 151 | $rememberedFile = new RememberedFile($storagePathName, $file); 152 | $this->cache->put('_remembered_files.'.$cacheKey, $rememberedFile, $this->cacheTimeout); 153 | $result[$key] = $rememberedFile; 154 | } 155 | } 156 | 157 | return $result; 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/RememberUploadsServiceProvider.php: -------------------------------------------------------------------------------- 1 | publishes([ 26 | __DIR__.DIRECTORY_SEPARATOR.'config.php' => config_path('remember-uploads.php'), 27 | ]); 28 | } 29 | 30 | /** 31 | * Register the service provider. 32 | * 33 | * @return void 34 | */ 35 | public function register() 36 | { 37 | $this->mergeConfigFrom( 38 | __DIR__.DIRECTORY_SEPARATOR.'config.php', 'remember-uploads' 39 | ); 40 | 41 | /** @var Router $router */ 42 | $router =$this->app->make(Router::class); 43 | $router->aliasMiddleware('remember.files', RememberFileUploads::class); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/RememberedFile.php: -------------------------------------------------------------------------------- 1 | tmpPathname = $tmpPathname; 46 | $this->originalName = $file->getClientOriginalName(); 47 | $this->mimeType = $file->getMimeType(); 48 | $this->size = $file->getSize(); 49 | } 50 | 51 | /** 52 | * @return UploadedFile 53 | */ 54 | public function toUploadedFile() 55 | { 56 | return new UploadedFile($this->tmpPathname, $this->originalName, $this->mimeType, $this->size); 57 | } 58 | } -------------------------------------------------------------------------------- /src/RememberedFileBag.php: -------------------------------------------------------------------------------- 1 | keys() as $key) { 16 | $this->remove($key); 17 | } 18 | } 19 | 20 | /** 21 | * @return FileBag 22 | */ 23 | public function toFileBag() 24 | { 25 | return new FileBag($this->allAsUploadFile($this->parameters)); 26 | } 27 | 28 | /** 29 | * @param array|null $items 30 | * @return array 31 | */ 32 | public function allAsUploadFile(array $items) 33 | { 34 | $result = []; 35 | 36 | foreach ($items as $key => $value) { 37 | if (is_array($value)){ 38 | $result[$key] = $this->allAsUploadFile($value); 39 | } else { 40 | if ($value instanceof RememberedFile) { 41 | $result[$key] = $value->toUploadedFile(); 42 | } 43 | } 44 | } 45 | 46 | return $result; 47 | } 48 | } -------------------------------------------------------------------------------- /src/ViewComposers/RememberedFilesComposer.php: -------------------------------------------------------------------------------- 1 | session = $store; 25 | } 26 | 27 | /** 28 | * Bind data to the view. 29 | * 30 | * @param View $view 31 | * @return void 32 | */ 33 | public function compose(View $view) 34 | { 35 | /** @var RememberedFileBag $rememberedFiles */ 36 | $rememberedFiles = $this->session->get('_remembered_files', new RememberedFileBag()); 37 | $view->with('rememberedFiles', $rememberedFiles->toFileBag()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/config.php: -------------------------------------------------------------------------------- 1 | = 24 hours old so that it doesn't 10 | * grow huge over time. 11 | */ 12 | 'temporary_storage_path' => storage_path('app' . DIRECTORY_SEPARATOR . 'tmp-image-uploads') 13 | 14 | ]; -------------------------------------------------------------------------------- /src/helpers.php: -------------------------------------------------------------------------------- 1 | forget('_remembered_files'); 7 | } 8 | } 9 | 10 | if (! function_exists('rememberedFile')) 11 | { 12 | /** 13 | * @param null|string $key 14 | * @param null|mixed $default 15 | * @return mixed|\Symfony\Component\HttpFoundation\FileBag|Illuminate\Http\UploadedFile 16 | */ 17 | function rememberedFile($key = null, $default = null) { 18 | /** @var Illuminate\Session\Store $session */ 19 | $session = app('session'); 20 | 21 | /** @var \Illuminate\Support\MessageBag $errors */ 22 | $errors = $session->get('errors', new \Illuminate\Support\MessageBag()); 23 | 24 | /** @var \Photogabble\LaravelRememberUploads\RememberedFileBag $fileBag */ 25 | $fileBag = $session->get('_remembered_files', new \Photogabble\LaravelRememberUploads\RememberedFileBag()); 26 | $fileBag->filterFailedValidation($errors); 27 | $fileBag = $fileBag->toFileBag(); 28 | 29 | return is_null($key) ? $fileBag : $fileBag->get($key, $default); 30 | } 31 | } 32 | 33 | if (! function_exists('oldFile')) { 34 | /** 35 | * @deprecated 36 | * @throws Exception 37 | * @todo remove from version 1.4 onwards 38 | */ 39 | function oldFile() { 40 | throw new Exception('The oldFile function has been deprecated in favour of using rememberedFile'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/ConfigurationTest.php: -------------------------------------------------------------------------------- 1 | assertCount(1, $config); 30 | $this->assertArrayHasKey('temporary_storage_path', $config); 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /tests/Stubs/TestController.php: -------------------------------------------------------------------------------- 1 | back(); 18 | } 19 | } -------------------------------------------------------------------------------- /tests/Stubs/ValidationTestController.php: -------------------------------------------------------------------------------- 1 | validate($request, [ 17 | 'img' => 'required_without:_rememberedFiles.img|mimes:jpeg' 18 | ]); 19 | 20 | $file = rememberedFile('img', $request->file('img')); 21 | 22 | return json_encode([ 23 | 'name' => $file->getFilename() 24 | ]); 25 | } 26 | 27 | public function arrayFileUpload(Request $request) 28 | { 29 | $this->validate($request, [ 30 | 'img' => 'required_without:_rememberedFiles.img', 31 | 'img.*' => 'mimes:jpeg' 32 | ]); 33 | 34 | /** @var UploadedFile[] $files */ 35 | $files = rememberedFile('img', $request->file('img')); 36 | 37 | return json_encode([ 38 | 'name_0' => $files[0]->getFilename(), 39 | 'name_1' => $files[1]->getFilename(), 40 | ]); 41 | } 42 | 43 | public function failedFileUpload(Request $request) 44 | { 45 | $this->validate($request, [ 46 | 'img' => 'required_without:_rememberedFiles.img|mimes:png' 47 | ]); 48 | } 49 | } -------------------------------------------------------------------------------- /tests/Stubs/test.blade.php: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/photogabble/laravel-remember-uploads/28d0667d2de74e5cd4f1980920df828cc5f99514/tests/Stubs/test.blade.php -------------------------------------------------------------------------------- /tests/Stubs/test.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/photogabble/laravel-remember-uploads/28d0667d2de74e5cd4f1980920df828cc5f99514/tests/Stubs/test.jpg -------------------------------------------------------------------------------- /tests/UploadTest.php: -------------------------------------------------------------------------------- 1 | app->make('router'); 36 | 37 | $router->post('test', function () { 38 | return ['ok' => true]; 39 | })->middleware('remember.files'); 40 | } 41 | 42 | public function tearDown() 43 | { 44 | $file = new Filesystem(); 45 | $file->cleanDirectory(storage_path('app' . DIRECTORY_SEPARATOR . 'tmp-image-uploads')); 46 | $file->deleteDirectory(storage_path('app' . DIRECTORY_SEPARATOR . 'tmp-image-uploads')); 47 | parent::tearDown(); 48 | } 49 | 50 | /** 51 | * This tests to see if the middleware correctly captures the uploaded file and that the 52 | * view composer injects that captured upload into the next page load via flash sessions. 53 | * 54 | * It then goes to check that "refreshing" the page without any file upload will clear 55 | * the captured uploaded file. 56 | */ 57 | public function testSingleFileUpload() 58 | { 59 | /** @var Store $session */ 60 | $session = $this->app->make(Store::class); 61 | 62 | $remembered = $session->get('_remembered_files', []); 63 | $this->assertEquals([], $remembered); 64 | 65 | $file = $this->mockUploadedFile(__DIR__.DIRECTORY_SEPARATOR.'Stubs'.DIRECTORY_SEPARATOR.'test.jpg'); 66 | 67 | $response = $this->call('POST', 'test', [], [], ['img' => $file], ['Accept' => 'application/json']); 68 | $this->assertTrue($response->isOk()); 69 | $content = json_decode($response->getContent()); 70 | $this->assertTrue($content->ok); 71 | $session->ageFlashData(); // should this be required, shouldn't it happen during $this->call? 72 | 73 | /** @var RememberedFileBag $remembered */ 74 | $remembered = $session->get('_remembered_files'); 75 | $this->assertInstanceOf(RememberedFileBag::class, $remembered); 76 | $this->assertTrue($remembered->has('img')); 77 | $this->assertEquals($file->getClientOriginalName(), $remembered->get('img')->originalName); 78 | 79 | // 80 | // Test that the view composer sets the right properties 81 | // 82 | 83 | $viewData = $this->mockView()->getData(); 84 | $this->assertArrayHasKey('rememberedFiles', $viewData); 85 | /** @var FileBag $remembered */ 86 | $remembered = $viewData['rememberedFiles']; 87 | $this->assertInstanceOf(FileBag::class, $remembered); 88 | $this->assertEquals(1, $remembered->count()); 89 | 90 | // 91 | // Test that upon re-calling the post event without any image data that 92 | // the _remembered_files doesn't contain any old data. 93 | // 94 | 95 | $response = $this->call('POST', 'test', [], [], [], ['Accept' => 'application/json']); 96 | $this->assertTrue($response->isOk()); 97 | $session->ageFlashData(); // should this be required, shouldn't it happen during $this- 98 | 99 | $remembered = $session->get('_remembered_files', new RememberedFileBag()); 100 | $this->assertInstanceOf(RememberedFileBag::class, $remembered); 101 | $this->assertEquals(0, $remembered->count()); 102 | 103 | // 104 | // Test that the view composer sets the right properties 105 | // 106 | 107 | $viewData = $this->mockView()->getData(); 108 | $this->assertArrayHasKey('rememberedFiles', $viewData); 109 | /** @var FileBag $remembered */ 110 | $remembered = $viewData['rememberedFiles']; 111 | $this->assertInstanceOf(FileBag::class, $remembered); 112 | $this->assertEquals(0, $remembered->count()); 113 | } 114 | 115 | /** 116 | * This tests to see if the middleware correctly captures the uploaded file if the input 117 | * is within an array. 118 | * 119 | * This test was written for issue #15. 120 | * @see https://github.com/photogabble/laravel-remember-uploads/issues/15 121 | */ 122 | public function testArrayFileUpload() 123 | { 124 | /** @var Store $session */ 125 | $session = $this->app->make(Store::class); 126 | 127 | $remembered = $session->get('_remembered_files', []); 128 | $this->assertEquals([], $remembered); 129 | 130 | $files = [ 131 | $this->mockUploadedFile(__DIR__.DIRECTORY_SEPARATOR.'Stubs'.DIRECTORY_SEPARATOR.'test.jpg'), 132 | $this->mockUploadedFile(__DIR__.DIRECTORY_SEPARATOR.'Stubs'.DIRECTORY_SEPARATOR.'test.jpg'), 133 | ]; 134 | 135 | $response = $this->call('POST', 'test', [], [], ['img' => $files], ['Accept' => 'application/json']); 136 | $this->assertTrue($response->isOk()); 137 | $content = json_decode($response->getContent()); 138 | $this->assertTrue($content->ok); 139 | $session->ageFlashData(); 140 | 141 | /** @var RememberedFileBag $remembered */ 142 | $remembered = $session->get('_remembered_files'); 143 | $this->assertTrue($remembered->has('img')); 144 | $this->assertTrue(is_array($remembered->get('img'))); 145 | $this->assertEquals($files[0]->getClientOriginalName(), $remembered->get('img')[0]->originalName); 146 | $this->assertEquals($files[1]->getClientOriginalName(), $remembered->get('img')[1]->originalName); 147 | 148 | // 149 | // Test that the view composer sets the right properties 150 | // 151 | 152 | $viewData = $this->mockView()->getData(); 153 | $this->assertArrayHasKey('rememberedFiles', $viewData); 154 | 155 | /** @var FileBag $remembered */ 156 | $remembered = $viewData['rememberedFiles']; 157 | 158 | $this->assertInstanceOf(FileBag::class, $viewData['rememberedFiles']); 159 | $this->assertEquals(1, $remembered->count()); 160 | $this->assertTrue($remembered->has('img')); 161 | $this->assertTrue(is_array($remembered->get('img'))); 162 | $this->assertEquals(2, count($remembered->get('img'))); 163 | 164 | // 165 | // Test that upon re-calling the post event without any image data that 166 | // the _remembered_files doesn't contain any old data. 167 | // 168 | 169 | $response = $this->call('POST', 'test', [], [], [], ['Accept' => 'application/json']); 170 | $this->assertTrue($response->isOk()); 171 | $session->ageFlashData(); // should this be required, shouldn't it happen during $this- 172 | 173 | $remembered = $session->get('_remembered_files', new RememberedFileBag()); 174 | $this->assertInstanceOf(RememberedFileBag::class, $remembered); 175 | $this->assertEquals(0, $remembered->count()); 176 | 177 | // 178 | // Test that the view composer sets the right properties 179 | // 180 | 181 | $viewData = $this->mockView()->getData(); 182 | $this->assertArrayHasKey('rememberedFiles', $viewData); 183 | /** @var FileBag $remembered */ 184 | $remembered = $viewData['rememberedFiles']; 185 | $this->assertInstanceOf(FileBag::class, $remembered); 186 | $this->assertEquals(0, $remembered->count()); 187 | } 188 | 189 | /** 190 | * This test is in place as an example to be referenced by the README.md 191 | */ 192 | public function testFileControllerExample() 193 | { 194 | /** 195 | * @var \Illuminate\Routing\Router $router 196 | */ 197 | $router = $this->app->make('router'); 198 | 199 | $router->post('test-request', function (Request $request) { 200 | $file = rememberedFile('img', $request->file('img')); 201 | return ['ok' => true, 'filename' => $file->getFilename(), 'pathname' => $file->getPathname()]; 202 | })->middleware('remember.files'); 203 | 204 | /** @var Store $session */ 205 | $session = $this->app->make(Store::class); 206 | 207 | // Post the File the first time 208 | $file = $this->mockUploadedFile(__DIR__.DIRECTORY_SEPARATOR.'Stubs'.DIRECTORY_SEPARATOR.'test.jpg'); 209 | 210 | $this->call('POST', 'test-request', [], [], ['img' => $file], ['Accept' => 'application/json']); 211 | $session->ageFlashData(); 212 | 213 | // Post the _rememberedFiles value 214 | $response = $this->call('POST', 'test-request', ['_rememberedFiles' => ['img' => rememberedFile('img')->getFilename()]], [], [], ['Accept' => 'application/json']); 215 | $content = json_decode($response->content()); 216 | 217 | $this->assertSame($file->getFilename(), $content->filename); 218 | $this->assertFileExists($content->pathname); 219 | $this->assertSame(sha1_file($file->getPathname()), sha1_file($content->pathname)); 220 | } 221 | 222 | /** 223 | * This tests to see if the middleware correctly captures the cached upload file from the 224 | * form data with the naming format _rememberedFiles[key]. 225 | * 226 | * It then goes to check that "refreshing" the page without any file upload will clear 227 | * the captured uploaded file. 228 | */ 229 | public function testSingleFileUploadOldRemembered() 230 | { 231 | /** @var Store $session */ 232 | $session = $this->app->make(Store::class); 233 | 234 | $remembered = $session->get('_remembered_files', []); 235 | $this->assertEquals([], $remembered); 236 | 237 | $file = $this->mockUploadedFile(__DIR__.DIRECTORY_SEPARATOR.'Stubs'.DIRECTORY_SEPARATOR.'test.jpg'); 238 | 239 | $response = $this->call('POST', 'test', [], [], ['img' => $file], ['Accept' => 'application/json']); 240 | $this->assertTrue($response->isOk()); 241 | $session->ageFlashData(); 242 | 243 | // "Refresh"... 244 | 245 | $response = $this->call('POST', 'test', ['_rememberedFiles' => ['img' => $file->getClientOriginalName()]], [], [], ['Accept' => 'application/json']); 246 | $this->assertTrue($response->isOk()); 247 | $session->ageFlashData(); 248 | 249 | $viewData = $this->mockView()->getData(); 250 | $this->assertArrayHasKey('rememberedFiles', $viewData); 251 | /** @var FileBag $remembered */ 252 | $remembered = $viewData['rememberedFiles']; 253 | $this->assertInstanceOf(FileBag::class, $remembered); 254 | $this->assertEquals(1, $remembered->count()); 255 | 256 | // "Refresh... 257 | 258 | $response = $this->call('POST', 'test', [], [], [], ['Accept' => 'application/json']); 259 | $this->assertTrue($response->isOk()); 260 | $session->ageFlashData(); 261 | 262 | $viewData = $this->mockView()->getData(); 263 | $this->assertArrayHasKey('rememberedFiles', $viewData); 264 | /** @var FileBag $remembered */ 265 | $remembered = $viewData['rememberedFiles']; 266 | $this->assertInstanceOf(FileBag::class, $remembered); 267 | $this->assertEquals(0, $remembered->count()); 268 | } 269 | 270 | /** 271 | * This test was written for issue #15. 272 | * @see https://github.com/photogabble/laravel-remember-uploads/issues/15 273 | */ 274 | public function testArrayFileUploadOldRemembered() 275 | { 276 | /** @var Store $session */ 277 | $session = $this->app->make(Store::class); 278 | 279 | $remembered = $session->get('_remembered_files', []); 280 | $this->assertEquals([], $remembered); 281 | 282 | $files = [ 283 | $this->mockUploadedFile(__DIR__.DIRECTORY_SEPARATOR.'Stubs'.DIRECTORY_SEPARATOR.'test.jpg'), 284 | $this->mockUploadedFile(__DIR__.DIRECTORY_SEPARATOR.'Stubs'.DIRECTORY_SEPARATOR.'test.jpg'), 285 | ]; 286 | 287 | $response = $this->call('POST', 'test', [], [], ['img' => $files], ['Accept' => 'application/json']); 288 | $this->assertTrue($response->isOk()); 289 | $session->ageFlashData(); 290 | 291 | // "Refresh"... 292 | 293 | $response = $this->call('POST', 'test', ['_rememberedFiles' => ['img' => [$files[0]->getClientOriginalName(), $files[1]->getClientOriginalName()]]], [], [], ['Accept' => 'application/json']); 294 | $this->assertTrue($response->isOk()); 295 | $session->ageFlashData(); 296 | 297 | $viewData = $this->mockView()->getData(); 298 | $this->assertArrayHasKey('rememberedFiles', $viewData); 299 | /** @var FileBag $remembered */ 300 | $remembered = $viewData['rememberedFiles']; 301 | $this->assertInstanceOf(FileBag::class, $remembered); 302 | $this->assertEquals(1, $remembered->count()); 303 | 304 | // "Refresh... 305 | 306 | $response = $this->call('POST', 'test', [], [], [], ['Accept' => 'application/json']); 307 | $this->assertTrue($response->isOk()); 308 | $session->ageFlashData(); 309 | 310 | $viewData = $this->mockView()->getData(); 311 | $this->assertArrayHasKey('rememberedFiles', $viewData); 312 | /** @var FileBag $remembered */ 313 | $remembered = $viewData['rememberedFiles']; 314 | $this->assertInstanceOf(FileBag::class, $remembered); 315 | $this->assertEquals(0, $remembered->count()); 316 | } 317 | 318 | /** 319 | * Test the rememberedFile helper. 320 | */ 321 | public function testHelper() 322 | { 323 | /** @var Store $session */ 324 | $session = $this->app->make(Store::class); 325 | 326 | $remembered = $session->get('_remembered_files', []); 327 | $this->assertEquals([], $remembered); 328 | 329 | $file = $this->mockUploadedFile(__DIR__.DIRECTORY_SEPARATOR.'Stubs'.DIRECTORY_SEPARATOR.'test.jpg'); 330 | 331 | $response = $this->call('POST', 'test', [], [], ['img' => $file], ['Accept' => 'application/json']); 332 | $this->assertTrue($response->isOk()); 333 | $session->ageFlashData(); 334 | 335 | $fileBag = rememberedFile(); 336 | $this->assertInstanceOf(FileBag::class, $fileBag); 337 | $this->assertInstanceOf(\Symfony\Component\HttpFoundation\File\UploadedFile::class, $fileBag->get('img')); 338 | 339 | $rememberedFile = rememberedFile('img'); 340 | $this->assertInstanceOf(\Symfony\Component\HttpFoundation\File\UploadedFile::class, $rememberedFile); 341 | 342 | $this->assertNull(rememberedFile('test')); 343 | $this->assertTrue(rememberedFile('test', true)); 344 | $this->assertFalse(rememberedFile('test', false)); 345 | } 346 | 347 | /** 348 | * Test the rememberedFile helper. 349 | * This test was extended for issue #15. 350 | * @see https://github.com/photogabble/laravel-remember-uploads/issues/15 351 | */ 352 | public function testHelperWithArray() 353 | { 354 | /** @var Store $session */ 355 | $session = $this->app->make(Store::class); 356 | 357 | $remembered = $session->get('_remembered_files', []); 358 | $this->assertEquals([], $remembered); 359 | 360 | $files = [ 361 | $this->mockUploadedFile(__DIR__.DIRECTORY_SEPARATOR.'Stubs'.DIRECTORY_SEPARATOR.'test.jpg'), 362 | $this->mockUploadedFile(__DIR__.DIRECTORY_SEPARATOR.'Stubs'.DIRECTORY_SEPARATOR.'test.jpg'), 363 | ]; 364 | 365 | $response = $this->call('POST', 'test', [], [], ['img' => $files], ['Accept' => 'application/json']); 366 | $this->assertTrue($response->isOk()); 367 | $session->ageFlashData(); 368 | 369 | $fileBag = rememberedFile(); 370 | $this->assertInstanceOf(FileBag::class, $fileBag); 371 | } 372 | 373 | /** 374 | * Test written for issue #2. 375 | * Tests to check that validation being recommended in the README actually works. 376 | * @see https://github.com/photogabble/laravel-remember-uploads/issues/2 377 | */ 378 | public function testSingleFileValidationPasses() 379 | { 380 | /** 381 | * @var \Illuminate\Routing\Router $router 382 | */ 383 | $router = $this->app->make('router'); 384 | $router->post( 385 | 'test-validation', 386 | [ 387 | 'middleware' => ['remember.files'], 388 | 'uses' => '\Photogabble\LaravelRememberUploads\Tests\Stubs\ValidationTestController@fileUpload' 389 | ] 390 | ); 391 | 392 | /** @var Store $session */ 393 | $session = $this->app->make(Store::class); 394 | 395 | // Test controller validation is working. 396 | $response = $this->call('POST', 'test-validation', [], [], [], ['Accept' => 'application/json']); 397 | $this->assertFalse($response->isOk()); 398 | 399 | // Test controller based rememberedFile is working. 400 | $file = $this->mockUploadedFile(__DIR__.DIRECTORY_SEPARATOR.'Stubs'.DIRECTORY_SEPARATOR.'test.jpg'); 401 | $response = $this->call('POST', 'test-validation', [], [], ['img' => $file], ['Accept' => 'application/json']); 402 | $this->assertTrue($response->isOk()); 403 | $content = json_decode($response->getContent()); 404 | $this->assertEquals($file->getClientOriginalName(), $content->name); 405 | 406 | $session->ageFlashData(); 407 | $session->flush(); 408 | 409 | // Test controller _rememberedFiles is working. 410 | $response = $this->call('POST', 'test-validation', ['_rememberedFiles'=> ['img' => $file->getClientOriginalName()]], [], [], ['Accept' => 'application/json']); 411 | $this->assertTrue($response->isOk()); 412 | $content = json_decode($response->getContent()); 413 | $this->assertEquals($file->getClientOriginalName(), $content->name); 414 | } 415 | 416 | public function testArrayFileValidationPasses() 417 | { 418 | /** 419 | * @var \Illuminate\Routing\Router $router 420 | */ 421 | $router = $this->app->make('router'); 422 | $router->post( 423 | 'test-validation', 424 | [ 425 | 'middleware' => ['remember.files'], 426 | 'uses' => '\Photogabble\LaravelRememberUploads\Tests\Stubs\ValidationTestController@arrayFileUpload' 427 | ] 428 | ); 429 | 430 | /** @var Store $session */ 431 | $session = $this->app->make(Store::class); 432 | 433 | // Test controller validation is working. 434 | $response = $this->call('POST', 'test-validation', [], [], [], ['Accept' => 'application/json']); 435 | $this->assertFalse(is_null($response->exception)); 436 | $this->assertEquals('The given data failed to pass validation.', $response->exception->getMessage()); 437 | $this->assertFalse($response->isOk()); 438 | 439 | // Test controller based rememberedFile is working. 440 | $files = [ 441 | $this->mockUploadedFile(__DIR__.DIRECTORY_SEPARATOR.'Stubs'.DIRECTORY_SEPARATOR.'test.jpg'), 442 | $this->mockUploadedFile(__DIR__.DIRECTORY_SEPARATOR.'Stubs'.DIRECTORY_SEPARATOR.'test.jpg'), 443 | ]; 444 | 445 | $response = $this->call('POST', 'test-validation', [], [], ['img' => $files], ['Accept' => 'application/json']); 446 | $this->assertTrue(is_null($response->exception)); 447 | $this->assertTrue($response->isOk()); 448 | 449 | $content = json_decode($response->getContent()); 450 | $this->assertEquals($files[0]->getClientOriginalName(), $content->name_0); 451 | $this->assertEquals($files[1]->getClientOriginalName(), $content->name_1); 452 | 453 | $session->ageFlashData(); 454 | $session->flush(); 455 | 456 | // Test controller _rememberedFiles is working. 457 | $response = $this->call('POST', 'test-validation', ['_rememberedFiles'=> ['img' => [$files[0]->getClientOriginalName(), $files[1]->getClientOriginalName()]]], [], [], ['Accept' => 'application/json']); 458 | $this->assertTrue($response->isOk()); 459 | $content = json_decode($response->getContent()); 460 | $this->assertEquals($files[0]->getClientOriginalName(), $content->name_0); 461 | $this->assertEquals($files[1]->getClientOriginalName(), $content->name_1); 462 | } 463 | 464 | /** 465 | * Test written for issue #2 466 | * @see https://github.com/photogabble/laravel-remember-uploads/issues/2 467 | */ 468 | public function testFilesForgottenWhenValidationFails() 469 | { 470 | /** 471 | * @var \Illuminate\Routing\Router $router 472 | */ 473 | $router = $this->app->make('router'); 474 | $router->post( 475 | 'test-validation', 476 | [ 477 | 'middleware' => ['remember.files'], 478 | 'uses' => '\Photogabble\LaravelRememberUploads\Tests\Stubs\ValidationTestController@failedFileUpload' 479 | ] 480 | ); 481 | 482 | $file = $this->mockUploadedFile(__DIR__.DIRECTORY_SEPARATOR.'Stubs'.DIRECTORY_SEPARATOR.'test.jpg'); 483 | $response = $this->call('POST', 'test-validation', [], [], ['img' => $file], ['Accept' => 'application/json']); 484 | $this->assertFalse($response->isOk()); 485 | 486 | $remembered = rememberedFile('img'); 487 | $this->assertNull($remembered); 488 | } 489 | 490 | /** 491 | * Test written for issue #4. 492 | * Tests the clearRememberedFiles helper function. 493 | * @see https://github.com/photogabble/laravel-remember-uploads/issues/4 494 | */ 495 | public function testClearRememberedFilesHelperFunction() 496 | { 497 | /** @var Store $session */ 498 | $session = $this->app->make(Store::class); 499 | 500 | $remembered = $session->get('_remembered_files', []); 501 | $this->assertEquals([], $remembered); 502 | 503 | $file = $this->mockUploadedFile(__DIR__.DIRECTORY_SEPARATOR.'Stubs'.DIRECTORY_SEPARATOR.'test.jpg'); 504 | 505 | $response = $this->call('POST', 'test', [], [], ['img' => $file], ['Accept' => 'application/json']); 506 | $this->assertTrue($response->isOk()); 507 | $session->ageFlashData(); 508 | 509 | /** @var RememberedFileBag $remembered */ 510 | $remembered = $session->get('_remembered_files', new RememberedFileBag()); 511 | $this->assertTrue($remembered->has(('img'))); 512 | 513 | clearRememberedFiles(); 514 | 515 | /** @var RememberedFileBag $remembered */ 516 | $remembered = $session->get('_remembered_files', new RememberedFileBag()); 517 | $this->assertFalse($remembered->has(('img'))); 518 | } 519 | 520 | 521 | /** 522 | * Test written for issue #15 523 | * Regression of single file upload found during 1.3.0-alpha 524 | * testing by Jarosław Goszowski. 525 | * @see https://github.com/photogabble/laravel-remember-uploads/issues/15 526 | * @throws \Exception 527 | */ 528 | public function testSingleFileRegressionTest() 529 | { 530 | /** 531 | * @var \Illuminate\Routing\Router $router 532 | */ 533 | $router = $this->app->make('router'); 534 | $router->post( 535 | 'test-controller', 536 | [ 537 | 'middleware' => ['remember.files'], 538 | 'uses' => '\Photogabble\LaravelRememberUploads\Tests\Stubs\TestController@store' 539 | ] 540 | ); 541 | 542 | $router->get('single-file-test', [ 543 | function(){ 544 | $files = rememberedFile(); 545 | $this->assertEquals(1, $files->count()); 546 | return ''; 547 | } 548 | ]); 549 | 550 | $file = $this->mockUploadedFile(__DIR__.DIRECTORY_SEPARATOR.'Stubs'.DIRECTORY_SEPARATOR.'test.jpg'); 551 | $response = $this->call('POST', 'test-controller', [], [], ['image' => $file], ['Accept' => 'application/json']); 552 | 553 | $this->assertTrue($response->isRedirection()); 554 | 555 | $response = $this->call('GET', 'single-file-test', [], [], [], ['Accept' => 'application/json']); 556 | 557 | if ($response->exception){ 558 | throw $response->exception; 559 | } 560 | 561 | $this->assertTrue($response->isOk()); 562 | } 563 | 564 | /** 565 | * Mock an uploaded file from a given src file. 566 | * 567 | * @param string $stub 568 | * @return UploadedFile 569 | */ 570 | private function mockUploadedFile($stub) { 571 | $name = str_random(8).'.jpg'; 572 | $path = sys_get_temp_dir().DIRECTORY_SEPARATOR.$name; 573 | 574 | copy($stub, $path); 575 | return new UploadedFile($path, $name, filesize($path), 'image/jpeg', null, true); 576 | } 577 | 578 | private function mockView() 579 | { 580 | /** @var Factory $factory */ 581 | $factory = app(Factory::class); 582 | 583 | /** @var View $mockView */ 584 | $mockView = $factory->file(__DIR__ . DIRECTORY_SEPARATOR . 'test.blade.php'); 585 | 586 | /** @var RememberedFilesComposer $mockComposer */ 587 | $mockComposer = $this->app->make(RememberedFilesComposer::class); 588 | 589 | $mockComposer->compose($mockView); 590 | return $mockView; 591 | } 592 | 593 | } --------------------------------------------------------------------------------