diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5826402
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+/vendor
+composer.phar
+composer.lock
+.DS_Store
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..f60bbe0
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,13 @@
+language: php
+
+php:
+ - 5.4
+ - 5.5
+ - 5.6
+ - hhvm
+
+before_script:
+ - travis_retry composer self-update
+ - travis_retry composer install --prefer-source --no-interaction --dev
+
+script: phpunit
diff --git a/assets/js/upload.js b/assets/js/upload.js
new file mode 100644
index 0000000..8099861
--- /dev/null
+++ b/assets/js/upload.js
@@ -0,0 +1,80 @@
+function createUploader(uploader)
+{
+ var $uploader = $('#uploader-' + uploader);
+
+ if (!$uploader || !$uploader.length) {
+ alert('Cannot find uploader');
+ }
+
+ var $filelist = $uploader.find('.filelist'),
+ $uploaded = $uploader.find('.uploaded'),
+ $uploadAction = $uploader.find('.upload-actions'),
+ $uploadBtn = $('#' + $uploader.data('uploadbtn')) || false,
+ options = $uploader.data('options') || {},
+ autoStart = $uploader.data('autostart') || false;
+
+ defaultOptions = {
+ init: {
+ PostInit: function(up) {
+ if (!autoStart && $uploadBtn) {
+ $uploadBtn.click(function() {
+ $uploadAction.hide();
+ up.start();
+ return false;
+ });
+ }
+ },
+
+ FilesAdded: function(up, files) {
+ // $filelist.find('.alert-file button.close').trigger('click'); //limit uploading to 1
+ // $uploaded.html('');
+ // $uploadAction.hide();
+ $.each(files, function(i, file){
+ $filelist.append(
+ '
' +
+ '
' + file.name + ' (' + plupload.formatSize(file.size) + ') ' +
+ '
');
+
+ $filelist.on('click', '#' + file.id + ' button.cancelUpload', function(){
+ $uploadAction.show();
+ });
+ });
+ up.refresh(); // Reposition Flash/Silverlight
+ if (autoStart) {
+ $uploadAction.hide();
+ up.start();
+ }
+ },
+
+ UploadProgress: function(up, file) {
+ //if(!$('#' + file.id + ' .progress').hasClass('progress-striped')){
+ $('#' + file.id + ' .progress').addClass('active');
+ $('#' + file.id + ' button.cancelUpload').hide();
+ //}
+ $('#' + file.id + ' .progress .progress-bar').animate({width: file.percent + '%'}, 100, 'linear');
+ },
+
+ Error: function(up, err) {
+ $filelist.append('' +
+ 'Error: ' + err.code + ', Message: ' + err.message +
+ (err.file ? ', File: ' + err.file.name : '') +
+ "
"
+ );
+ up.refresh(); // Reposition Flash/Silverlight
+ },
+
+ FileUploaded: function(up, file, info) {
+ var response = JSON.parse(info.response);
+ $('#' + file.id + ' .progress .progress-bar').animate({width: '100%'}, 100, 'linear');
+ $('#' + file.id + ' .progress').removeClass('progress-striped').removeClass('active').fadeOut();
+ $('#' + file.id + ' .filename').removeClass('hide').show();
+ $('#' + file.id + ' button.cancelUpload').attr('data-id', response.id).show();
+ }
+ }
+ };
+
+ $.extend(options, defaultOptions);
+
+ var uploader = new plupload.Uploader(options);
+ uploader.init();
+}
\ No newline at end of file
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..7b767b3
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,20 @@
+{
+ "name": "jenky/laravel-plupload",
+ "description": "",
+ "authors": [
+ {
+ "name": "Linh Tran",
+ "email": "jenky.w0w@gmail.com"
+ }
+ ],
+ "require": {
+ "php": ">=5.4.0",
+ "illuminate/support": "5.0.*"
+ },
+ "autoload": {
+ "psr-4": {
+ "Jenky\\LaravelPlupload": "src/"
+ }
+ },
+ "minimum-stability": "stable"
+}
diff --git a/config/plupload.php b/config/plupload.php
new file mode 100644
index 0000000..501bd69
--- /dev/null
+++ b/config/plupload.php
@@ -0,0 +1,8 @@
+ storage_path('plupload'),
+
+ 'flash_swf_url' => asset("assets/plupload/js/Moxie.swf"),
+ 'silverlight_xap_url' => asset("assets/plupload/js/Moxie.xap"),
+];
\ No newline at end of file
diff --git a/phpunit.xml b/phpunit.xml
new file mode 100644
index 0000000..3347b75
--- /dev/null
+++ b/phpunit.xml
@@ -0,0 +1,18 @@
+
+
+
+
+ ./tests/
+
+
+
diff --git a/src/Exception.php b/src/Exception.php
new file mode 100644
index 0000000..d15e6c4
--- /dev/null
+++ b/src/Exception.php
@@ -0,0 +1,5 @@
+request = $request;
+ $this->storage = app('filesystem');
+ }
+
+ /**
+ * Get chuck upload path
+ *
+ * @return string
+ */
+ public function getChunkPath()
+ {
+ $path = config('plupload.chunk_path');
+
+ if (!$this->storage->isDirectory($path))
+ {
+ $this->storage->makeDirectory($path, 0777, true);
+ }
+
+ return $path;
+ }
+
+ /**
+ * Process uploaded files
+ *
+ * @param string $name
+ * @param closure $closure
+ *
+ * @return array
+ */
+ public function process($name, Closure $closure)
+ {
+ $response = [];
+ $response['jsonrpc'] = "2.0";
+
+ if ($this->hasChunks())
+ {
+ $result = $this->chunks($name, $closure);
+ }
+ else
+ {
+ $result = $this->single($name, $closure);
+ }
+
+ $response['result'] = $result;
+
+ return $response;
+ }
+
+ /**
+ * Handle single uploaded file
+ *
+ * @param string $name
+ * @param closure $closure
+ *
+ * @return mixed
+ */
+ public function single($name, Closure $closure)
+ {
+ if ($this->request->hasFile($name))
+ {
+ return $closure($this->request->file($name));
+ }
+ }
+
+ /**
+ * Handle single uploaded file
+ *
+ * @param string $name
+ * @param closure $closure
+ *
+ * @return mixed
+ */
+ public function chunks($name, Closure $closure)
+ {
+ $result = false;
+
+ if ($this->request->hasFile($name))
+ {
+ $file = $this->request->file($name);
+
+ $chunk = (int) $this->request->get('chunk', false);
+ $chunks = (int) $this->request->get('chunks', false);
+ $originalName = $this->request->get('name');
+
+ $filePath = $this->getChunkPath() . '/' . $originalName . '.part';
+
+ $this->removeOldData($filePath);
+ $this->appendData($filePath, $file);
+
+ if ($chunk == $chunks - 1)
+ {
+ $file = new UploadedFile($filePath, $originalName, 'blob', sizeof($filePath), UPLOAD_ERR_OK, true);
+ $result = $closure($file);
+ @unlink($filePath);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * Remove old chunks
+ */
+ protected function removeOldData($filePath)
+ {
+ if ($this->storage->exists($filePath) && ($this->storage->lastModified($filePath) < time() - $this->maxFileAge))
+ {
+ $this->storage->delete($filePath);
+ }
+ }
+
+ /**
+ * Merge chunks
+ */
+ protected function appendData($filePathPartial, UploadedFile $file)
+ {
+ if (!$out = @fopen($filePathPartial, "ab"))
+ {
+ throw new Exception("Failed to open output stream.", 102);
+ }
+
+ if (!$in = @fopen($file->getPathname(), "rb"))
+ {
+ throw new Exception("Failed to open input stream", 101);
+ }
+
+ while ($buff = fread($in, 4096))
+ {
+ fwrite($out, $buff);
+ }
+
+ @fclose($out);
+ @fclose($in);
+ }
+
+ /**
+ * Check if request has chunks
+ *
+ * @return bool
+ */
+ public function hasChunks()
+ {
+ return (bool) $this->request->get('chunks', false);
+ }
+}
\ No newline at end of file
diff --git a/src/Html.php b/src/Html.php
new file mode 100644
index 0000000..7ca4632
--- /dev/null
+++ b/src/Html.php
@@ -0,0 +1,187 @@
+id = $id;
+ $this->options['url'] = $url;
+
+ $this->initDefaultOptions();
+ }
+
+ /**
+ * Set default uploader options
+ */
+ protected function initDefaultOptions()
+ {
+ $options = ['flash_swf_url', 'silverlight_xap_url'];
+
+ foreach ($options as $option)
+ {
+ $this->options[$option] = config('plupload.' . $option);
+ }
+ }
+
+ /**
+ * Set default uploader buttons
+ *
+ * @param array $options
+ *
+ * @return void
+ */
+ protected function initDefaultButtons(array $options)
+ {
+ if (!$this->pickFilesButton)
+ {
+ $this->pickFilesButton = '
+
+ Browse
+
+ ';
+ }
+
+ if (!$this->uploadButton)
+ {
+ $this->uploadButton = '
+
+ Upload
+
+ ';
+ }
+ }
+
+ protected function init()
+ {
+ if (!$this->data)
+ {
+ if (empty($this->options['url']))
+ {
+ throw new Exception("Missing URL option.", 1);
+ }
+
+ $options = [];
+
+ if (empty($this->options['browse_button']))
+ {
+ $options['browse_button'] = 'uploader-' . $this->id . '-pickfiles';
+ }
+
+ if (empty($this->options['container']))
+ {
+ $options['container'] = 'uploader-' . $this->id . '-container';
+ }
+
+ $options = array_merge($this->options, $options);
+
+ // csrf token
+ $options['multipart_params']['_token'] = csrf_token();
+
+ $this->initDefaultButtons($options);
+
+ $id = $this->id;
+ $autoStart = $this->autoStart;
+ $buttons = [
+ 'pickFiles' => $this->pickFilesButton,
+ 'upload' => $this->uploadButton
+ ];
+
+ $this->data = compact('options', 'id', 'autoStart', 'buttons');
+ }
+
+ return $this->data;
+ }
+
+ /**
+ * Set uploader auto start
+ *
+ * @param bool $bool
+ *
+ * @return void
+ */
+ public function setAutoStart($bool)
+ {
+ $this->autoStart = (bool) $bool;
+
+ return $this;
+ }
+
+ /**
+ * Set uploader options
+ * @see https://github.com/moxiecode/plupload/wiki/Options
+ *
+ * @param array $options
+ *
+ * @return void
+ */
+ public function setOptions(array $options)
+ {
+ $options = array_except($options, ['url']);
+ $this->options = array_merge($this->options, $options);
+
+ return $this;
+ }
+
+ /**
+ * Set uploader pick files button
+ *
+ * @param string $button
+ *
+ * @return void
+ */
+ public function setPickFilesButton($button)
+ {
+ $this->pickFilesButton = $button;
+ return $this;
+ }
+
+ /**
+ * Set uploader upload button
+ *
+ * @param string $button
+ *
+ * @return void
+ */
+ public function setUploadButton($button)
+ {
+ $this->uploadButton = $button;
+ return $this;
+ }
+
+ public function render($view = 'plupload::uploader', array $extra = array())
+ {
+ $this->init();
+
+ return view($view, $this->data);
+ }
+}
\ No newline at end of file
diff --git a/src/Plupload.php b/src/Plupload.php
new file mode 100644
index 0000000..65bdcc4
--- /dev/null
+++ b/src/Plupload.php
@@ -0,0 +1,47 @@
+request = $request;
+ }
+
+ /**
+ * File upload handler
+ *
+ * @param string $name
+ * @param closure $closure
+ *
+ * @return void
+ */
+ public function file($name, Closure $closure)
+ {
+ $fileHandler = new File($this->request);
+
+ return $fileHandler->process($name, $closure);
+ }
+
+ /**
+ * Html template handler
+ *
+ * @param string $id
+ * @param string $url
+ *
+ * @return void
+ */
+ public function make($id, $url)
+ {
+ // $id = is_null($id) ? str_random() : $id;
+
+ return new Html($id, $url);
+ }
+}
\ No newline at end of file
diff --git a/src/PluploadServiceProvider.php b/src/PluploadServiceProvider.php
new file mode 100644
index 0000000..a4049ea
--- /dev/null
+++ b/src/PluploadServiceProvider.php
@@ -0,0 +1,61 @@
+mergeConfigFrom($configPath, 'plupload');
+
+ $this->app['plupload'] = $this->app->share(function($app)
+ {
+ return $app->make('Jenky\LaravelPlupload\Plupload', [
+ 'request' => $app['request']
+ ]);
+ });
+ }
+
+ /**
+ * Bootstrap the application events.
+ *
+ * @return void
+ */
+ public function boot()
+ {
+ $configPath = __DIR__ . '/../config/plupload.php';
+ $viewsPath = __DIR__.'/../views';
+
+ $this->loadViewsFrom($viewsPath, 'plupload');
+
+ $this->publishes([$configPath => config_path('plupload.php')], 'config');
+ $this->publishes([
+ $viewsPath => base_path('resources/views/vendor/plupload'),
+ ]);
+ }
+
+ /**
+ * Get the services provided by the provider.
+ *
+ * @return array
+ */
+ public function provides()
+ {
+ return ['plupload'];
+ }
+
+}
diff --git a/tests/.gitkeep b/tests/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/views/uploader.blade.php b/views/uploader.blade.php
new file mode 100644
index 0000000..7e6d2e5
--- /dev/null
+++ b/views/uploader.blade.php
@@ -0,0 +1,19 @@
+@if (!empty($options['url']))
+
+
+
+
+ {!! $buttons['pickFiles'] !!}
+ @if (!$autoStart)
+ {!! $buttons['upload'] !!}
+ @endif
+
+
+
+
+@else
+ Missing URL option.
+@endif
\ No newline at end of file