Commit ad0ca20b authored by Primsi's avatar Primsi
Browse files

by primsi: interim commit for initial implementation.

parent b2954bca
name: dropzonejs
type: module
description: DropzoneJS
core: 8.x
package: Other
dependencies:
- file
dropzonejs:
version: VERSION
js:
# @todo add option to include uncompressed
assets/dropzone/dist/min/dropzone.min.js: {}
js/dropzone.integration.js: {}
css:
component:
# @todo add option to include uncompressed
assets/dropzone/dist/min/dropzone.min.css: {}
<?php
/**
* @file
* Contains dropzonejs.module.
*/
use Drupal\Core\Routing\RouteMatchInterface;
/**
* Implements hook_help().
*/
function dropzonejs_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
// Main module help for the dropzonejs module.
case 'help.page.dropzonejs':
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('DropzoneJS') . '</p>';
return $output;
default:
}
}
/**
* Implements hook_theme().
*/
function dropzonejs_theme() {
return [
'dropzonejs' => [
'render element' => 'element',
],
];
}
/**
* Prepares variables for dropzone form element.
*
* Default template: dropzonejs.html.twig.
*
* @param array $variables
* An associative array containing:
* - element: A render element representing the file.
*/
function template_preprocess_dropzonejs(&$variables) {
$element = $variables['element'];
$variables['attributes'] = [];
if (isset($element['#id'])) {
$variables['attributes']['id'] = $element['#id'];
}
if (!empty($element['#attributes']['class'])) {
$variables['attributes']['class'] = (array) $element['#attributes']['class'];
}
$variables['uploaded_files'] = $element['uploaded_files'];
}
dropzone upload files:
title: 'Dropzone upload files'
description: 'Allow uploading of files via dropzonejs.'
dropzonejs.upload:
path: '/dropzonejs/upload'
defaults:
_controller: '\Drupal\dropzonejs\Controller\UploadController::handleUploads'
requirements:
_permission: 'dropzone upload files'
/**
* @file dropzone.integration.js
*
* Defines the behaviors needed for dropzonejs integration.
*/
(function ($, Drupal, drupalSettings) {
"use strict";
Drupal.behaviors.dropzonejsIntegraion = {
attach: function(context) {
Dropzone.autoDiscover = false;
var selector = $(".dropzone-enable");
selector.addClass("dropzone");
var dropzoneInstance = new Dropzone("#" + selector.attr("id"), {
// @todo we should get all this from somewhere.
url: "/drupal-8-media/dropzonejs/upload",
maxFilesize: 2,
});
dropzoneInstance.on("addedfile", function(file) {
console.log(file);
});
}
};
}(jQuery, Drupal, drupalSettings));
<?php
/**
* @file
* Contains \Drupal\dropzonejs\Controller\UploadController.
*/
namespace Drupal\dropzonejs\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\dropzonejs\UploadException;
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;
/**
* Handles requests that dropzone issues when uploading files.
*/
class UploadController extends ControllerBase {
/**
* The current request.
*
* @var \Symfony\Component\HttpFoundation\Request $request
* The HTTP request object.
*/
protected $request;
/**
* Stores temporary folder URI.
*
* 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
*/
protected $filename;
/**
* Constructs dropzone upload controller route controller.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* Request object.
*/
public function __construct(Request $request) {
$this->request = $request;
$this->temporaryUploadLocation = \Drupal::config('system.file')->get('path.temporary');
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static($container->get('request_stack')->getCurrentRequest());
}
/**
* 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.
// @todo Not sure if we need this.
return new JsonResponse(
array(
'jsonrpc' => '2.0',
'result' => NULL,
'id' => 'id',
),
200
);
}
/**
* Prepares temporary destination folder for uploaded files.
*
* @return bool
* TRUE if destination folder looks OK and FALSE otherwise.
*
* @throws \Drupal\plupload\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\plupload\UploadException
*/
protected function getFilename(UploadedFile $file) {
if (empty($this->filename)) {
// @todo I am not sure why plupload gets 'name' here ($this->request->request->get('name')).
$this->filename = $file->getClientOriginalName();
// Check the file name for security reasons; it must contain letters,
// numbers and underscores.
if (!preg_match('/[\w\.]/', $this->filename)) {
throw new UploadException(UploadException::FILENAME_ERROR);
}
}
return $this->filename;
}
/**
* Handles multipart uploads.
*
* @throws \Drupal\dropzonejs\UploadException
*/
protected function handleUpload() {
/** @var \Symfony\Component\HttpFoundation\File\UploadedFile $file */
$file = $this->request->files->get('file');
if (!$file instanceof UploadedFile) {
throw new UploadException(UploadException::REQUEST_ERROR);
}
elseif ($file->getError() != UPLOAD_ERR_OK) {
throw new UploadException(UploadException::FILE_UPLOAD_ERROR);
}
// Open temp file.
$tmp = $this->temporaryUploadLocation . $this->getFilename($file);
// @todo Is the 'b' at the end of the mode ok?
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 = "{$this->temporaryUploadLocation}/{$file->getFilename()}";
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.
fclose($in);
fclose($out);
if ($is_multipart) {
drupal_unlink($multipart_file['tmp_name']);
}
}
}
<?php
/**
* @file
* Contains \Drupal\dropzonejs\src\Element.
*/
namespace Drupal\dropzonejs\Element;
use Drupal\Component\Utility\Html;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Render\Element\File;
/**
* Provides a DropzoneJS atop of the file element.
*
* @FormElement("dropzonejs")
*/
class DropzoneJs extends File {
/**
* {@inheritdoc}
*/
public function getInfo() {
$class = get_class($this);
return array(
'#input' => TRUE,
'#multiple' => FALSE,
'#process' => array(
array($class, 'processDropzoneJs'),
),
'#size' => 60,
'#pre_render' => array(
array($class, 'preRenderDropzoneJs'),
),
'#theme' => 'dropzonejs',
'#theme_wrappers' => array('form_element'),
);
}
/**
* Processes a dropzone upload element, make use of #multiple if present.
*/
public static function processDropzoneJs(&$element, FormStateInterface $form_state, &$complete_form) {
$element['#attached']['library'][] = 'dropzonejs/dropzonejs';
$element['uploaded_files'] = [
'#type' => 'hidden',
// @todo Handle defaults.
'#value' => '',
];
return $element;
}
/**
* Prepares a #type 'dropzone' render element for dropzonejs.html.twig.
*
* For assistance with handling the uploaded file correctly, see the API
* provided by file.inc.
*
* @param array $element
* An associative array containing the properties of the element.
* Properties used: #title, #name, #size, #description, #required,
* #attributes.
*
* @return array
* The $element with prepared variables ready for input.html.twig.
*/
public static function preRenderDropzoneJs($element) {
static::setAttributes($element, ['dropzone-enable']);
return $element;
}
}
<?php
/**
* @file
* Contains \Drupal\dropzonejs\DropzoneJsUploadException.
*/
namespace Drupal\dropzonejs;
use Symfony\Component\HttpFoundation\JsonResponse;
class UploadExceptionn extends \Exception {
/**
* Error with input stream.
*/
const INPUT_ERROR = 101;
/**
* Error with output stream.
*/
const OUTPUT_ERROR = 102;
/**
* Error moving uploaded file.
*/
const MOVE_ERROR = 103;
/**
* Error with destination folder.
*/
const DESTINATION_FOLDER_ERROR = 104;
/**
* Error with temporary file name.
*/
const FILENAME_ERROR = 105;
/**
* Error with temporary file name.
*/
const REQUEST_ERROR = 106;
/**
* File upload resulted in error.
*/
const FILE_UPLOAD_ERROR = 107;
/**
* Code to error message mapping.
*
* @param array $code
*/
public $errorMessages = array(
self::INPUT_ERROR => 'Failed to open input stream.',
self::OUTPUT_ERROR => 'Failed to open output stream.',
self::MOVE_ERROR => 'Failed to move uploaded file.',
self::DESTINATION_FOLDER_ERROR => 'Failed to open temporary directory.',
self::FILENAME_ERROR => 'Invalid temporary file name.',
self::REQUEST_ERROR => 'The request does not contain a UploadedFile object.',
self::FILE_UPLOAD_ERROR => 'The file upload resulted in an error on php level. See http://php.net/manual/en/features.file-upload.errors.php',
);
/**
* Constructs UploadException.
*
* @param int $code
* Error code.
*/
public function __construct($code) {
$this->code = $code;
$this->message = $this->errorMessages[$this->code];
}
/**
* Generates and returns JSON response object for the error.
*
* @return \Symfony\Component\HttpFoundation\JsonResponse
* JSON response object.
*/
public function getErrorResponse() {
return new JsonResponse(
array(
'jsonrpc' => '2.0',
'error' => array(
'code' => $this->code,
'message' => $this->errorMessages[$this->code],
),
'id' => 'id',
),
500
);
}
}
{#
/**
* @file
* Theme implementation for the dropzonejs form element.
*
* Available variables:
* - attributes: A list of HTML attributes for the element.
* - children: Optional additional rendered elements.
* - uploaded_files: Hidden element that holds uploaded files.
*
* @see template_preprocess_dropzonejs()
*/
#}
<div{{ attributes }} /></div>
{{ uploaded_files }}
{{ children }}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment