From 5c07c9291fb440ffcb6565506020922e13794d01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= Date: Tue, 17 Nov 2015 15:54:20 -0500 Subject: [PATCH 1/9] Created UpdateDropzoneCommand. --- js/dropzone.integration.js | 12 ++++++- src/Ajax/UpdateDropzoneCommand.php | 55 +++++++++++++++++++++++++++++ src/Controller/UploadController.php | 30 +++++++++++----- 3 files changed, 88 insertions(+), 9 deletions(-) create mode 100644 src/Ajax/UpdateDropzoneCommand.php diff --git a/js/dropzone.integration.js b/js/dropzone.integration.js index 2b8f489..24b3286 100644 --- a/js/dropzone.integration.js +++ b/js/dropzone.integration.js @@ -9,6 +9,12 @@ (function ($, Drupal, drupalSettings) { "use strict"; + Drupal.AjaxCommands.prototype.updateDropzone = function (ajax, response, status) { + $(response.selector).siblings(':hidden').val(function (value) { + return value + response.filename + ';'; + }); + }; + Drupal.dropzonejsInstances = []; Drupal.behaviors.dropzonejsIntegraion = { @@ -21,7 +27,11 @@ // Initiate dropzonejs. var config = { url: input.attr('data-upload-path'), - addRemoveLinks: true + addRemoveLinks: true, + params: { + // Send the dropzone's CSS ID to the server for AJAX purposes. + selector: selector.attr('id') + } }; var instanceConfig = drupalSettings.dropzonejs.instances[selector.attr('id')]; var dropzoneInstance = new Dropzone("#" + selector.attr("id"), $.extend({}, instanceConfig, config)); diff --git a/src/Ajax/UpdateDropzoneCommand.php b/src/Ajax/UpdateDropzoneCommand.php new file mode 100644 index 0000000..677c198 --- /dev/null +++ b/src/Ajax/UpdateDropzoneCommand.php @@ -0,0 +1,55 @@ +selector = $selector; + $this->fileName = $filename; + } + + /** + * {@inheritdoc} + */ + public function render() { + return [ + 'command' => 'updateDropzone', + 'filename' => $this->fileName, + ]; + } + +} diff --git a/src/Controller/UploadController.php b/src/Controller/UploadController.php index bb525e8..18ab53b 100644 --- a/src/Controller/UploadController.php +++ b/src/Controller/UploadController.php @@ -7,10 +7,12 @@ namespace Drupal\dropzonejs\Controller; +use Drupal\Core\Ajax\AjaxResponse; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Controller\ControllerBase; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\Core\Transliteration\PhpTransliteration; +use Drupal\dropzonejs\Ajax\UpdateDropzoneCommand; use Drupal\dropzonejs\UploadException; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\File\UploadedFile; @@ -92,8 +94,13 @@ public static function create(ContainerInterface $container) { /** * Handles DropzoneJs uploads. + * + * @param \Symfony\Component\HttpFoundation\Request $req + * The request. + * + * @return \Symfony\Component\HttpFoundation\Response */ - public function handleUploads() { + public function handleUploads(Request $req) { // @todo: Implement file_validate_size(); try { $this->prepareTemporaryUploadDestination(); @@ -103,13 +110,20 @@ public function handleUploads() { return $e->getErrorResponse(); } - // Return JSON-RPC response. - // Controllers should return a response. - return new JsonResponse([ - 'jsonrpc' => '2.0', - 'result' => $this->filename, - 'id' => 'id', - ], 200); + // Return either an AJAX command or a JSON-RPC response (default). + $request = $req->request; + if ($request->has('_drupal_ajax')) { + $command = new UpdateDropzoneCommand($request->get('selector'), $this->filename); + return (new AjaxResponse())->addCommand($command); + } + else { + // Return JSON-RPC response. + return new JsonResponse([ + 'jsonrpc' => '2.0', + 'result' => $this->filename, + 'id' => 'id', + ], 200); + } } /** From ecddf24c796945125d0f9f3147603e859bc348c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= Date: Tue, 17 Nov 2015 17:01:59 -0500 Subject: [PATCH 2/9] Added support for JSON-RPC to the client-side integration code. --- js/dropzone.integration.js | 14 ++++++++------ src/Controller/UploadController.php | 4 +--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/js/dropzone.integration.js b/js/dropzone.integration.js index 24b3286..2d5e1f1 100644 --- a/js/dropzone.integration.js +++ b/js/dropzone.integration.js @@ -41,14 +41,16 @@ // React on add file. Add only accepted files. dropzoneInstance.on("success", function(file, response) { - var uploadedFilesElement = selector.siblings(':hidden'); - var currentValue = uploadedFilesElement.attr('value'); + if (response.hasOwnProperty('json-rpc')) { + var uploadedFilesElement = selector.siblings(':hidden'); + var currentValue = uploadedFilesElement.attr('value'); - // The file is transliterated on upload. The element has to reflect - // the real filename. - file.processedName = response.result; + // The file is transliterated on upload. The element has to reflect + // the real filename. + file.processedName = response.result; - uploadedFilesElement.attr('value', currentValue + response.result + ';'); + uploadedFilesElement.attr('value', currentValue + response.result + ';'); + } }); // React on file removing. diff --git a/src/Controller/UploadController.php b/src/Controller/UploadController.php index 18ab53b..aad4efc 100644 --- a/src/Controller/UploadController.php +++ b/src/Controller/UploadController.php @@ -10,7 +10,6 @@ use Drupal\Core\Ajax\AjaxResponse; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Controller\ControllerBase; -use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\Core\Transliteration\PhpTransliteration; use Drupal\dropzonejs\Ajax\UpdateDropzoneCommand; use Drupal\dropzonejs\UploadException; @@ -18,7 +17,6 @@ use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; /** @@ -180,7 +178,7 @@ protected function getFilename(UploadedFile $file) { * Handles multipart uploads. * * @throws \Drupal\dropzonejs\UploadException - * @throws Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException + * @throws \Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException */ protected function handleUpload() { /** @var \Symfony\Component\HttpFoundation\File\UploadedFile $file */ From fdd350546e036c5584e1f22e2ed4c11da3c8f4f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= Date: Wed, 18 Nov 2015 09:45:49 -0500 Subject: [PATCH 3/9] Created an upload_handler service. --- dropzonejs.services.yml | 3 + src/Controller/UploadController.php | 182 ++++------------------------ src/UploadHandler.php | 164 +++++++++++++++++++++++++ src/UploadHandlerInterface.php | 40 ++++++ 4 files changed, 230 insertions(+), 159 deletions(-) create mode 100644 src/UploadHandler.php create mode 100644 src/UploadHandlerInterface.php diff --git a/dropzonejs.services.yml b/dropzonejs.services.yml index 4abfd10..93b1d6b 100644 --- a/dropzonejs.services.yml +++ b/dropzonejs.services.yml @@ -2,3 +2,6 @@ services: dropzonejs.upload_save: class: Drupal\dropzonejs\DropzoneJsUploadSave arguments: ['@entity.manager', '@file.mime_type.guesser', '@file_system', '@logger.factory', '@renderer', '@config.factory'] + dropzonejs.upload_handler: + class: Drupal\dropzonejs\UploadHandler + arguments: ['@request_stack', '@config.factory', '@transliteration'] diff --git a/src/Controller/UploadController.php b/src/Controller/UploadController.php index bb525e8..4af465b 100644 --- a/src/Controller/UploadController.php +++ b/src/Controller/UploadController.php @@ -7,60 +7,34 @@ namespace Drupal\dropzonejs\Controller; -use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Controller\ControllerBase; -use Drupal\Core\DependencyInjection\ContainerInjectionInterface; -use Drupal\Core\Transliteration\PhpTransliteration; use Drupal\dropzonejs\UploadException; +use Drupal\dropzonejs\UploadHandlerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; /** * Handles requests that dropzone issues when uploading files. - * - * The uploaded file will be stored in the configured tmp folder and will be - * added a tmp extension. Further filename processing will be done in - * Drupal\dropzonejs\Element::valueCallback. This means that the final - * filename will be provided only after that callback. */ class UploadController extends ControllerBase { /** - * The current request. - * - * @var \Symfony\Component\HttpFoundation\Request $request - * The HTTP request object. - */ - protected $request; - - /** - * Stores temporary folder URI. + * The upload handler service. * - * This is configurable via the configuration variable. It was added for HA - * environments where temporary location may need to be a shared across all - * servers. - * - * @var string - */ - protected $temporaryUploadLocation; - - /** - * Filename of a file that is being uploaded. - * - * @var string + * @var \Drupal\dropzonejs\UploadHandlerInterface */ - protected $filename; + protected $uploadHandler; /** - * Transliteration service. + * The current request. * - * @var \Drupal\Core\Transliteration\PhpTransliteration + * @var \Symfony\Component\HttpFoundation\Request $request + * The HTTP request object. */ - protected $transliteration; + protected $request; /** * Constructs dropzone upload controller route controller. @@ -72,11 +46,9 @@ class UploadController extends ControllerBase { * @param \Drupal\Core\Transliteration\PhpTransliteration $transliteration * Transliteration service. */ - public function __construct(Request $request, ConfigFactoryInterface $config, PhpTransliteration $transliteration) { + public function __construct(UploadHandlerInterface $upload_handler, Request $request) { + $this->uploadHandler = $upload_handler; $this->request = $request; - $tmp_override = $config->get('dropzonejs.settings')->get('tmp_dir'); - $this->temporaryUploadLocation = ($tmp_override) ? $tmp_override : $config->get('system.file')->get('path.temporary'); - $this->trasliteration = $transliteration; } /** @@ -84,9 +56,8 @@ public function __construct(Request $request, ConfigFactoryInterface $config, Ph */ public static function create(ContainerInterface $container) { return new static( - $container->get('request_stack')->getCurrentRequest(), - $container->get('config.factory'), - $container->get('transliteration') + $container->get('dropzonejs.upload_handler'), + $container->get('request_stack')->getCurrentRequest() ); } @@ -94,130 +65,23 @@ public static function create(ContainerInterface $container) { * Handles DropzoneJs uploads. */ public function handleUploads() { - // @todo: Implement file_validate_size(); - try { - $this->prepareTemporaryUploadDestination(); - $this->handleUpload(); - } - catch (UploadException $e) { - return $e->getErrorResponse(); - } - - // Return JSON-RPC response. - // Controllers should return a response. - return new JsonResponse([ - 'jsonrpc' => '2.0', - 'result' => $this->filename, - 'id' => 'id', - ], 200); - } - - /** - * Prepares temporary destination folder for uploaded files. - * - * @return bool - * TRUE if destination folder looks OK and FALSE otherwise. - * - * @throws \Drupal\dropzonejs\UploadException - */ - protected function prepareTemporaryUploadDestination() { - $writable = file_prepare_directory($this->temporaryUploadLocation, FILE_CREATE_DIRECTORY); - if (!$writable) { - throw new UploadException(UploadException::DESTINATION_FOLDER_ERROR); - } - - // Try to make sure this is private via htaccess. - file_save_htaccess($this->temporaryUploadLocation, TRUE); - } - - /** - * Reads, checks and return filename of a file being uploaded. - * - * @param \Symfony\Component\HttpFoundation\File\UploadedFile $file - * An instance of UploadedFile. - * - * @throws \Drupal\dropzonejs\UploadException - */ - protected function getFilename(UploadedFile $file) { - if (empty($this->filename)) { - $original_name = $file->getClientOriginalName(); - - // There should be a filename and it should not contain a semicolon, - // which we use to separate filenames. - if (!isset($original_name)) { - throw new UploadException(UploadException::FILENAME_ERROR); - } - - // Transliterate. - $processed_filename = \Drupal::transliteration()->transliterate($original_name); - - // For security reasons append the txt extension. It will be removed in - // Drupal\dropzonejs\Element::valueCallback when we will know the valid - // extension and we will be abble to properly sanitaze the filename. - $processed_filename = $processed_filename . '.txt'; - - $this->filename = $processed_filename; - } - - return $this->filename; - } - - /** - * Handles multipart uploads. - * - * @throws \Drupal\dropzonejs\UploadException - * @throws Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException - */ - protected function handleUpload() { - /** @var \Symfony\Component\HttpFoundation\File\UploadedFile $file */ $file = $this->request->files->get('file'); if (!$file instanceof UploadedFile) { throw new AccessDeniedHttpException(); } - elseif ($error = $file->getError() && $error != UPLOAD_ERR_OK) { - // Check for file upload errors and return FALSE for this file if a lower - // level system error occurred. For a complete list of errors: - // See http://php.net/manual/features.file-upload.errors.php. - switch ($error) { - case UPLOAD_ERR_INI_SIZE: - case UPLOAD_ERR_FORM_SIZE: - $message = t('The file could not be saved because it exceeds the maximum allowed size for uploads.'); - continue; - - case UPLOAD_ERR_PARTIAL: - case UPLOAD_ERR_NO_FILE: - $message = t('The file could not be saved because the upload did not complete.'); - continue; - - // Unknown error. - default: - $message = t('The file could not be saved. An unknown error has occurred.'); - continue; - } - - throw new UploadException(UploadException::FILE_UPLOAD_ERROR, $message); - } - - // Open temp file. - $tmp = "{$this->temporaryUploadLocation}/{$this->getFilename($file)}"; - if (!($out = fopen("{$this->temporaryUploadLocation}/{$this->getFilename($file)}", $this->request->request->get('chunk', 0) ? 'ab' : 'wb'))) { - throw new UploadException(UploadException::OUTPUT_ERROR); - } - // Read binary input stream. - $input_uri = $file->getFileInfo()->getRealPath(); - if (!($in = fopen($input_uri, 'rb'))) { - throw new UploadException(UploadException::INPUT_ERROR); + // @todo: Implement file_validate_size(); + try { + // Return JSON-RPC response. + return new JsonResponse([ + 'jsonrpc' => '2.0', + 'result' => basename($this->uploadHandler->handleUpload($file)), + 'id' => 'id', + ], 200); } - - // Append input stream to temp file. - while ($buff = fread($in, 4096)) { - fwrite($out, $buff); + catch (UploadException $e) { + return $e->getErrorResponse(); } - - // Be nice and keep everything nice and clean. - // @todo when implementing multipart dont forget to drupal_unlink. - fclose($in); - fclose($out); } + } diff --git a/src/UploadHandler.php b/src/UploadHandler.php new file mode 100644 index 0000000..bd7b65b --- /dev/null +++ b/src/UploadHandler.php @@ -0,0 +1,164 @@ +request = $request_stack->getCurrentRequest(); + $tmp_override = $config->get('dropzonejs.settings')->get('tmp_dir'); + $this->temporaryUploadLocation = ($tmp_override) ? $tmp_override : $config->get('system.file')->get('path.temporary'); + $this->transliteration = $transliteration; + } + + /** + * Prepares temporary destination folder for uploaded files. + * + * @return bool + * TRUE if destination folder looks OK and FALSE otherwise. + * + * @throws \Drupal\dropzonejs\UploadException + */ + protected function prepareTemporaryUploadDestination() { + $writable = file_prepare_directory($this->temporaryUploadLocation, FILE_CREATE_DIRECTORY); + if (!$writable) { + throw new UploadException(UploadException::DESTINATION_FOLDER_ERROR); + } + + // Try to make sure this is private via htaccess. + file_save_htaccess($this->temporaryUploadLocation, TRUE); + } + + /** + * {@inheritdoc} + */ + public function getFilename(UploadedFile $file) { + $original_name = $file->getClientOriginalName(); + + // There should be a filename and it should not contain a semicolon, + // which we use to separate filenames. + if (!isset($original_name)) { + throw new UploadException(UploadException::FILENAME_ERROR); + } + + // Transliterate. + $processed_filename = $this->transliteration->transliterate($original_name); + + // For security reasons append the txt extension. It will be removed in + // Drupal\dropzonejs\Element::valueCallback when we will know the valid + // extension and we will be able to properly sanitize the filename. + $processed_filename = $processed_filename . '.txt'; + + return $processed_filename; + } + + /** + * {@inheritdoc} + */ + public function handleUpload(UploadedFile $file) { + $this->prepareTemporaryUploadDestination(); + + $error = $file->getError(); + if ($error != UPLOAD_ERR_OK) { + // Check for file upload errors and return FALSE for this file if a lower + // level system error occurred. For a complete list of errors: + // See http://php.net/manual/features.file-upload.errors.php. + switch ($error) { + case UPLOAD_ERR_INI_SIZE: + case UPLOAD_ERR_FORM_SIZE: + $message = t('The file could not be saved because it exceeds the maximum allowed size for uploads.'); + continue; + + case UPLOAD_ERR_PARTIAL: + case UPLOAD_ERR_NO_FILE: + $message = t('The file could not be saved because the upload did not complete.'); + continue; + + // Unknown error. + default: + $message = t('The file could not be saved. An unknown error has occurred.'); + continue; + } + + throw new UploadException(UploadException::FILE_UPLOAD_ERROR, $message); + } + + // Open temp file. + $tmp = "{$this->temporaryUploadLocation}/{$this->getFilename($file)}"; + if (!($out = fopen($tmp, $this->request->request->get('chunk', 0) ? 'ab' : 'wb'))) { + throw new UploadException(UploadException::OUTPUT_ERROR); + } + + // Read binary input stream. + $input_uri = $file->getFileInfo()->getRealPath(); + if (!($in = fopen($input_uri, 'rb'))) { + throw new UploadException(UploadException::INPUT_ERROR); + } + + // Append input stream to temp file. + while ($buff = fread($in, 4096)) { + fwrite($out, $buff); + } + + // Be nice and keep everything nice and clean. + // @todo when implementing multipart don't forget to drupal_unlink. + fclose($in); + fclose($out); + + return $tmp; + } +} diff --git a/src/UploadHandlerInterface.php b/src/UploadHandlerInterface.php new file mode 100644 index 0000000..47ee510 --- /dev/null +++ b/src/UploadHandlerInterface.php @@ -0,0 +1,40 @@ + Date: Wed, 18 Nov 2015 10:03:57 -0500 Subject: [PATCH 4/9] Fixed doc comment. --- src/UploadHandler.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/UploadHandler.php b/src/UploadHandler.php index bd7b65b..c00bc63 100644 --- a/src/UploadHandler.php +++ b/src/UploadHandler.php @@ -51,8 +51,8 @@ class UploadHandler implements UploadHandlerInterface { /** * Constructs dropzone upload controller route controller. * - * @param \Symfony\Component\HttpFoundation\Request $request - * Request object. + * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack + * The request stack. * @param \Drupal\Core\Config\ConfigFactoryInterface $config * Config factory. * @param \Drupal\Core\Transliteration\PhpTransliteration $transliteration @@ -61,7 +61,7 @@ class UploadHandler implements UploadHandlerInterface { public function __construct(RequestStack $request_stack, ConfigFactoryInterface $config, TransliterationInterface $transliteration) { $this->request = $request_stack->getCurrentRequest(); $tmp_override = $config->get('dropzonejs.settings')->get('tmp_dir'); - $this->temporaryUploadLocation = ($tmp_override) ? $tmp_override : $config->get('system.file')->get('path.temporary'); + $this->temporaryUploadLocation = $tmp_override ?: $config->get('system.file')->get('path.temporary'); $this->transliteration = $transliteration; } @@ -161,4 +161,5 @@ public function handleUpload(UploadedFile $file) { return $tmp; } + } From 8bb246de19bc46b00def19f943a8bcca4fa692e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= Date: Wed, 18 Nov 2015 13:16:38 -0500 Subject: [PATCH 5/9] Client-side code now cleanly supports different kinds of responses. --- js/dropzone.integration.js | 65 ++++++++++++++++++++++++++---- src/Ajax/UpdateDropzoneCommand.php | 65 ++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+), 8 deletions(-) create mode 100644 src/Ajax/UpdateDropzoneCommand.php diff --git a/js/dropzone.integration.js b/js/dropzone.integration.js index 2b8f489..08fb9b3 100644 --- a/js/dropzone.integration.js +++ b/js/dropzone.integration.js @@ -9,6 +9,54 @@ (function ($, Drupal, drupalSettings) { "use strict"; + Drupal.AjaxCommands.prototype.update_dropzone = function (ajax, response, status) { + $(response.selector).val(function (value) { + return value + response.files.join(';') + ';'; + }); + }; + + Drupal.dropzonejs = { + responseHandlers: { + /** + * Handles JSON-RPC response from DropzoneJS' UploadController. + */ + jsonRPC: { + canHandle: function (response) { + return response.hasOwnProperty('jsonrpc'); + }, + handle: function (response) { + var uploadedFilesElement = this.element.siblings(':hidden'); + var currentValue = uploadedFilesElement.attr('value'); + + // The file is transliterated on upload. The element has to reflect + // the real filename. + file.processedName = response.result; + + uploadedFilesElement.attr('value', currentValue + response.result + ';'); + } + }, + + /** + * Handles response from Drupal's AJAX framework (an array of commands). + */ + drupalAjax: { + canHandle: function (response) { + return response instanceof Array; + }, + handle: function (response) { + if (typeof this.drupalAjax === 'undefined') { + var settings = { + element: $(this.element) + }; + settings.url = settings.element.siblings('input[data-upload-path]').attr('data-upload-path'); + this.drupalAjax = Drupal.ajax(settings); + } + this.drupalAjax.success(response); + } + } + } + }; + Drupal.dropzonejsInstances = []; Drupal.behaviors.dropzonejsIntegraion = { @@ -31,18 +79,19 @@ // React on add file. Add only accepted files. dropzoneInstance.on("success", function(file, response) { - var uploadedFilesElement = selector.siblings(':hidden'); - var currentValue = uploadedFilesElement.attr('value'); - - // The file is transliterated on upload. The element has to reflect - // the real filename. - file.processedName = response.result; - - uploadedFilesElement.attr('value', currentValue + response.result + ';'); + // Find the appropriate response handler. + for (var type in Drupal.dropzonejs.responseHandlers) { + var handler = Drupal.dropzonejs.responseHandlers[type]; + if (handler.canHandle.call(this, response)) { + handler.handle.call(this, response); + break; + } + } }); // React on file removing. dropzoneInstance.on("removedfile", function(file) { + console.log(this); var uploadedFilesElement = selector.siblings(':hidden'); var currentValue = uploadedFilesElement.attr('value'); diff --git a/src/Ajax/UpdateDropzoneCommand.php b/src/Ajax/UpdateDropzoneCommand.php new file mode 100644 index 0000000..3c6759e --- /dev/null +++ b/src/Ajax/UpdateDropzoneCommand.php @@ -0,0 +1,65 @@ +element = $element; + } + + /** + * Adds to the list of files to be updated in the dropzone element. + * + * @param string|\Symfony\Component\HttpFoundation\File\UploadedFile $file + * The uploaded file name, or an UploadedFile object. + * + * @return $this + */ + public function addFile($file) { + $this->files[] = $file instanceof UploadedFile ? $file->getFilename() : (string) $file; + return $this; + } + + /** + * {@inheritdoc} + */ + public function render() { + return [ + 'command' => 'update_dropzone', + 'selector' => 'input[name="' . $this->element['uploaded_files']['#name'] . '"]', + 'files' => array_unique($this->files), + ]; + } + +} From d7f44bcb7b682cb9501409fea3384b8b5ad525de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= Date: Wed, 18 Nov 2015 13:23:00 -0500 Subject: [PATCH 6/9] Re-added forgotten 403 exception. --- src/Controller/UploadController.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Controller/UploadController.php b/src/Controller/UploadController.php index aad4020..3fd811a 100644 --- a/src/Controller/UploadController.php +++ b/src/Controller/UploadController.php @@ -11,8 +11,10 @@ use Drupal\dropzonejs\UploadException; use Drupal\dropzonejs\UploadHandlerInterface; use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpFoundation\File\UploadedFile; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; /** * Handles requests that dropzone issues when uploading files. @@ -67,6 +69,10 @@ public static function create(ContainerInterface $container) { */ public function handleUploads() { $file = $this->request->files->get('file'); + if (!$file instanceof UploadedFile) { + throw new AccessDeniedHttpException(); + } + // @todo: Implement file_validate_size(); try { $uri = $this->uploadHandler->handleUpload($file); From 7dedf3448924bc3f75cbd3514068c01bed207d8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= Date: Wed, 18 Nov 2015 13:55:09 -0500 Subject: [PATCH 7/9] Fixed bugs in the client-side code. --- js/dropzone.integration.js | 53 +++++++++++++----------------- src/Ajax/UpdateDropzoneCommand.php | 22 ++++--------- 2 files changed, 29 insertions(+), 46 deletions(-) diff --git a/js/dropzone.integration.js b/js/dropzone.integration.js index 08fb9b3..001cde0 100644 --- a/js/dropzone.integration.js +++ b/js/dropzone.integration.js @@ -10,8 +10,8 @@ "use strict"; Drupal.AjaxCommands.prototype.update_dropzone = function (ajax, response, status) { - $(response.selector).val(function (value) { - return value + response.files.join(';') + ';'; + $(response.selector).val(function (i, value) { + return value.split(';').concat(response.file).join(';'); }); }; @@ -21,18 +21,17 @@ * Handles JSON-RPC response from DropzoneJS' UploadController. */ jsonRPC: { - canHandle: function (response) { + canHandle: function (file, response) { return response.hasOwnProperty('jsonrpc'); }, - handle: function (response) { - var uploadedFilesElement = this.element.siblings(':hidden'); - var currentValue = uploadedFilesElement.attr('value'); - + handle: function (file, response) { // The file is transliterated on upload. The element has to reflect // the real filename. file.processedName = response.result; - uploadedFilesElement.attr('value', currentValue + response.result + ';'); + this.element.siblings(':hidden').val(function (i, value) { + return value.split(';').concat(response.result).join(';'); + }); } }, @@ -40,10 +39,12 @@ * Handles response from Drupal's AJAX framework (an array of commands). */ drupalAjax: { - canHandle: function (response) { + canHandle: function (file, response) { return response instanceof Array; }, - handle: function (response) { + handle: function (file, response) { + // Create a Drupal.Ajax object so that we can call its success() method to + // run the commands in the response. if (typeof this.drupalAjax === 'undefined') { var settings = { element: $(this.element) @@ -51,6 +52,11 @@ settings.url = settings.element.siblings('input[data-upload-path]').attr('data-upload-path'); this.drupalAjax = Drupal.ajax(settings); } + response.forEach(function (command) { + if (command.command === 'update_dropzone') { + file.processedName = command.file; + } + }) this.drupalAjax.success(response); } } @@ -60,7 +66,7 @@ Drupal.dropzonejsInstances = []; Drupal.behaviors.dropzonejsIntegraion = { - attach: function(context) { + attach: function () { Dropzone.autoDiscover = false; var selector = $(".dropzone-enable"); selector.addClass("dropzone"); @@ -82,8 +88,8 @@ // Find the appropriate response handler. for (var type in Drupal.dropzonejs.responseHandlers) { var handler = Drupal.dropzonejs.responseHandlers[type]; - if (handler.canHandle.call(this, response)) { - handler.handle.call(this, response); + if (handler.canHandle.call(this, file, response)) { + handler.handle.call(this, file, response); break; } } @@ -91,26 +97,11 @@ // React on file removing. dropzoneInstance.on("removedfile", function(file) { - console.log(this); - var uploadedFilesElement = selector.siblings(':hidden'); - var currentValue = uploadedFilesElement.attr('value'); - - // Remove the file from the element. - if (currentValue.length) { - var fileNames = currentValue.split(";"); - for (var i in fileNames) { - if (fileNames[i] == file.processedName) { - fileNames.splice(i,1); - break; - } - } - - var newValue = fileNames.join(';'); - uploadedFilesElement.attr('value', newValue); - } + selector.siblings(':hidden').val(function (i, value) { + return value.split(';').filter(function (f) { return f !== file.processedName; }).join(';'); + }); }); } }; - }(jQuery, Drupal, drupalSettings)); diff --git a/src/Ajax/UpdateDropzoneCommand.php b/src/Ajax/UpdateDropzoneCommand.php index 3c6759e..4690145 100644 --- a/src/Ajax/UpdateDropzoneCommand.php +++ b/src/Ajax/UpdateDropzoneCommand.php @@ -24,31 +24,23 @@ class UpdateDropzoneCommand implements CommandInterface { protected $element; /** + * The name of the uploaded file. + * * @var string */ - protected $files = []; + protected $file; /** * UpdateDropzoneCommand constructor. * * @param array $element * The form element to be updated. - */ - public function __construct(array $element) { - $this->element = $element; - } - - /** - * Adds to the list of files to be updated in the dropzone element. - * * @param string|\Symfony\Component\HttpFoundation\File\UploadedFile $file * The uploaded file name, or an UploadedFile object. - * - * @return $this */ - public function addFile($file) { - $this->files[] = $file instanceof UploadedFile ? $file->getFilename() : (string) $file; - return $this; + public function __construct(array $element, $file) { + $this->element = $element; + $this->file = $file instanceof UploadedFile ? $file->getFilename() : $file; } /** @@ -58,7 +50,7 @@ public function render() { return [ 'command' => 'update_dropzone', 'selector' => 'input[name="' . $this->element['uploaded_files']['#name'] . '"]', - 'files' => array_unique($this->files), + 'file' => $this->file, ]; } From 28e5b8ab3d7077443740fa4d359db1e68d43abed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= Date: Wed, 18 Nov 2015 14:25:31 -0500 Subject: [PATCH 8/9] Fixed typo in behavior property. --- js/dropzone.integration.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/dropzone.integration.js b/js/dropzone.integration.js index 001cde0..01f8faa 100644 --- a/js/dropzone.integration.js +++ b/js/dropzone.integration.js @@ -65,7 +65,7 @@ Drupal.dropzonejsInstances = []; - Drupal.behaviors.dropzonejsIntegraion = { + Drupal.behaviors.dropzonejsIntegration = { attach: function () { Dropzone.autoDiscover = false; var selector = $(".dropzone-enable"); From d5ab18a1aa875d95042996350b33a8dd41fa072f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ph=C3=A9na=20Proxima?= Date: Wed, 18 Nov 2015 15:12:39 -0500 Subject: [PATCH 9/9] Fixed minor bug in the JS .val() calls. --- js/dropzone.integration.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/js/dropzone.integration.js b/js/dropzone.integration.js index 01f8faa..21c9f12 100644 --- a/js/dropzone.integration.js +++ b/js/dropzone.integration.js @@ -11,7 +11,9 @@ Drupal.AjaxCommands.prototype.update_dropzone = function (ajax, response, status) { $(response.selector).val(function (i, value) { - return value.split(';').concat(response.file).join(';'); + value = value.split(';'); + value.push(response.file); + return value.join(';'); }); }; @@ -30,7 +32,9 @@ file.processedName = response.result; this.element.siblings(':hidden').val(function (i, value) { - return value.split(';').concat(response.result).join(';'); + value = value.split(';'); + value.push(response.result); + return value.join(';'); }); } },