├── .gitattributes ├── .gitignore ├── .travis.yml ├── README.md ├── composer.json ├── config └── plupload.php ├── phpunit.xml ├── resources ├── assets │ └── js │ │ └── upload.js ├── lang │ └── en │ │ └── ui.php └── views │ └── uploader.blade.php ├── src ├── Contracts │ └── Plupload.php ├── Exception.php ├── Facades │ └── Plupload.php ├── File.php ├── Html.php ├── Plupload.php ├── PluploadServiceProvider.php └── helpers.php └── tests └── .gitkeep /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.phar 3 | composer.lock 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.4 5 | - 5.5 6 | - 5.6 7 | - hhvm 8 | 9 | before_script: 10 | - travis_retry composer self-update 11 | - travis_retry composer install --prefer-source --no-interaction --dev 12 | 13 | script: phpunit 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Laravel 5 Plupload 3 | 4 | [![Latest Stable Version](https://poser.pugx.org/jenky/laravel-plupload/v/stable.svg)](https://packagist.org/packages/jenky/laravel-plupload) 5 | [![Total Downloads](https://poser.pugx.org/jenky/laravel-plupload/d/total.svg)](https://packagist.org/packages/jenky/laravel-plupload) 6 | [![License](https://poser.pugx.org/jenky/laravel-plupload/license.svg)](https://packagist.org/packages/jenky/laravel-plupload) 7 | 8 | ##### Laravel package for Plupload http://plupload.com. 9 | This package uses some parts of https://github.com/jildertmiedema/laravel-plupload 10 | 11 | ## Installation 12 | Require this package with composer: 13 | 14 | ``` 15 | composer require jenky/laravel-plupload 16 | ``` 17 | 18 | Laravel 5.5+ uses Package Auto-Discovery, so doesn't require you to manually add the ServiceProvider. 19 | 20 | **For Laravel 5.4 or older** 21 | 22 | Add the ServiceProvider to the providers array in `config/app.php` 23 | 24 | ```php 25 | Jenky\LaravelPlupload\PluploadServiceProvider::class, 26 | ``` 27 | 28 | and add this to your facades in `config/app.php`: 29 | 30 | ```php 31 | 'Plupload' => Jenky\LaravelPlupload\Facades\Plupload::class, 32 | ``` 33 | 34 | Copy the package config to your local config with the publish command: 35 | 36 | ``` 37 | php artisan vendor:publish 38 | ``` 39 | or 40 | ``` 41 | php artisan vendor:publish --provider="Jenky\LaravelPlupload\PluploadServiceProvider" 42 | ``` 43 | 44 | 45 | ## Usage 46 | 47 | 48 | ### Uploading files 49 | ##### 1. Use default plupload html 50 | 51 | Use the [examples](http://www.plupload.com/examples) found on the plupload site. The [Getting Started](http://plupload.com/docs/Getting-Started) page is good place to start. 52 | 53 | 54 | ##### 2. Plupload builder 55 | 56 | **make($id, $url)** 57 | 58 | Create new uploader. 59 | * **$id**: the unique identification for the uploader. 60 | * **$url**: the upload url end point. 61 | ```php 62 | {!! Plupload::make('my_uploader_id', route('photos.store'))->render() !!} 63 | ``` 64 | or use the helper 65 | ```php 66 | {!! plupload()->make('my_uploader_id', route('photos.store')) !!} 67 | // or even shorter 68 | {!! plupload('my_uploader_id', route('photos.store')) !!} 69 | ``` 70 | 71 | **render($view = 'plupload::uploader', array $data = [])** 72 | 73 | Renders the uploader. You can customize this by passing a view name and it's data. From version `2.0`, you can omit the `render` method in the builder if you don't want to set the view or extra data. 74 | 75 | ##### 3. Use package js file to initialize Plupload (Optional) 76 | 77 | If you do not want to write your own js to initialize Plupload, you can use the `upload.js` file that included with the package in `resources/views/vendor/plupload/assets/js`. Make sure that you already have `jQuery` loaded on your page. 78 | 79 | **Initialize Plupload** 80 | 81 | ```js 82 | 87 | ``` 88 | 89 | 90 | These following methods are useable with the `upload.js` file. 91 | 92 | **Set Uploader options** 93 | 94 | **setOptions(array $options)** 95 | 96 | Set uploader options. Please visit https://github.com/moxiecode/plupload/wiki/Options to see all the options. You can set the default global options in `config/plupload.php` 97 | 98 | ```php 99 | {!! plupload('my_uploader_id', route('photos.store')) 100 | ->setOptions([ 101 | 'filters' => [ 102 | 'max_file_size' => '2mb', 103 | 'mime_types' => [ 104 | ['title' => 'Image files', 'extensions' => 'jpg,gif,png'], 105 | ], 106 | ], 107 | ]) !!} 108 | ``` 109 | 110 | **Automatically start upload when files added** 111 | 112 | Use `setAutoStart()` in your builder before calling render() function. 113 | 114 | **setAutoStart($bool)** 115 | 116 | * **$bool**: `true` or `false` 117 | 118 | ```php 119 | {!! plupload('my_uploader_id', route('photos.store'))->setAutoStart(true) !!} 120 | ``` 121 | 122 | 123 | ### Receiving files 124 | 125 | 126 | **file($name, $handler)** 127 | * **$name**: the input name. 128 | * **$handler**: callback handler. 129 | 130 | Use this in your route or your controller. Feel free to modify to suit your needs. 131 | 132 | ```php 133 | return Plupload::file('file', function($file) { 134 | // Store the uploaded file using storage disk 135 | $path = Storage::disk('local')->putFile('photos', $file); 136 | 137 | // Save the record to the db 138 | $photo = App\Photo::create([ 139 | 'name' => $file->getClientOriginalName(), 140 | 'type' => 'image', 141 | // ... 142 | ]); 143 | 144 | // This will be included in JSON response result 145 | return [ 146 | 'success' => true, 147 | 'message' => 'Upload successful.', 148 | 'id' => $photo->id, 149 | // 'url' => $photo->getImageUrl($filename, 'medium'), 150 | // 'deleteUrl' => route('photos.destroy', $photo) 151 | // ... 152 | ]; 153 | }); 154 | ``` 155 | Helper is also available 156 | ```php 157 | return plupload()->file('file', function($file) { 158 | 159 | }); 160 | ``` 161 | 162 | 163 | If you are using the package `upload.js` file. The `url` and `deleteUrl` in the JSON payload will be used to generate preview and delete link while the `id` will be appended to the uploader as a hidden field with the following format: 164 | 165 | ``. 166 | 167 | Please note that the `deleteUrl` uses `DELETE` method. 168 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jenky/laravel-plupload", 3 | "description": "Plupload package for Laravel 5", 4 | "keywords": [ 5 | "laravel", 6 | "plupload" 7 | ], 8 | "license": "MIT", 9 | "authors": [ 10 | { 11 | "name": "Linh Tran", 12 | "email": "jenky.w0w@gmail.com" 13 | } 14 | ], 15 | "require": { 16 | "php": ">=5.5.9", 17 | "illuminate/contracts": "^5.2", 18 | "illuminate/support": "^5.2" 19 | }, 20 | "autoload": { 21 | "psr-4": { 22 | "Jenky\\LaravelPlupload\\": "src/" 23 | }, 24 | "files": [ 25 | "src/helpers.php" 26 | ] 27 | }, 28 | "extra": { 29 | "branch-alias": { 30 | "dev-master": "2.0-dev", 31 | "dev-develop": "3.0-dev" 32 | }, 33 | "laravel": { 34 | "providers": [ 35 | "Jenky\\LaravelPlupload\\PluploadServiceProvider" 36 | ], 37 | "aliases": { 38 | "Plupload": "Jenky\\LaravelPlupload\\Facades\\Plupload" 39 | } 40 | } 41 | }, 42 | "config": { 43 | "sort-packages": true 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /config/plupload.php: -------------------------------------------------------------------------------- 1 | storage_path('plupload'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Plupload Global Options 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Set default global options for Plupload. 24 | | 25 | | See https://github.com/moxiecode/plupload/wiki/Options 26 | | 27 | */ 28 | 29 | 'global' => [ 30 | 'flash_swf_url' => '/js/Moxie.swf', 31 | 'silverlight_xap_url' => '/js/Moxie.xap', 32 | ], 33 | 34 | ]; 35 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests/ 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /resources/assets/js/upload.js: -------------------------------------------------------------------------------- 1 | function createUploader(uploaderId) 2 | { 3 | var $uploader = $('#uploader-' + uploaderId); 4 | 5 | if (!$uploader || !$uploader.length) { 6 | alert('Cannot find uploader'); 7 | } 8 | 9 | var $filelist = $uploader.find('.filelist'), 10 | $uploaded = $uploader.find('.uploaded'), 11 | $uploadAction = $uploader.find('.upload-actions'), 12 | $uploadBtn = $('#' + $uploader.data('uploadbtn')) || false, 13 | options = $uploader.data('options') || {}, 14 | autoStart = $uploader.data('autostart') || false, 15 | deleteUrl = $uploader.data('deleteurl') || false, 16 | deleteMethod = $uploader.data('deletemethod') || 'DELETE'; 17 | 18 | defaultOptions = { 19 | init: { 20 | PostInit: function(up) { 21 | if (!autoStart && $uploadBtn) { 22 | $uploadBtn.click(function() { 23 | up.start(); 24 | return false; 25 | }); 26 | } 27 | }, 28 | 29 | FilesAdded: function(up, files) { 30 | $.each(files, function(i, file){ 31 | $filelist.append( 32 | '
' + 33 | '
' + file.name + ' (' + plupload.formatSize(file.size) + ')
' + 34 | '
'); 35 | 36 | $filelist.on('click', '#' + file.id + ' button.cancelUpload', function() { 37 | var $this = $(this), 38 | $file = $('#' + file.id), 39 | deleteUrl = $this.data('deleteurl') || false, 40 | id = $this.data('id') || false; 41 | 42 | if (deleteUrl) { 43 | $.ajax({ 44 | dataType: 'json', 45 | type: deleteMethod, 46 | url: deleteUrl, 47 | data: options.multipart_params, 48 | success: function(result) { 49 | if (result.success) { 50 | up.removeFile(file); 51 | $file.remove(); 52 | $('#' + file.id + '-hidden').remove(); 53 | $uploadAction.show(); 54 | } 55 | else { 56 | $('#' + file.id).append('' + result.message + ''); 57 | } 58 | } 59 | }); 60 | } 61 | else { 62 | $uploadAction.show(); 63 | $file.remove(); 64 | $('#' + file.id + '-hidden').remove(); 65 | up.removeFile(file); 66 | } 67 | }); 68 | }); 69 | up.refresh(); // Reposition Flash/Silverlight 70 | if (autoStart) { 71 | $uploadAction.hide(); 72 | up.start(); 73 | } 74 | }, 75 | 76 | UploadProgress: function(up, file) { 77 | $uploadAction.hide(); 78 | $('#' + file.id + ' .progress').addClass('active'); 79 | $('#' + file.id + ' button.cancelUpload').hide(); 80 | $('#' + file.id + ' .progress .progress-bar').animate({width: file.percent + '%'}, 100, 'linear'); 81 | }, 82 | 83 | Error: function(up, err) { 84 | $filelist.append('
' + 85 | 'Error: ' + err.code + ', Message: ' + err.message + 86 | (err.file ? ', File: ' + err.file.name : '') + 87 | "
" 88 | ); 89 | up.refresh(); // Reposition Flash/Silverlight 90 | }, 91 | 92 | FileUploaded: function(up, file, info) { 93 | var response = JSON.parse(info.response); 94 | 95 | $('#' + file.id + ' .progress .progress-bar').animate({width: '100%'}, 100, 'linear'); 96 | $('#' + file.id + ' .progress').removeClass('progress-striped').removeClass('active').fadeOut(); 97 | $('#' + file.id + ' .filename').removeClass('hide').show(); 98 | $('#' + file.id + ' button.cancelUpload').show(); 99 | 100 | if (response.result.id) { 101 | $('#' + file.id + ' button.cancelUpload').attr('data-id', response.result.id); 102 | $('').appendTo($uploader); 103 | } 104 | 105 | if (response.result.deleteUrl) { 106 | $('#' + file.id + ' button.cancelUpload').attr('data-deleteurl', response.result.deleteUrl); 107 | } 108 | 109 | if (response.result.url) { 110 | $('#' + file.id).append(''); 111 | } 112 | 113 | } 114 | } 115 | }; 116 | 117 | $.extend(options, defaultOptions); 118 | 119 | var uploader = new plupload.Uploader(options); 120 | uploader.init(); 121 | } 122 | -------------------------------------------------------------------------------- /resources/lang/en/ui.php: -------------------------------------------------------------------------------- 1 | 'Browse', 16 | 'upload' => 'Upload', 17 | 18 | ]; 19 | -------------------------------------------------------------------------------- /resources/views/uploader.blade.php: -------------------------------------------------------------------------------- 1 | @if (!empty($options['url'])) 2 |
6 |
7 |
8 | 18 |
19 |
20 |
21 | @else 22 | Missing URL option. 23 | @endif 24 | -------------------------------------------------------------------------------- /src/Contracts/Plupload.php: -------------------------------------------------------------------------------- 1 | request = $request; 37 | $this->storage = $file; 38 | } 39 | 40 | /** 41 | * Get chuck upload path. 42 | * 43 | * @return string 44 | */ 45 | public function getChunkPath() 46 | { 47 | $path = config('plupload.chunk_path'); 48 | 49 | if (! $this->storage->isDirectory($path)) { 50 | $this->storage->makeDirectory($path, 0777, true); 51 | } 52 | 53 | return $path; 54 | } 55 | 56 | /** 57 | * Process uploaded files. 58 | * 59 | * @param string $name 60 | * @param \Closure $closure 61 | * @return array 62 | */ 63 | public function process($name, Closure $closure) 64 | { 65 | $response = []; 66 | $response['jsonrpc'] = '2.0'; 67 | 68 | if ($this->hasChunks()) { 69 | $result = $this->chunks($name, $closure); 70 | } else { 71 | $result = $this->single($name, $closure); 72 | } 73 | 74 | $response['result'] = $result; 75 | 76 | return $response; 77 | } 78 | 79 | /** 80 | * Handle single uploaded file. 81 | * 82 | * @param string $name 83 | * @param \Closure $closure 84 | * @return void 85 | */ 86 | public function single($name, Closure $closure) 87 | { 88 | if ($this->request->hasFile($name)) { 89 | return $closure($this->request->file($name)); 90 | } 91 | } 92 | 93 | /** 94 | * Handle single uploaded file. 95 | * 96 | * @param string $name 97 | * @param \Closure $closure 98 | * @return mixed 99 | */ 100 | public function chunks($name, Closure $closure) 101 | { 102 | if (! $this->request->hasFile($name)) { 103 | return; 104 | } 105 | 106 | $file = $this->request->file($name); 107 | $chunk = (int) $this->request->input('chunk', false); 108 | $chunks = (int) $this->request->input('chunks', false); 109 | $originalName = $this->request->input('name'); 110 | 111 | $filePath = $this->getChunkPath().'/'.$originalName.'.part'; 112 | 113 | $this->removeOldData($filePath); 114 | $this->appendData($filePath, $file); 115 | 116 | if ($chunk == $chunks - 1) { 117 | $file = new UploadedFile($filePath, $originalName, 'blob', UPLOAD_ERR_OK, true); 118 | @unlink($filePath); 119 | 120 | return $closure($file); 121 | } 122 | } 123 | 124 | /** 125 | * Remove old chunks. 126 | * 127 | * @param string $filePath 128 | * @return void 129 | */ 130 | protected function removeOldData($filePath) 131 | { 132 | if ($this->storage->exists($filePath) && ($this->storage->lastModified($filePath) < time() - $this->maxFileAge)) { 133 | $this->storage->delete($filePath); 134 | } 135 | } 136 | 137 | /** 138 | * Merge chunks. 139 | * 140 | * @param string $filePathPartial 141 | * @param \Illuminate\Http\UploadedFile $file 142 | * @return void 143 | */ 144 | protected function appendData($filePathPartial, UploadedFile $file) 145 | { 146 | if (! $out = @fopen($filePathPartial, 'ab')) { 147 | throw new Exception('Failed to open output stream.', 102); 148 | } 149 | 150 | if (! $in = @fopen($file->getPathname(), 'rb')) { 151 | throw new Exception('Failed to open input stream', 101); 152 | } 153 | 154 | while ($buff = fread($in, 4096)) { 155 | fwrite($out, $buff); 156 | } 157 | 158 | @fclose($out); 159 | @fclose($in); 160 | } 161 | 162 | /** 163 | * Check if request has chunks. 164 | * 165 | * @return bool 166 | */ 167 | public function hasChunks() 168 | { 169 | return (bool) $this->request->input('chunks', false); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/Html.php: -------------------------------------------------------------------------------- 1 | id = $id; 37 | $this->options = config('plupload.global', []); 38 | $this->options['url'] = $url; 39 | } 40 | 41 | /** 42 | * Initialize the options. 43 | * 44 | * @return void 45 | */ 46 | protected function init() 47 | { 48 | if (empty($this->options['url'])) { 49 | throw new Exception('Missing URL option.'); 50 | } 51 | 52 | $options = $this->options; 53 | $id = $this->id; 54 | $autoStart = $this->autoStart; 55 | 56 | // csrf token 57 | $options['multipart_params']['_token'] = csrf_token(); 58 | $options['browse_button'] = 'uploader-'.$this->id.'-pickfiles'; 59 | $options['container'] = 'uploader-'.$this->id.'-container'; 60 | 61 | $this->data = array_merge($this->data, compact('options', 'id', 'autoStart')); 62 | } 63 | 64 | /** 65 | * Set uploader auto start. 66 | * 67 | * @param bool $bool 68 | * @return void 69 | */ 70 | public function setAutoStart($bool) 71 | { 72 | $this->autoStart = (bool) $bool; 73 | 74 | return $this; 75 | } 76 | 77 | /** 78 | * Set uploader options. 79 | * 80 | * @see https://github.com/moxiecode/plupload/wiki/Options 81 | * @param array $options 82 | * @return $this 83 | */ 84 | public function setOptions(array $options) 85 | { 86 | $this->options = array_merge($this->options, array_except($options, ['url'])); 87 | 88 | return $this; 89 | } 90 | 91 | /** 92 | * Set uploader custom params. 93 | * 94 | * @param array $params 95 | * @return void 96 | */ 97 | public function setCustomParams(array $params) 98 | { 99 | $this->data['params'] = $params; 100 | 101 | return $this; 102 | } 103 | 104 | /** 105 | * Render the upload handler buttons. 106 | * 107 | * @param string $view 108 | * @param array $data 109 | * @return \Illuminate\View\View 110 | */ 111 | public function render($view = 'plupload::uploader', array $data = []) 112 | { 113 | $this->init(); 114 | 115 | return view($view, array_merge($this->data, $data)); 116 | } 117 | 118 | /** 119 | * Get the string contents of the view. 120 | * 121 | * @return string 122 | */ 123 | public function __toString() 124 | { 125 | return (string) $this->render(); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/Plupload.php: -------------------------------------------------------------------------------- 1 | app = $app; 25 | } 26 | 27 | /** 28 | * File upload handler. 29 | * 30 | * @param string $name 31 | * @param closure $closure 32 | * @return void 33 | */ 34 | public function file($name, Closure $closure) 35 | { 36 | $fileHandler = $this->app->make(File::class); 37 | 38 | return $fileHandler->process($name, $closure); 39 | } 40 | 41 | /** 42 | * Html template handler. 43 | * 44 | * @param string $id 45 | * @param string $url 46 | * @return \Jenky\LaravelPlupload\Html 47 | */ 48 | public function make($id, $url) 49 | { 50 | return $this->app->make(Html::class, compact('id', 'url')); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/PluploadServiceProvider.php: -------------------------------------------------------------------------------- 1 | registerPlupload(); 25 | } 26 | 27 | /** 28 | * Bootstrap the application events. 29 | * 30 | * @return void 31 | */ 32 | public function boot() 33 | { 34 | $this->setupConfig(); 35 | } 36 | 37 | /** 38 | * Setup the config. 39 | * 40 | * @return void 41 | */ 42 | protected function setupConfig() 43 | { 44 | $configPath = __DIR__.'/../config/plupload.php'; 45 | $viewsPath = __DIR__.'/../resources/views'; 46 | $assetsPath = __DIR__.'/../resources/assets'; 47 | $translationsPath = __DIR__.'/../resources/lang'; 48 | 49 | $this->mergeConfigFrom($configPath, 'plupload'); 50 | $this->loadViewsFrom($viewsPath, 'plupload'); 51 | $this->loadTranslationsFrom($translationsPath, 'plupload'); 52 | 53 | $this->publishes([$configPath => config_path('plupload.php')], 'config'); 54 | $this->publishes([ 55 | $viewsPath => base_path('resources/views/vendor/plupload'), 56 | $assetsPath.'/js' => base_path('resources/assets/plupload'), 57 | $translationsPath => base_path('resources/lang/vendor/plupload'), 58 | ]); 59 | } 60 | 61 | /** 62 | * Register the plupload class. 63 | * 64 | * @return void 65 | */ 66 | protected function registerPlupload() 67 | { 68 | $this->app->singleton(PluploadContract::class, function ($app) { 69 | return new Plupload($app); 70 | }); 71 | 72 | $this->app->alias(PluploadContract::class, 'plupload'); 73 | } 74 | 75 | /** 76 | * Get the services provided by the provider. 77 | * 78 | * @return array 79 | */ 80 | public function provides() 81 | { 82 | return [PluploadContract::class, 'plupload']; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/helpers.php: -------------------------------------------------------------------------------- 1 | make($id, $url); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /tests/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenky/laravel-plupload/19123d6ede6204fea6efdc69286435674c0e3fee/tests/.gitkeep --------------------------------------------------------------------------------