DropzoneJs.php 6.24 KB
Newer Older
1
2
3
4
5
6
7
8
9
<?php

/**
 * @file
 * Contains \Drupal\dropzonejs\src\Element.
 */

namespace Drupal\dropzonejs\Element;

10
use Drupal\Component\Utility\Bytes;
11
use Drupal\Component\Utility\NestedArray;
12
use Drupal\Core\Form\FormStateInterface;
13
use Drupal\Core\Render\Element\FormElement;
14
15
16
17

/**
 * Provides a DropzoneJS atop of the file element.
 *
Primsi's avatar
Primsi committed
18
19
20
21
22
23
24
25
 * Configuration options are:
 * - #title
 *   The main field title.
 * - #description
 *   Description under the field.
 * - #dropzone_description
 *   Will be visible inside the upload area.
 * - #max_filesize
26
27
 *   Used by dropzonejs and expressed in number + unit (i.e. 1.1M) This will be
 *   converted to a form that DropzoneJs understands. See:
Primsi's avatar
Primsi committed
28
 *   http://www.dropzonejs.com/#config-maxFilesize
29
30
 * - #extensions
 *   A string of valid extensions separated by a space.
Primsi's avatar
Primsi committed
31
 *
32
33
34
 * When submitted the element returns an array of temporary file locations. It's
 * the duty of the environment that implements this element to handle the
 * uploaded files.
35
 *
36
37
 * @FormElement("dropzonejs")
 */
38
class DropzoneJs extends FormElement {
39
40
41
42
43
44

  /**
   * A defualut set of valid extensions.
   */
  const DEFAULT_VALID_EXTENSIONS = 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp';

45
46
47
48
49
  /**
   * {@inheritdoc}
   */
  public function getInfo() {
    $class = get_class($this);
50
    return [
51
52
      '#input' => TRUE,
      '#multiple' => FALSE,
53
      '#process' => [[$class, 'processDropzoneJs']],
54
      '#size' => 60,
55
      '#pre_render' => [[$class, 'preRenderDropzoneJs']],
56
      '#theme' => 'dropzonejs',
57
      '#theme_wrappers' => ['form_element'],
58
      '#tree' => TRUE,
Primsi's avatar
Primsi committed
59
60
61
      '#attached' => [
        'library' => ['dropzonejs/dropzonejs', 'dropzonejs/integration']
      ],
62
    ];
63
64
65
66
67
68
69
70
71
  }

  /**
   * Processes a dropzone upload element, make use of #multiple if present.
   */
  public static function processDropzoneJs(&$element, FormStateInterface $form_state, &$complete_form) {
    $element['uploaded_files'] = [
      '#type' => 'hidden',
      // @todo Handle defaults.
72
      '#default_value' => '',
73
74
75
76
      // If we send a url with a token through drupalSettings the placeholder
      // doesn't get replaced, because the actual scripts markup is not there
      // yet. So we pass this information through a data attribute.
      '#attributes' => ['data-upload-path' => \Drupal::url('dropzonejs.upload')],
77
78
    ];

79
80
81
82
83
    if (!\Drupal::currentUser()->hasPermission('dropzone upload files')) {
      $element['#access'] = FALSE;
      drupal_set_message("You don't have sufficent permissions to use the DropzoneJS uploader. Contact your system administrator", 'warning');
    }

84
85
86
87
88
89
90
91
    return $element;
  }

  /**
   * Prepares a #type 'dropzone' render element for dropzonejs.html.twig.
   *
   * @param array $element
   *   An associative array containing the properties of the element.
Primsi's avatar
Primsi committed
92
93
   *   Properties used: #title, #description, #required, #attributes,
   *   #dropzone_description, #max_filesize.
94
95
96
97
98
   *
   * @return array
   *   The $element with prepared variables ready for input.html.twig.
   */
  public static function preRenderDropzoneJs($element) {
99
100
101
    // Convert the human size input to bytes, convert it to MB and round it.
    $max_size = round(Bytes::toInt($element['#max_filesize']) / pow(Bytes::KILOBYTE, 2), 2);

Primsi's avatar
Primsi committed
102
103
    $element['#attached']['drupalSettings']['dropzonejs'] = [
      'instances' => [
104
105
        // Configuration keys are matched with DropzoneJS configuration
        // options.
Primsi's avatar
Primsi committed
106
        $element['#id'] => [
107
          'maxFilesize' => $max_size,
108
          'dictDefaultMessage' => $element['#dropzone_description'],
109
          'acceptedFiles' => '.' . str_replace(' ', ',.', self::getValidExtensions($element)),
Primsi's avatar
Primsi committed
110
111
112
113
        ],
      ],
    ];

114
115
116
    static::setAttributes($element, ['dropzone-enable']);
    return $element;
  }
117
118
119
120
121
122

  /**
   * {@inheritdoc}
   */
  public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
    $file_names = [];
123
    $return['uploaded_files'] = NULL;
124
125

    if ($input !== FALSE) {
126
      $user_input = NestedArray::getValue($form_state->getUserInput(), $element['#parents'] + ['uploaded_files']);
127
128
129

      if (!empty($user_input['uploaded_files'])) {
        $file_names = array_filter(explode(';', $user_input['uploaded_files']));
130
131
        $tmp_override = \Drupal::config('dropzonejs.settings')->get('tmp_dir');
        $temp_path = ($tmp_override) ? $tmp_override : \Drupal::config('system.file')->get('path.temporary');
132
133

        foreach ($file_names as $name) {
134
135
136
137
138
139
140
141
          // The upload handler appended the txt extension to the file for
          // security reasons. We will remove it in this callback.
          $old_filepath = "$temp_path/$name";

          // The upload handler appended the txt extension to the file for
          // security reasons. Because here we know the acceptable extensions
          // we can remove that extension and sanitize the filename.
          $name = self::fixTmpFilename($name);
142
          $name = file_munge_filename($name, self::getValidExtensions($element));
143
144
145
146
147
148

          // Finaly rename the file and add it to results.
          $new_filepath = "$temp_path/$name";
          $move_result = file_unmanaged_move($old_filepath, $new_filepath);

          if ($move_result) {
149
150
151
152
            $return['uploaded_files'][] = [
              'path' => $move_result,
              'filename' => $name,
            ];
153
154
155
156
          }
          else {
            drupal_set_message(t('There was a problem while processing the file named @name', ['@name' => $name]), 'error');
          }
157
158
159
160
161
162
163
        }
      }
      $form_state->setValueForElement($element, $return);

      return $return;
    }
  }
164
165
166
167

  /**
   * Gets valid file extensions for this element.
   *
168
169
170
   * @param array $element
   *   The element array.
   *
171
172
173
   * @return string
   *   A space separated list of extensions.
   */
174
  public static function getValidExtensions($element) {
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
    return isset($element['#extensions']) ? $element['#extensions'] : self::DEFAULT_VALID_EXTENSIONS;
  }

  /**
   * Fix temporary filename.
   *
   * The upload handler appended the txt extension to the file for
   * security reasons.
   *
   * @param string $filename
   *   The filename we need to fix.
   *
   * @return string
   *   The fixed filename.
   */
  public static function fixTmpFilename($filename) {
    $parts = explode('.', $filename);
    array_pop($parts);
    return implode('.', $parts);
  }
195
}