Version 1
[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  * Configuration options are:
17  * - #title
18  *   The main field title.
19  * - #description
20  *   Description under the field.
21  * - #dropzone_description
22  *   Will be visible inside the upload area.
23  * - #max_filesize
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
28  *   A string of valid extensions separated by a space.
29  * - #max_files
30  *   Number of files that can be uploaded.
31  *   If < 1, there is no limit.
32  *
33  * When submitted the element returns an array of temporary file locations. It's
34  * the duty of the environment that implements this element to handle the
35  * uploaded files.
36  *
37  * @FormElement("dropzonejs")
38  */
39 class DropzoneJs extends FormElement {
40
41   /**
42    * A defualut set of valid extensions.
43    */
44   const DEFAULT_VALID_EXTENSIONS = 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp';
45
46   /**
47    * {@inheritdoc}
48    */
49   public function getInfo() {
50     $class = get_class($this);
51     return [
52       '#input' => TRUE,
53       '#process' => [[$class, 'processDropzoneJs']],
54       '#pre_render' => [[$class, 'preRenderDropzoneJs']],
55       '#theme' => 'dropzonejs',
56       '#theme_wrappers' => ['form_element'],
57       '#tree' => TRUE,
58       '#attached' => [
59         'library' => ['dropzonejs/integration'],
60       ],
61     ];
62   }
63
64   /**
65    * Processes a dropzone upload element.
66    */
67   public static function processDropzoneJs(&$element, FormStateInterface $form_state, &$complete_form) {
68     $element['uploaded_files'] = [
69       '#type' => 'hidden',
70       // @todo Handle defaults.
71       '#default_value' => '',
72       // If we send a url with a token through drupalSettings the placeholder
73       // doesn't get replaced, because the actual scripts markup is not there
74       // yet. So we pass this information through a data attribute.
75       '#attributes' => ['data-upload-path' => Url::fromRoute('dropzonejs.upload')->toString()],
76     ];
77
78     if (empty($element['#max_filesize'])) {
79       $element['#max_filesize'] = file_upload_max_size();
80     }
81
82     // Set #max_files to NULL (explicitly unlimited) if #max_files is not
83     // specified.
84     if (empty($element['#max_files'])) {
85       $element['#max_files'] = NULL;
86     }
87
88     if (!\Drupal::currentUser()->hasPermission('dropzone upload files')) {
89       $element['#access'] = FALSE;
90       drupal_set_message(new TranslatableMarkup("You don't have sufficent permissions to use the DropzoneJS uploader. Contact your system administrator"), 'warning');
91     }
92
93     return $element;
94   }
95
96   /**
97    * Prepares a #type 'dropzone' render element for dropzonejs.html.twig.
98    *
99    * @param array $element
100    *   An associative array containing the properties of the element.
101    *   Properties used: #title, #description, #required, #attributes,
102    *   #dropzone_description, #max_filesize.
103    *
104    * @return array
105    *   The $element with prepared variables ready for input.html.twig.
106    */
107   public static function preRenderDropzoneJs(array $element) {
108     // Convert the human size input to bytes, convert it to MB and round it.
109     $max_size = round(Bytes::toInt($element['#max_filesize']) / pow(Bytes::KILOBYTE, 2), 2);
110
111     $element['#attached']['drupalSettings']['dropzonejs'] = [
112       'instances' => [
113         // Configuration keys are matched with DropzoneJS configuration
114         // options.
115         $element['#id'] => [
116           'maxFilesize' => $max_size,
117           'dictDefaultMessage' => Html::escape($element['#dropzone_description']),
118           'acceptedFiles' => '.' . str_replace(' ', ',.', self::getValidExtensions($element)),
119           'maxFiles' => $element['#max_files'],
120         ],
121       ],
122     ];
123
124     static::setAttributes($element, ['dropzone-enable']);
125     return $element;
126   }
127
128   /**
129    * {@inheritdoc}
130    */
131   public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
132     $return['uploaded_files'] = [];
133
134     if ($input !== FALSE) {
135       $user_input = NestedArray::getValue($form_state->getUserInput(), $element['#parents'] + ['uploaded_files']);
136
137       if (!empty($user_input['uploaded_files'])) {
138         $file_names = array_filter(explode(';', $user_input['uploaded_files']));
139         $tmp_upload_scheme = \Drupal::configFactory()->get('dropzonejs.settings')->get('tmp_upload_scheme');
140
141         foreach ($file_names as $name) {
142           // The upload handler appended the txt extension to the file for
143           // security reasons. We will remove it in this callback.
144           $old_filepath = $tmp_upload_scheme . '://' . $name;
145
146           // The upload handler appended the txt extension to the file for
147           // security reasons. Because here we know the acceptable extensions
148           // we can remove that extension and sanitize the filename.
149           $name = self::fixTmpFilename($name);
150           $name = file_munge_filename($name, self::getValidExtensions($element));
151
152           // Potentially we moved the file already, so let's check first whether
153           // we still have to move.
154           if (file_exists($old_filepath)) {
155             // Finaly rename the file and add it to results.
156             $new_filepath = $tmp_upload_scheme . '://' . $name;
157             $move_result = file_unmanaged_move($old_filepath, $new_filepath);
158
159             if ($move_result) {
160               $return['uploaded_files'][] = [
161                 'path' => $move_result,
162                 'filename' => $name,
163               ];
164             }
165             else {
166               drupal_set_message(self::t('There was a problem while processing the file named @name', ['@name' => $name]), 'error');
167             }
168           }
169         }
170       }
171       $form_state->setValueForElement($element, $return);
172     }
173     return $return;
174   }
175
176   /**
177    * Gets valid file extensions for this element.
178    *
179    * @param array $element
180    *   The element array.
181    *
182    * @return string
183    *   A space separated list of extensions.
184    */
185   public static function getValidExtensions(array $element) {
186     return isset($element['#extensions']) ? $element['#extensions'] : self::DEFAULT_VALID_EXTENSIONS;
187   }
188
189   /**
190    * Fix temporary filename.
191    *
192    * The upload handler appended the txt extension to the file for
193    * security reasons.
194    *
195    * @param string $filename
196    *   The filename we need to fix.
197    *
198    * @return string
199    *   The fixed filename.
200    */
201   public static function fixTmpFilename($filename) {
202     $parts = explode('.', $filename);
203     array_pop($parts);
204     return implode('.', $parts);
205   }
206
207 }