Problem/Motivation
A form with a '#type' => 'file'
element that is required will always fail. Since there is no #value set (theoretically, there is no value at all) the validation will raise an error.
Steps to reproduce
1) Create a Form class
2) Add
$form['file_test_upload'] = [
'#type' => 'file',
'#title' => t('Upload a file'),
'#required' => TRUE,
];
to your buildForm method
3) Go to the form
4) Add a file to the field and submit it.
5) Notice the form are not able to be submitted because the file field is required.
Proposed resolution
Add a valueCallback on \Drupal\Core\Render\Element\File.
Remaining tasks
Change notice
Manual testing
Review
Commit
User interface changes
API changes
Elements of #type file
now have a #value
. This means that using $form_state->getValue('some_file_field')
will return an array of \Symfony\Component\HttpFoundation\File\UploadedFile
objects
Data model changes
Release notes snippet
Comment | File | Size | Author |
---|---|---|---|
#126 | Screencast_08_07_22_19:56:03.mkv | 120.01 KB | quietone |
#125 | 59750.jpeg | 30.1 KB | thidd |
#123 | 59750-123.patch | 5 KB | quietone |
| |||
#123 | 59750-123-fail.patch | 1.3 KB | quietone |
#123 | interdiff-101-123.txt | 574 bytes | quietone |
Comments
Comment #1
samc CreditAttribution: samc commentedBacklink to open flexinode issue: http://drupal.org/node/42762
Comment #2
adrian CreditAttribution: adrian commentedComment #3
chx CreditAttribution: chx commentedThis is not the place neither the time to fix file API. Everybody on its own. I am outta here. If the change gets in, I will doc the change.
Comment #4
killes@www.drop.org CreditAttribution: killes@www.drop.org commentedI am not too happy about it, but unless somethign better comes up, I will commit it.
Comment #5
Bèr Kessels CreditAttribution: Bèr Kessels commentedThis works. It fixes the issue.
but:
* Its a HACK, spelled in capitals all along. Whether or not we allow such a hack to get in to solve this issue, or not, is not up to me.
* Will it not open up a bit of a security hole? I mean: if we are making exceptions that will allow certain "things" not to pass by the validation, we are moving on slippery ice (or whatever the saying is).
but:
again: it solves the issue. Tested, and confirmed with a few flexinode file fields.
thanks. but I dare not give it a + or - 1. Such issues, IMO are for Dries to decide.....
Comment #6
Bèr Kessels CreditAttribution: Bèr Kessels commentedI forgot to mention, that there are other ways around this issue. For example image.module does this. They are not solutions, but simply ways to not run into this form api bug.
* do not set the required flag on a file field.
* then validate it yourself in a validation hook.
The downside is, that the "required" mark (by default the *) is not printed on the file form.
Comment #7
killes@www.drop.org CreditAttribution: killes@www.drop.org commentedMaybe we should simply document that you can't use required with file fields. Also, Ber coul dpass an attribute to his home-brew validation routine.
Since this issue does not affect core, I'd like to not patch core, but to simply update the docs. Opinions?
Comment #8
samc CreditAttribution: samc commentedLike the previous posters, I'm not crazy about the idea of introducing such a hack into core.
I may have found a potential workaround, at least as far as flexinode goes (but should be generally applicable).
Can you guys take a look at http://drupal.org/node/42762, #12. It seems to work fine... Am I missing something that makes this a bad approach?
It might be better to ensure that that type of approach is supported via forms system, rather than just bypass required checking for file fields.
What do you think?
Comment #9
moshe weitzman CreditAttribution: moshe weitzman commentedI'm OK with this patch for 4.7.0. We can make it work properly for 4.7.1 IMO
Comment #10
chx CreditAttribution: chx commentedWe agreed that the solution is that I am doccing right now that #type file can't be required.
Comment #11
Bèr Kessels CreditAttribution: Bèr Kessels commentedI am sure we agreed to lower the priority and move this to the "file system" component. "wont fixes" will be forgotton. This should not be forgotton.
For it remains a bug.
Comment #12
nedjoI marked http://drupal.org/node/64984 as a duplicate. There is a patch on that issue.
Comment #13
Bèr Kessels CreditAttribution: Bèr Kessels commentedAttached the patch by quicksketch on #64984
Comment #14
neclimdulPatch does not apply and a conversion of the patch does not seem to work.
Comment #15
bomarmonk CreditAttribution: bomarmonk commentedIs a patch applied for 4.7? It seems that is should be, as flexinode is still better at handling files and many sites are using it. This seems like a sad oversight... apply a patch to core that fixes this, please (easy for me to say, since I'm more of a bug hunter than a bug fixer). Just throwing my support at this as loudly as possible.
Comment #16
webchickThis is still an issue in 5.x and, presumably, HEAD.
This prevents programmatically creating node content with drupal_execute on imagefields, for example, when the field's #required property is set to TRUE.
Comment #17
ilmaestro CreditAttribution: ilmaestro commented...still an issue.
I'm sure it goes without saying, but stating that the #required setting is not allowable for file types is a pretty lousy solution.
Comment #18
jakeg CreditAttribution: jakeg commentedit may be a lousy option to say #required is not allowable for file types, but at the moment #required doesn't work for them and I've just spent hours trying to figure out where in my script I was going wrong not getting it working. I had already done my own workaround (also suggested above) of having my own #validate function and doing stuff in there, and not having the field #required, but the fact that the docs say #required is allowed is very confusing. For now at least, the docs must be changed to make #required not an option for 'file' form fields, otherwise other coders will face the same headache in figuring out where they've gone wrong in implementing it.
Comment #19
webchick@jakeg: Please update the docs. Anyone with a CVS account may do so. The file's in contributions/docs/developer/forms_api_reference.html, I believe.
Comment #20
jakeg CreditAttribution: jakeg commented@webchick: thanks, I didn't realise/think I had access. Have updated the docs:
http://cvs.drupal.org/viewvc.py/drupal/contributions/docs/developer/topi...
Comment #21
celstonvml CreditAttribution: celstonvml commentedSubscribing
Patch from post #13 appears to be working in my 5.7 instance.
Comment #22
quicksketchHa, this was "fixed" 3 years(!) ago in #64984-4: File Uploads fail when field is set to "required", but it never made it into core. Sad, sad. Here's a reroll of my old patch, not yet tested, but just putting this on the radar.
Comment #23
quicksketchUpdated patch that has been tested.
Comment #24
yonailo CreditAttribution: yonailo commented@jakeg: thanks for fixing up the documentation, it was really important IMHO and I have experienced this headache so I totally agree with #18
Comment #25
drewish CreditAttribution: drewish commentedsubscribing
Comment #26
drewish CreditAttribution: drewish commentedseems like we could use a unit test for this...
Comment #27
moshe weitzman CreditAttribution: moshe weitzman commentedFixes a bug that has pestered since FAPI 1.0. A test would be nice but squashing the bug is more important.
Comment #28
drewish CreditAttribution: drewish commentedi'd disagree since the whole point of the test is making sure it says unbroken.
Comment #29
webchickWow!! Great to see this bug fixed! But I agree that a test is mandatory.
Also, is there by chance a nicer way to get this value than $elements['#parents'][0]? that's not really at all intuitive.
Comment #30
sreynen CreditAttribution: sreynen commentedOn testing, the previous patch went too far and didn't throw an error when a required file field was actually missing a file. Updated patch, including a test.
Comment #32
sreynen CreditAttribution: sreynen commentedComment #34
moshe weitzman CreditAttribution: moshe weitzman commented@Scott - the bot tested today and this patch will not apply cleanly :(
Comment #35
sreynen CreditAttribution: sreynen commentedUpdated patch for most recent 7.x-dev release.
Comment #37
bryancasler CreditAttribution: bryancasler commentedIs this possible in 6.x?
Comment #38
vectoroc CreditAttribution: vectoroc commentedIncrease priority. Patch looks good.
Don't get why it still not fixed for years
Comment #39
kleinmp CreditAttribution: kleinmp commentedHere's the patch re-roled against the newest drupal 7 dev.
Comment #41
Damien Tournoud CreditAttribution: Damien Tournoud commentedThis is going to need to be fixed in Drupal 8 first.
Comment #42
kleinmp CreditAttribution: kleinmp commentedI re-roled the patch off of drupal 8 and hopefully fixed the simple test issue.
Comment #43
marcingy CreditAttribution: marcingy commentedSo we have a test for this already in file.test for the file module so I actually assume this is fixed in core....as a result closing as this is fixed.
Comment #45
vectoroc CreditAttribution: vectoroc commented@marcingy: how about D6 ?
Comment #46
vectoroc CreditAttribution: vectoroc commentedComment #47
catchMoving version then.
Comment #48
webchickuntagging for backport to D7.
Comment #49
Dave ReidNo, this is fixed for file_managed fields, but not for #type => 'file'. This is still a valid bug in D8 and below.
Comment #50
Dave ReidComment #51
quicksketchYeah the #type = managed_file element has mitigated this problem significantly, since most uploads would want to use the more advanced managed_file element anyway (who wouldn't want an AJAX-upload and progress bar?) I personally don't think this is a "major" issue any more and it's not affecting nearly as many sites now since File module is available in core to provide the majority use-case. This seems like just an inconvenience now.
Comment #52
Dave ReidFor some uses managed_file is not appropriate, but yeah I agree with the bump down to normal.
Comment #53
fromtheindia CreditAttribution: fromtheindia commented#39: file-field-required-59750-39.patch queued for re-testing.
Comment #55
kscheirer#42: file-field-required-59750-42.patch queued for re-testing.
Comment #57
rudiedirkx CreditAttribution: rudiedirkx commentedThis patch doesn't keep in mind
#name
changes. In a file input it's very useful to be able to name it to "files[photos][]" so you can upload multiple. (Yes, HTML has been doing that for a couple of years now.) It also doesn't handle files in a fieldset with#tree
=> TRUE.I've solved this differently: http://drupalcode.org/project/value_is.git/blob/refs/heads/7.x-1.x:/valu...
Comment #59
mvonfrie CreditAttribution: mvonfrie commentedI just got the exact same error in 7.43 as well.
Comment #67
kim.pepperThis needs a re-roll for 8.9.x.
Comment #68
KapilV CreditAttribution: KapilV as a volunteer and commentedComment #69
KapilV CreditAttribution: KapilV as a volunteer and commentedComment #70
larowlanComment #71
quietone CreditAttribution: quietone as a volunteer commentedDoes this mean a form with a file field that is required?
Comment #72
larowlanDiscussed this with @quietone
\Drupal\file_test\Form\FileTestForm
provides a sample form and\Drupal\Tests\file\Functional\SaveUploadTest
tests it.If we make the element there #required we should yield the bug.
I think we can solve this differently now that we have plugins for elements.
We can put a valueCallback on the element and populate it with the temporary file name from the request's file bag if a file with that name has been uploaded.
Comment #73
quietone CreditAttribution: quietone as a volunteer commentedThanks to @larowlan for proving the sample code for this in #bugsmash. With that I have made a rough patch as well as a fail patch. There is no interdiff because this is a different approach.
This makes the file field required for all the tests using FileTestForm. I haven't looked yet but is there a still a test without the field being required?
Comment #74
quietone CreditAttribution: quietone as a volunteer commentedAh, this should be on 9.3.x
Comment #75
quietone CreditAttribution: quietone as a volunteer commentedShould be @inheritdoc
Comment #76
LendudeYeah an probably better
Yeah and probably better/easier to split the required field off into its own form that you can use just for testing that
Comment #77
quietone CreditAttribution: quietone as a volunteer commentedOK.
I removed the addition of required in FileTestForm and added a new test form. Instead of making a new test file I added, testRequired, to the existing SaveUploadTest.php although I am not sure if that is the best way to do this.
Comment #79
quietone CreditAttribution: quietone as a volunteer commentedNot sure if there is a better way, this now checks that the files variable is of the UploadedFile class before treating it like an object.
Comment #81
quietone CreditAttribution: quietone as a volunteer commentedRestore the test that $all_files is not empty. Don't know why I removed it. Added comments to the test.
Comment #83
quietone CreditAttribution: quietone as a volunteer commentedChanging to use NULL coalesce on the file variable.
Comment #84
paulocsTest looks good and the problem is fixed.
I'm adding in the IS the steps to reproduce.
Comment #85
paulocsComment #86
quietone CreditAttribution: quietone as a volunteer commentedI'm adding credit to larowlan who provided the solution, and support, in bugsmash.
Comment #87
larowlanNit, should this be image instead of images?
Comment #88
quietone CreditAttribution: quietone as a volunteer commentedThe new test uses code from SaveUploadTest::testNormal and SaveUploadTest::testDuplicate and both of those use $image2 for the second image that is uploaded. For example, https://git.drupalcode.org/project/drupal/-/blob/9.3.x/core/modules/file.... I thought it best to kept to the existing style in the file.
Happy to change it though, shall I?
Comment #89
larowlanI think with it as image2 our future selves might wonder what happened to image 1, so its probably worth fixing for future comprehension sake
can be fixed on commit though
Comment #90
larowlanI'm going to ask chx on slack if he can have a look at this to make sure we're not doing anything wrong in the value callback
Comment #91
larowlanDiscussed this with @chx on slack and he pointed out a few minor code-style changes
But we thought that using #value for this might be risky, because file elements have never had a value.
So I've come up with a different approach, patch to follow
Comment #92
larowlanSomething like this perhaps
Comment #93
larowlanphpcs
Comment #95
larowlanSo I've thought about this some more and I think the approach of unsetting #required is wrong - as the tests show - because it means that the required state is not available to the theme system
So I think we should go back to the approach from #83, here's that patch plus some minor changes that chx identified on slack
Because this will mean that file elements now have a #value, we'll need a change record.
Updated the issue summary while here.
Thinking about this some more, should we put the UploadedFile object in the value instead of just the file name?
Comment #97
larowlanOk, those fails have convinced me, because with #multiple this needs to be an array.
So I'll make the #value an array of UploadedFile objects
Comment #98
kim.pepperFix unused import.
Comment #100
larowlanOk, that fail seems to point to somewhere we're using #value for a file element, in theme settings. So this is good, we might have found a possible regression
Comment #101
larowlanSo theme settings form has a #maxlength on a file upload field, which obviously makes no sense.
Removing it.
Comment #102
quietone CreditAttribution: quietone as a volunteer commentedAdded a change record.
Comment #103
neclimdul"Why is this in my dashboard? I don't remember running into this recently that's just the way things work. ... Oh, I commented _15 years ago_!"
This isn't so much a review of the patch but an observation about the form api.
This smells bad. Why do we need to look at the request? Makes something already difficult to test(I'm waiting on a test run for something I'm trying to write while writing this) and likely makes it harder. I know the answer is not the fault of this patch its just the only option. Should the FormState give us access to files the same way it does values? Probably. Is that likely to be hacky and ugly which is why it doesn't exist? Again probably but worth the effort in another patch/follow up I think.
This part was also confusing until I confirmed it matches logic duplicated in a couple places in file.module. Again not a problem with this patch but highlighting maybe some debt in FormApi's file handling that could be addressed. Again in formstate's interface?
This isn't going to be backported to 6 or 7 at this point I wager so a little tag cleanup as well.
Patch looks solid and skim of tests looks good. Wager this is good to go but smell makes me feel like it needs more manually testing than I can do in this review so leaving an rtbc to someone else or another review.
Comment #104
kattekrab CreditAttribution: kattekrab at Red Hat commentedThanks for the change record @quietone - Looks good. I'll remove the needs CR tag now.
I'm going to have a crack at a manual test, but it's been a while since I've done this. Wish me luck ;-)
Reading the issue thread here is like a who's who of Drupal!
It would be amazing to get this one smashed. :)
Comment #105
kattekrab CreditAttribution: kattekrab at Red Hat commentedComment #106
kattekrab CreditAttribution: kattekrab at Red Hat commentedComment #107
quietone CreditAttribution: quietone as a volunteer commentedAdd manual testing to the tasks.
Comment #108
mikeryanNot that anyone asked for this... But I'm working on a D7 project now that needs this, so here's a reroll of #42.
Comment #109
mikeryanOops...
Comment #111
mikeryanComment #112
mikeryanRestoring "Needs review", since my D7 testing bumped it back to "Needs work"...
Comment #113
quietone CreditAttribution: quietone as a volunteer commentedAdding tag.
Comment #114
Mir4 CreditAttribution: Mir4 as a volunteer commentedI have an issue with a part of the patch :
implode('_', $element['#parents'])
This part does not work for me because i use sub index in my form for file type (#tree = TRUE).
If i replace this by :
array_shift($element['#parents'])
It works for me. What do you think about this?
https://www.drupal.org/files/issues/2021-11-30/59750-112.patch
Comment #115
Mir4 CreditAttribution: Mir4 as a volunteer commenteddelete coment
Comment #116
S3b0uN3tAdd @Mir4 purposed patch in comment #114.
Comment #119
quietone CreditAttribution: quietone at PreviousNext commentedThe patch in #116 still applies to 9.5.x.
Adding interdiff.
Comment #120
kim.pepperDo we have an issue to move files to form state instead of calling this?
I think this needs a test-only patch.
Comment #121
ravi.shankar CreditAttribution: ravi.shankar at OpenSense Labs commentedAdded a test-only patch.
Comment #123
quietone CreditAttribution: quietone at PreviousNext commentedUpdating for 10.0.x and adding interdiffs. Interdiff.txt is between the success and fail patch. The other interdiff is highlight the changed suggested in #114 since there was not an interdiff in #116.
Comment #125
thidd CreditAttribution: thidd as a volunteer commentedThe patches
could be applied via composer but the feature still not work
Attached the image for your reference
Comment #126
quietone CreditAttribution: quietone at PreviousNext commented@mrddthi, thank you for your interest in this issue. What patch did you test? Your comment lists three patches.
I decided to retest with #123 on Drupal 10. It works fine for me, I even managed to make a screencast.
Comment #127
quietone CreditAttribution: quietone at PreviousNext commentedPatch applies to 9.5.x
Comment #128
smustgrave CreditAttribution: smustgrave at Mobomo commentedTested using the form api sub module from the examples module
Added the file type to a simple form confirmed the bug.
Applied the patch and the issue was resolved.
On Drupal 9.5.x
Comment #130
quietone CreditAttribution: quietone at PreviousNext commentedTest failure in unrelated file
then I found #3101130: ConfigEntityQueryTest::testCaseSensitivity can randomly fail. Maybe related?
Starting tests again.
Comment #131
smustgrave CreditAttribution: smustgrave at Mobomo commented#123 seemed to pass and test-only patch failed.
Comment #133
smustgrave CreditAttribution: smustgrave at Mobomo commentedMoving back as it seemed to be a random failure.
Comment #134
alexpottCommitted and pushed a5b44e8f74 to 10.1.x and 964a3f3aeb to 10.0.x and e3930ea53e to 9.5.x. Thanks!
Congrats to bugsmash for resurrecting and fixing this one. Always fun to commit a 5 digit NID :D!
Comment #138
PapaGrandeMaybe I missed this, but did the D7 patch in #111 also get committed?
Comment #139
alexpott@PapaGrande the process for backporting this to D7 would be to open another issue against Drupal 7 for this. I think this change is unlikely to make it D7 at this point but that is for a D7 maintainer to decide on the new issue. Also the patch in #111 is a very different solution from the final patch.
Comment #140
kattekrab CreditAttribution: kattekrab at Red Hat commentedWow!!! Amazing. Thanks everyone :)
Comment #141
quietone CreditAttribution: quietone at PreviousNext commentedThis was tested manually, removing tag.
Comment #143
codebymikey CreditAttribution: codebymikey at Zodiac Media commentedJust a note that this commit actually introduces a pretty big bug when the File form element has
#multiple
set tofalse
.Attempting to cast a complex object like this:
Will result in just the UploadedFile properties being outputed as the output array (https://stackoverflow.com/a/4345609).
The actual return value should be something along the lines of:
And only obvious when trying to read the file value from the form state.
Comment #144
yovinceyes, having some issues.
I have to set #multiple set to false, otherwise, I will get the error below
Exception: Serialization of 'Symfony\Component\HttpFoundation\File\UploadedFile' is not allowed in serialize() (line 157 of /app/docroot/core/lib/Drupal/Core/Batch/BatchStorage.php).
Comment #145
larowlan@codebymikey did you happen to open a new issue for that regression?
Comment #146
codebymikey CreditAttribution: codebymikey at Zodiac Media commented@larowlan yes, the regression issue has already referenced this issue, but I'll add a direct relation now.
Comment #147
larowlanthanks 🙌