3 namespace Drupal\dropzonejs;
5 use Drupal\Component\Transliteration\TransliterationInterface;
6 use Drupal\Core\Config\ConfigFactoryInterface;
7 use Drupal\Core\Language\LanguageManagerInterface;
8 use Drupal\Core\StringTranslation\StringTranslationTrait;
9 use Symfony\Component\HttpFoundation\File\UploadedFile;
10 use Symfony\Component\HttpFoundation\RequestStack;
11 use Drupal\Component\Utility\Unicode;
14 * Handles files uploaded by Dropzone.
16 * The uploaded file will be stored in the configured tmp folder and will be
17 * added a tmp extension. Further filename processing will be done in
18 * Drupal\dropzonejs\Element::valueCallback. This means that the final
19 * filename will be provided only after that callback.
21 class UploadHandler implements UploadHandlerInterface {
23 use StringTranslationTrait;
26 * The current request.
28 * @var \Symfony\Component\HttpFoundation\Request
29 * The HTTP request object.
34 * Transliteration service.
36 * @var \Drupal\Core\Transliteration\PhpTransliteration
38 protected $transliteration;
41 * Language manager service.
43 * @var \Drupal\Core\Language\LanguageManagerInterface
45 protected $languageManager;
48 * The scheme (stream wrapper) used to store uploaded files.
52 protected $tmpUploadScheme;
55 * Constructs dropzone upload controller route controller.
57 * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
59 * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
61 * @param \Drupal\Component\Transliteration\TransliterationInterface $transliteration
62 * Transliteration service.
63 * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
64 * LanguageManager service.
66 public function __construct(RequestStack $request_stack, ConfigFactoryInterface $config_factory, TransliterationInterface $transliteration, LanguageManagerInterface $language_manager) {
67 $this->request = $request_stack->getCurrentRequest();
68 $this->transliteration = $transliteration;
69 $this->languageManager = $language_manager;
70 $this->tmpUploadScheme = $config_factory->get('dropzonejs.settings')->get('tmp_upload_scheme');
76 public function getFilename(UploadedFile $file) {
77 $original_name = $file->getClientOriginalName();
79 // There should be a filename and it should not contain a semicolon,
80 // which we use to separate filenames.
81 if (!isset($original_name)) {
82 throw new UploadException(UploadException::FILENAME_ERROR);
85 // @todo The following filename sanitization steps replicate the behaviour
86 // of the 2492171-28 patch for https://www.drupal.org/node/2492171.
87 // Try to reuse that code instead, once that issue is committed.
89 $langcode = $this->languageManager->getCurrentLanguage()->getId();
90 $filename = $this->transliteration->transliterate($original_name, $langcode, '');
92 // Replace whitespace.
93 $filename = str_replace(' ', '_', $filename);
94 // Remove remaining unsafe characters.
95 $filename = preg_replace('![^0-9A-Za-z_.-]!', '', $filename);
96 // Remove multiple consecutive non-alphabetical characters.
97 $filename = preg_replace('/(_)_+|(\.)\.+|(-)-+/', '\\1\\2\\3', $filename);
98 // Force lowercase to prevent issues on case-insensitive file systems.
99 $filename = Unicode::strtolower($filename);
101 // For security reasons append the txt extension. It will be removed in
102 // Drupal\dropzonejs\Element::valueCallback when we will know the valid
103 // extension and we will be able to properly sanitize the filename.
104 $processed_filename = $filename . '.txt';
106 return $processed_filename;
112 public function handleUpload(UploadedFile $file) {
114 $error = $file->getError();
115 if ($error != UPLOAD_ERR_OK) {
116 // Check for file upload errors and return FALSE for this file if a lower
117 // level system error occurred. For a complete list of errors:
118 // See http://php.net/manual/features.file-upload.errors.php.
120 case UPLOAD_ERR_INI_SIZE:
121 case UPLOAD_ERR_FORM_SIZE:
122 $message = $this->t('The file could not be saved because it exceeds the maximum allowed size for uploads.');
125 case UPLOAD_ERR_PARTIAL:
126 case UPLOAD_ERR_NO_FILE:
127 $message = $this->t('The file could not be saved because the upload did not complete.');
132 $message = $this->t('The file could not be saved. An unknown error has occurred.');
136 throw new UploadException(UploadException::FILE_UPLOAD_ERROR, $message);
140 $tmp = $this->tmpUploadScheme . '://' . $this->getFilename($file);
141 if (!($out = fopen($tmp, $this->request->request->get('chunk', 0) ? 'ab' : 'wb'))) {
142 throw new UploadException(UploadException::OUTPUT_ERROR);
145 // Read binary input stream.
146 $input_uri = $file->getFileInfo()->getRealPath();
147 if (!($in = fopen($input_uri, 'rb'))) {
148 throw new UploadException(UploadException::INPUT_ERROR);
151 // Append input stream to temp file.
152 while ($buff = fread($in, 4096)) {
156 // Be nice and keep everything nice and clean. Initial uploaded files are
157 // automatically removed by PHP at the end of the request so we don't need
159 // @todo when implementing multipart don't forget to drupal_unlink.