diff --git a/core/lib/Drupal/Core/Render/Element/File.php b/core/lib/Drupal/Core/Render/Element/File.php
index b8d5263..e18564d 100644
--- a/core/lib/Drupal/Core/Render/Element/File.php
+++ b/core/lib/Drupal/Core/Render/Element/File.php
@@ -49,7 +49,7 @@ public function getInfo() {
*/
public static function processFile(&$element, FormStateInterface $form_state, &$complete_form) {
if ($element['#multiple']) {
- $element['#attributes'] = array('multiple' => 'multiple');
+ $element['#attributes'] += array('multiple' => 'multiple');
$element['#name'] .= '[]';
}
return $element;
diff --git a/core/modules/file/file.js b/core/modules/file/file.js
index 5a4aea8..abf99fe 100644
--- a/core/modules/file/file.js
+++ b/core/modules/file/file.js
@@ -17,35 +17,21 @@
* @type {Drupal~behavior}
*/
Drupal.behaviors.fileValidateAutoAttach = {
- attach: function (context, settings) {
+ attach: function (context) {
var $context = $(context);
- var elements;
-
- function initFileValidation(selector) {
- $context.find(selector)
- .once('fileValidate')
- .on('change.fileValidate', {extensions: elements[selector]}, Drupal.file.validateExtension);
- }
- if (settings.file && settings.file.elements) {
- elements = settings.file.elements;
- Object.keys(elements).forEach(initFileValidation);
- }
+ $context.find('.js-form-managed-file .js-form-file')
+ .once('fileValidate')
+ .on('change.fileValidate', Drupal.file.validateExtension);
},
detach: function (context, settings, trigger) {
var $context = $(context);
- var elements;
- function removeFileValidation(selector) {
- $context.find(selector)
+ if (trigger === 'unload') {
+ $context.find('.js-form-managed-file .js-form-file')
.removeOnce('fileValidate')
.off('change.fileValidate', Drupal.file.validateExtension);
}
-
- if (trigger === 'unload' && settings.file && settings.file.elements) {
- elements = settings.file.elements;
- Object.keys(elements).forEach(removeFileValidation);
- }
}
};
@@ -117,11 +103,19 @@
$('.file-upload-js-error').remove();
// Add client side validation for the input[type=file].
- var extensionPattern = event.data.extensions.replace(/,\s*/g, '|');
- if (extensionPattern.length > 1 && this.value.length > 0) {
- var acceptableMatch = new RegExp('\\.(' + extensionPattern + ')$', 'gi');
- if (!acceptableMatch.test(this.value)) {
- var error = Drupal.t("The selected file %filename cannot be uploaded. Only files with the following extensions are allowed: %extensions.", {
+ var extensionPattern = $(event.target).attr('data-drupal-validate-extensions').replace(/,|\s+/g, '|');
+ if (extensionPattern.length > 1 && event.target.value.length > 0) {
+ var acceptableMatch = new RegExp('\\.(' + extensionPattern + ')$', 'i');
+ var testSubjects;
+ if(event.target.files) {
+ testSubjects = event.target.files;
+ } else {
+ testSubjects = [{name: event.target.value}];
+ }
+ var invalidFiles = [];
+ for(var i = 0; i < testSubjects.length; i++) {
+ var testSubject = testSubjects[i];
+ if(!acceptableMatch.test(testSubject.name)) {
// According to the specifications of HTML5, a file upload control
// should not reveal the real local path to the file that a user
// has selected. Some web browsers implement this restriction by
@@ -129,7 +123,22 @@
// confusion by leaving the user thinking perhaps Drupal could not
// find the file because it messed up the file path. To avoid this
// confusion, therefore, we strip out the bogus fakepath string.
- '%filename': this.value.replace('C:\\fakepath\\', ''),
+ invalidFiles.push(testSubject.name.replace('C:\\fakepath\\', ''));
+ }
+ }
+ if (invalidFiles.length) {
+ var error = Drupal.formatPlural(invalidFiles.length, 'The selected file %filename cannot be uploaded.', '@count of the selected files cannot be uploaded.', {
+ '%filename': invalidFiles[0]
+ });
+ if(invalidFiles.length > 1) {
+ error += '
';
+ for(var j = 0; j < invalidFiles.length; j++) {
+ error += '- ' + Drupal.formatString('%filename', {'%filename': invalidFiles[j]}) + '
';
+ }
+ error += '
';
+ }
+ error += ' ';
+ error += Drupal.t("Only files with the following extensions are allowed: %extensions.", {
'%extensions': extensionPattern.replace(/\|/g, ', ')
});
$(this).closest('div.js-form-managed-file').prepend('' + error + '
');
diff --git a/core/modules/file/src/Element/ManagedFile.php b/core/modules/file/src/Element/ManagedFile.php
index c2fa103..d7d41ef 100644
--- a/core/modules/file/src/Element/ManagedFile.php
+++ b/core/modules/file/src/Element/ManagedFile.php
@@ -305,12 +305,6 @@ public static function processManagedFile(&$element, FormStateInterface $form_st
}
}
- // Add the extension list to the page as JavaScript settings.
- if (isset($element['#upload_validators']['file_validate_extensions'][0])) {
- $extension_list = implode(',', array_filter(explode(' ', $element['#upload_validators']['file_validate_extensions'][0])));
- $element['upload']['#attached']['drupalSettings']['file']['elements']['#' . $element['#id']] = $extension_list;
- }
-
// Let #id point to the file element, so the field label's 'for' corresponds
// with it.
$element['#id'] = &$element['upload']['#id'];
diff --git a/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php b/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php
index 137d841..abd0eec 100644
--- a/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php
+++ b/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php
@@ -237,6 +237,7 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
'#type' => 'managed_file',
'#upload_location' => $items[$delta]->getUploadLocation(),
'#upload_validators' => $items[$delta]->getUploadValidators(),
+ '#after_build' => array(array($this, 'addValidateDataForJs')),
'#value_callback' => array(get_class($this), 'value'),
'#process' => array_merge($element_info['#process'], array(array(get_class($this), 'process'))),
'#progress_indicator' => $this->getSetting('progress_indicator'),
@@ -279,6 +280,22 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen
}
/**
+ * Form API callback. Attaches data- attributes to the file input field
+ * for JavaScript's benefit.
+ *
+ * @param array $element The FileWidget's render array
+ * @param FormStateInterface $form_state
+ * @return array The updated render array
+ */
+ public function addValidateDataForJs($element, FormStateInterface &$form_state) {
+ // file extensions go on the ManagedFile's 'upload' child as data- attribute
+ if (is_array($element['upload']) && !empty($element['#upload_validators']['file_validate_extensions'])) {
+ $element['upload']['#attributes']['data-drupal-validate-extensions'] = $element['#upload_validators']['file_validate_extensions'][0];
+ }
+ return $element;
+ }
+
+ /**
* {@inheritdoc}
*/
public function massageFormValues(array $values, array $form, FormStateInterface $form_state) {
diff --git a/core/modules/file/src/Tests/FileFieldWidgetTest.php b/core/modules/file/src/Tests/FileFieldWidgetTest.php
index b696596..e3d2626 100644
--- a/core/modules/file/src/Tests/FileFieldWidgetTest.php
+++ b/core/modules/file/src/Tests/FileFieldWidgetTest.php
@@ -347,6 +347,9 @@ function testWidgetValidation() {
$test_file_image = $this->getTestFile('image');
$name = 'files[' . $field_name . '_0]';
+ // check field is marked with expected attr for client-side validation
+ $this->assertFieldByXPath('//input[@type="file" and @data-drupal-validate-extensions="txt"]', null, 'data-drupal-validate-extensions is present');
+
// Upload file with incorrect extension, check for validation error.
$edit[$name] = drupal_realpath($test_file_image->getFileUri());
switch ($type) {
diff --git a/core/modules/image/src/Tests/ImageFieldDisplayTest.php b/core/modules/image/src/Tests/ImageFieldDisplayTest.php
index 1e1d798..b502f67 100644
--- a/core/modules/image/src/Tests/ImageFieldDisplayTest.php
+++ b/core/modules/image/src/Tests/ImageFieldDisplayTest.php
@@ -315,8 +315,13 @@ function testImageFieldSettings() {
'files[' . $field_name . '_2][]' => drupal_realpath($test_image->uri),
);
$this->drupalPostAjaxForm(NULL, $edit, $field_name . '_2_upload_button');
- $this->assertNoRaw('');
- $this->assertRaw('');
+ $this->assertNoFieldByXPath('//input[@id="edit-' . strtr($field_name, '_', '-') . '-2-upload"]', 'Upload field for second file not present after it is uploaded.');
+
+ $this->assertTrue($this->cssSelect('input#edit-' . strtr($field_name, '_', '-') . '-3-upload[multiple=multiple]'), 'Third file field presented having multiple attribute');
+ $this->assertFieldByName('files[' . $field_name . '_3][]', null, 'Third file field has correct name');
+ $this->assertTrue($this->cssSelect('input[id=edit-' . strtr($field_name, '_', '-') . '-3-upload][size=22]'), 'Third file field presented having correct size attribute');
+ $this->assertTrue($this->cssSelect('input.js-form-file.form-file[id=edit-' . strtr($field_name, '_', '-') . '-3-upload]'), 'Third file field presented having correct css classes');
+ $this->assertTrue($this->cssSelect('input[id=edit-' . strtr($field_name, '_', '-') . "-3-upload][data-drupal-validate-extensions=$test_image_extension]"), 'Third file field presented having correct data-drupal-validate-extensions');
}
/**