From a5b44e8f740c6fc5f50fba1289962176293c52ee Mon Sep 17 00:00:00 2001
From: Alex Pott <alex.a.pott@googlemail.com>
Date: Mon, 3 Oct 2022 20:21:47 +0100
Subject: [PATCH] =?UTF-8?q?Issue=20#59750=20by=20quietone,=20larowlan,=20m?=
 =?UTF-8?q?ikeryan,=20quicksketch,=20sreynen,=20kleinmp,=20kim.pepper,=20B?=
 =?UTF-8?q?=C3=A8r=20Kessels,=20chx,=20S3b0uN3t,=20ravi.shankar,=20mrddthi?=
 =?UTF-8?q?,=20webchick,=20neclimdul,=20Lendude:=20Required=20flag=20on=20?=
 =?UTF-8?q?file=20forms=20breaks=20on=20validation?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 core/lib/Drupal/Core/Render/Element/File.php  | 26 +++++++++++++++++
 .../tests/file_test/file_test.routing.yml     |  6 ++++
 .../src/Form/FileRequiredTestForm.php         | 28 +++++++++++++++++++
 .../tests/src/Functional/SaveUploadTest.php   | 22 +++++++++++++++
 .../system/src/Form/ThemeSettingsForm.php     |  1 -
 5 files changed, 82 insertions(+), 1 deletion(-)
 create mode 100644 core/modules/file/tests/file_test/src/Form/FileRequiredTestForm.php

diff --git a/core/lib/Drupal/Core/Render/Element/File.php b/core/lib/Drupal/Core/Render/Element/File.php
index c909b926deaf..315817319862 100644
--- a/core/lib/Drupal/Core/Render/Element/File.php
+++ b/core/lib/Drupal/Core/Render/Element/File.php
@@ -15,6 +15,10 @@
  * - #multiple: A Boolean indicating whether multiple files may be uploaded.
  * - #size: The size of the file input element in characters.
  *
+ * The value of this form element will always be an array of
+ * \Symfony\Component\HttpFoundation\File\UploadedFile objects, regardless of
+ * whether #multiple is TRUE or FALSE
+ *
  * @FormElement("file")
  */
 class File extends FormElement {
@@ -36,6 +40,9 @@ public function getInfo() {
       ],
       '#theme' => 'input__file',
       '#theme_wrappers' => ['form_element'],
+      '#value_callback' => [
+        [$class, 'valueCallback'],
+      ],
     ];
   }
 
@@ -72,4 +79,23 @@ public static function preRenderFile($element) {
     return $element;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
+    if ($input === FALSE) {
+      return NULL;
+    }
+    $parents = $element['#parents'];
+    $element_name = array_shift($parents);
+    $uploaded_files = \Drupal::request()->files->get('files', []);
+    $uploaded_file = $uploaded_files[$element_name] ?? NULL;
+    if ($uploaded_file) {
+      // Cast this to an array so that the structure is consistent regardless of
+      // whether #value is set or not.
+      return (array) $uploaded_file;
+    }
+    return NULL;
+  }
+
 }
diff --git a/core/modules/file/tests/file_test/file_test.routing.yml b/core/modules/file/tests/file_test/file_test.routing.yml
index 0505615a4e5a..20af642f92a5 100644
--- a/core/modules/file/tests/file_test/file_test.routing.yml
+++ b/core/modules/file/tests/file_test/file_test.routing.yml
@@ -10,3 +10,9 @@ file.save_upload_from_form_test:
     _form: '\Drupal\file_test\Form\FileTestSaveUploadFromForm'
   requirements:
     _access: 'TRUE'
+file.required_test:
+  path: '/file-test/upload_required'
+  defaults:
+    _form: '\Drupal\file_test\Form\FileRequiredTestForm'
+  requirements:
+    _access: 'TRUE'
diff --git a/core/modules/file/tests/file_test/src/Form/FileRequiredTestForm.php b/core/modules/file/tests/file_test/src/Form/FileRequiredTestForm.php
new file mode 100644
index 000000000000..c6dca9b0f689
--- /dev/null
+++ b/core/modules/file/tests/file_test/src/Form/FileRequiredTestForm.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace Drupal\file_test\Form;
+
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * File required test form class.
+ */
+class FileRequiredTestForm extends FileTestForm {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return '_file_required_test_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $form = parent::buildForm($form, $form_state);
+    $form['file_test_upload']['#required'] = TRUE;
+    return $form;
+  }
+
+}
diff --git a/core/modules/file/tests/src/Functional/SaveUploadTest.php b/core/modules/file/tests/src/Functional/SaveUploadTest.php
index 3fa9f3d8f76f..4a70d2d1c5a6 100644
--- a/core/modules/file/tests/src/Functional/SaveUploadTest.php
+++ b/core/modules/file/tests/src/Functional/SaveUploadTest.php
@@ -732,4 +732,26 @@ public function testInvalidUtf8FilenameUpload() {
     $this->assertFileDoesNotExist('temporary://' . $filename);
   }
 
+  /**
+   * Tests the file_save_upload() function when the field is required.
+   */
+  public function testRequired() {
+    // Reset the hook counters to get rid of the 'load' we just called.
+    file_test_reset();
+
+    // Confirm the field is required.
+    $this->drupalGet('file-test/upload_required');
+    $this->submitForm([], 'Submit');
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->responseContains('field is required');
+
+    // Confirm that uploading another file works.
+    $image = current($this->drupalGetTestFiles('image'));
+    $edit = ['files[file_test_upload]' => \Drupal::service('file_system')->realpath($image->uri)];
+    $this->drupalGet('file-test/upload_required');
+    $this->submitForm($edit, 'Submit');
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->responseContains('You WIN!');
+  }
+
 }
diff --git a/core/modules/system/src/Form/ThemeSettingsForm.php b/core/modules/system/src/Form/ThemeSettingsForm.php
index 00d6b28c71e9..3d044f5c28cc 100644
--- a/core/modules/system/src/Form/ThemeSettingsForm.php
+++ b/core/modules/system/src/Form/ThemeSettingsForm.php
@@ -234,7 +234,6 @@ public function buildForm(array $form, FormStateInterface $form_state, $theme =
       $form['logo']['settings']['logo_upload'] = [
         '#type' => 'file',
         '#title' => $this->t('Upload logo image'),
-        '#maxlength' => 40,
         '#description' => $this->t("If you don't have direct file access to the server, use this field to upload your logo."),
         '#upload_validators' => [
           'file_validate_is_image' => [],
-- 
GitLab