├── .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 |
6 |
7 |
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 | }
--------------------------------------------------------------------------------