ead6be03830f7a669365b02381e21f3fe2e05f09
[yaffs-website] / web / modules / contrib / dropzonejs / src / Element / DropzoneJs.php
1 <?php
2
3 namespace Drupal\dropzonejs\Element;
4
5 use Drupal\Component\Utility\Bytes;
6 use Drupal\Component\Utility\Html;
7 use Drupal\Component\Utility\NestedArray;
8 use Drupal\Core\Form\FormStateInterface;
9 use Drupal\Core\Render\Element\FormElement;
10 use Drupal\Core\StringTranslation\TranslatableMarkup;
11 use Drupal\Core\Url;
12
13 /**
14  * Provides a DropzoneJS atop of the file element.
15  *
16  * Required options are:
17  * - #title (string)
18  *   The main field title.
19  * - #description (string)
20  *   Description under the field.
21  * - #dropzone_description (string)
22  *   Will be visible inside the upload area.
23  * - #max_filesize (string)
24  *   Used by dropzonejs and expressed in number + unit (i.e. 1.1M) This will be
25  *   converted to a form that DropzoneJs understands. See:
26  *   http://www.dropzonejs.com/#config-maxFilesize
27  * - #extensions (string)
28  *   A string of valid extensions separated by a space.
29  * - #max_files (integer)
30  *   Number of files that can be uploaded.
31  *   If < 1, there is no limit.
32  * - #clientside_resize (bool)
33  *   Whether or not to use DropzoneJS clientside resizing. It requires v4.4.0+
34  *   version of the library.
35  *
36  * Optional options are:
37  * - #resize_width (integer)
38  *   (optional) The maximum with in px. If omitted defaults to NULL.
39  * - #resize_height (integer)
40  *   (optional) The maximum height in px. If omitted defaults to NULL.
41  * - #resize_quality (float)
42  *   (optional) The quality of the resize. Accepts values from 0 - 1. Ie: 0.8.
43  *   Defautls to 1.
44  * - #resize_method (string)
45  *   (optional) Accepts 'contain', which scales the image, or 'crop' which crops
46  *   the image. Defaults to 'contain'.
47  * - #thumbnail_method (string).
48  *   (optional) Accepts 'contain', which scales the image, or 'crop' which crops
49  *   the image. Defaults to 'contain'.
50  *
51  * @todo Not sure about the version for clientside.
52  *
53  * When submitted the element returns an array of temporary file locations. It's
54  * the duty of the environment that implements this element to handle the
55  * uploaded files.
56  *
57  * @FormElement("dropzonejs")
58  */
59 class DropzoneJs extends FormElement {
60
61   /**
62    * A defualut set of valid extensions.
63    */
64   const DEFAULT_VALID_EXTENSIONS = 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp';
65
66   /**
67    * {@inheritdoc}
68    */
69   public function getInfo() {
70     $class = get_class($this);
71     return [
72       '#input' => TRUE,
73       '#process' => [[$class, 'processDropzoneJs']],
74       '#pre_render' => [[$class, 'preRenderDropzoneJs']],
75       '#theme' => 'dropzonejs',
76       '#theme_wrappers' => ['form_element'],
77       '#tree' => TRUE,
78       '#attached' => [
79         'library' => ['dropzonejs/integration'],
80       ],
81     ];
82   }
83
84   /**
85    * Processes a dropzone upload element.
86    */
87   public static function processDropzoneJs(&$element, FormStateInterface $form_state, &$complete_form) {
88     $element['uploaded_files'] = [
89       '#type' => 'hidden',
90       // @todo Handle defaults.
91       '#default_value' => '',
92       // If we send a url with a token through drupalSettings the placeholder
93       // doesn't get replaced, because the actual scripts markup is not there
94       // yet. So we pass this information through a data attribute.
95       '#attributes' => ['data-upload-path' => Url::fromRoute('dropzonejs.upload')->toString()],
96     ];
97
98     if (empty($element['#max_filesize'])) {
99       $element['#max_filesize'] = file_upload_max_size();
100     }
101
102     // Set #max_files to NULL (explicitly unlimited) if #max_files is not
103     // specified.
104     if (empty($element['#max_files'])) {
105       $element['#max_files'] = NULL;
106     }
107
108     if (!\Drupal::currentUser()->hasPermission('dropzone upload files')) {
109       $element['#access'] = FALSE;
110       drupal_set_message(new TranslatableMarkup("You don't have sufficent permissions to use the DropzoneJS uploader. Contact your system administrator"), 'warning');
111     }
112
113     return $element;
114   }
115
116   /**
117    * Prepares a #type 'dropzone' render element for dropzonejs.html.twig.
118    *
119    * @param array $element
120    *   An associative array containing the properties of the element.
121    *   Properties used: #title, #description, #required, #attributes,
122    *   #dropzone_description, #max_filesize.
123    *
124    * @return array
125    *   The $element with prepared variables ready for input.html.twig.
126    */
127   public static function preRenderDropzoneJs(array $element) {
128     // Convert the human size input to bytes, convert it to MB and round it.
129     $max_size = round(Bytes::toInt($element['#max_filesize']) / pow(Bytes::KILOBYTE, 2), 2);
130
131     $element['#attached']['drupalSettings']['dropzonejs'] = [
132       'instances' => [
133         // Configuration keys are matched with DropzoneJS configuration
134         // options.
135         $element['#id'] => [
136           'maxFilesize' => $max_size,
137           'dictDefaultMessage' => Html::escape($element['#dropzone_description']),
138           'acceptedFiles' => '.' . str_replace(' ', ',.', self::getValidExtensions($element)),
139           'maxFiles' => $element['#max_files'],
140         ],
141       ],
142     ];
143
144     if (!empty($element['#clientside_resize'])) {
145       $element['#attached']['drupalSettings']['dropzonejs']['instances'][$element['#id']] += [
146         'resizeWidth' => !empty($element['#resize_width']) ? $element['#resize_width'] : NULL,
147         'resizeHeight' => !empty($element['#resize_height']) ? $element['#resize_height'] : NULL,
148         'resizeQuality' => !empty($element['#resize_quality']) ? $element['#resize_quality'] : 1,
149         'resizeMethod' => !empty($element['#resize_method']) ? $element['#resize_method'] : 'contain',
150         'thumbnailMethod' => !empty($element['#thumbnail_method']) ? $element['#thumbnail_method'] : 'contain',
151       ];
152       array_unshift($element['#attached']['library'], 'dropzonejs/exif-js');
153     }
154
155     static::setAttributes($element, ['dropzone-enable']);
156     return $element;
157   }
158
159   /**
160    * {@inheritdoc}
161    */
162   public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
163     $return['uploaded_files'] = [];
164
165     if ($input !== FALSE) {
166       $user_input = NestedArray::getValue($form_state->getUserInput(), $element['#parents'] + ['uploaded_files']);
167
168       if (!empty($user_input['uploaded_files'])) {
169         $file_names = array_filter(explode(';', $user_input['uploaded_files']));
170         $tmp_upload_scheme = \Drupal::configFactory()->get('dropzonejs.settings')->get('tmp_upload_scheme');
171
172         foreach ($file_names as $name) {
173           // The upload handler appended the txt extension to the file for
174           // security reasons. We will remove it in this callback.
175           $old_filepath = $tmp_upload_scheme . '://' . $name;
176
177           // The upload handler appended the txt extension to the file for
178           // security reasons. Because here we know the acceptable extensions
179           // we can remove that extension and sanitize the filename.
180           $name = self::fixTmpFilename($name);
181           $name = file_munge_filename($name, self::getValidExtensions($element));
182
183           // Potentially we moved the file already, so let's check first whether
184           // we still have to move.
185           if (file_exists($old_filepath)) {
186             // Finaly rename the file and add it to results.
187             $new_filepath = $tmp_upload_scheme . '://' . $name;
188             $move_result = file_unmanaged_move($old_filepath, $new_filepath);
189
190             if ($move_result) {
191               $return['uploaded_files'][] = [
192                 'path' => $move_result,
193                 'filename' => $name,
194               ];
195             }
196             else {
197               drupal_set_message(self::t('There was a problem while processing the file named @name', ['@name' => $name]), 'error');
198             }
199           }
200         }
201       }
202       $form_state->setValueForElement($element, $return);
203     }
204     return $return;
205   }
206
207   /**
208    * Gets valid file extensions for this element.
209    *
210    * @param array $element
211    *   The element array.
212    *
213    * @return string
214    *   A space separated list of extensions.
215    */
216   public static function getValidExtensions(array $element) {
217     return isset($element['#extensions']) ? $element['#extensions'] : self::DEFAULT_VALID_EXTENSIONS;
218   }
219
220   /**
221    * Fix temporary filename.
222    *
223    * The upload handler appended the txt extension to the file for
224    * security reasons.
225    *
226    * @param string $filename
227    *   The filename we need to fix.
228    *
229    * @return string
230    *   The fixed filename.
231    */
232   public static function fixTmpFilename($filename) {
233     $parts = explode('.', $filename);
234     array_pop($parts);
235     return implode('.', $parts);
236   }
237
238 }