diff --git a/core/includes/form.inc b/core/includes/form.inc index 8170820..91ffdb8 100644 --- a/core/includes/form.inc +++ b/core/includes/form.inc @@ -462,6 +462,21 @@ function template_preprocess_form_element(&$variables) { $variables['label'] = array('#theme' => 'form_element_label'); $variables['label'] += array_intersect_key($element, array_flip(array('#id', '#required', '#title', '#title_display'))); + // The element may have specified its label should refer to one of its + // children, or to a fixed string, rather than the default of itself. + if(!empty($element['#for'])) { + if(is_array($element['#for'])) { + // if #for is an array, it is interpreted as a reference to the #id of a + // child of $element. + $for_element = NestedArray::getValue($element, $element['#for']); + if(!empty($for_element['#id'])) { + $variables['label']['#for'] = $for_element['#id']; + } + } else { + $variables['label']['#for'] = $element['#for']; + } + } + $variables['children'] = $element['#children']; } diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php index 64e01e6..1e305dd 100644 --- a/core/lib/Drupal/Core/Form/FormBuilder.php +++ b/core/lib/Drupal/Core/Form/FormBuilder.php @@ -24,6 +24,7 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; +use Zend\Stdlib\ArrayUtils; /** * Provides form building and processing. @@ -783,12 +784,15 @@ public function doBuildForm($form_id, &$element, FormStateInterface &$form_state $element['#defaults_loaded'] = TRUE; } // Assign basic defaults common for all form elements. - $element += array( - '#required' => FALSE, - '#attributes' => array(), - '#title_display' => 'before', - '#description_display' => 'after', - '#errors' => NULL, + + $element = ArrayUtils::merge(array( + '#required' => FALSE, + '#attributes' => array(), + '#title_display' => 'before', + '#description_display' => 'after', + '#errors' => NULL, + ), + $element ); // Special handling if we're on the top level form element. 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..30e2d45 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,10 +103,10 @@ $('.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 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 + ')$', 'gi'); + if (!acceptableMatch.test(event.target.value)) { var error = Drupal.t("The selected file %filename cannot be uploaded. Only files with the following extensions are allowed: %extensions.", { // According to the specifications of HTML5, a file upload control // should not reveal the real local path to the file that a user @@ -129,7 +115,7 @@ // 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\\', ''), + '%filename': event.target.value.replace('C:\\fakepath\\', ''), '%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..666c322 100644 --- a/core/modules/file/src/Element/ManagedFile.php +++ b/core/modules/file/src/Element/ManagedFile.php @@ -275,9 +275,15 @@ public static function processManagedFile(&$element, FormStateInterface $form_st } // The file upload field itself. + $validate_attrs = []; + if(!empty($element['#upload_validators']['file_validate_extensions'])) { + $validate_attrs += ['data-drupal-validate-extensions' => implode(' ', $element['#upload_validators']['file_validate_extensions'])]; + } + $element['upload'] = [ '#name' => 'files[' . $parents_prefix . ']', '#type' => 'file', + '#attributes' => $validate_attrs, '#title' => t('Choose a file'), '#title_display' => 'invisible', '#size' => $element['#size'], @@ -304,16 +310,11 @@ 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']; + + // Let #for reference the file element by its array index. This causes + // the label for the overall manged file form element to reference the + // nested file input, see form.inc's template_preprocess_form_element. + $element['#for'] = array('upload'); // Prefix and suffix used for Ajax replacement. $element['#prefix'] = '
';