Version 1
[yaffs-website] / web / modules / contrib / dropzonejs / src / UploadHandler.php
1 <?php
2
3 namespace Drupal\dropzonejs;
4
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;
12
13 /**
14  * Handles files uploaded by Dropzone.
15  *
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.
20  */
21 class UploadHandler implements UploadHandlerInterface {
22
23   use StringTranslationTrait;
24
25   /**
26    * The current request.
27    *
28    * @var \Symfony\Component\HttpFoundation\Request
29    *   The HTTP request object.
30    */
31   protected $request;
32
33   /**
34    * Transliteration service.
35    *
36    * @var \Drupal\Core\Transliteration\PhpTransliteration
37    */
38   protected $transliteration;
39
40   /**
41    * Language manager service.
42    *
43    * @var \Drupal\Core\Language\LanguageManagerInterface
44    */
45   protected $languageManager;
46
47   /**
48    * The scheme (stream wrapper) used to store uploaded files.
49    *
50    * @var string
51    */
52   protected $tmpUploadScheme;
53
54   /**
55    * Constructs dropzone upload controller route controller.
56    *
57    * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
58    *   The request stack.
59    * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
60    *   Config factory.
61    * @param \Drupal\Component\Transliteration\TransliterationInterface $transliteration
62    *   Transliteration service.
63    * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
64    *   LanguageManager service.
65    */
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');
71   }
72
73   /**
74    * {@inheritdoc}
75    */
76   public function getFilename(UploadedFile $file) {
77     $original_name = $file->getClientOriginalName();
78
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);
83     }
84
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.
88     // Transliterate.
89     $langcode = $this->languageManager->getCurrentLanguage()->getId();
90     $filename = $this->transliteration->transliterate($original_name, $langcode, '');
91
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);
100
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';
105
106     return $processed_filename;
107   }
108
109   /**
110    * {@inheritdoc}
111    */
112   public function handleUpload(UploadedFile $file) {
113
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.
119       switch ($error) {
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.');
123           continue;
124
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.');
128           continue;
129
130         // Unknown error.
131         default:
132           $message = $this->t('The file could not be saved. An unknown error has occurred.');
133           continue;
134       }
135
136       throw new UploadException(UploadException::FILE_UPLOAD_ERROR, $message);
137     }
138
139     // Open temp file.
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);
143     }
144
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);
149     }
150
151     // Append input stream to temp file.
152     while ($buff = fread($in, 4096)) {
153       fwrite($out, $buff);
154     }
155
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
158     // to do that.
159     // @todo when implementing multipart don't forget to drupal_unlink.
160     fclose($in);
161     fclose($out);
162
163     return $tmp;
164   }
165
166 }