Problem/Motivation
When a form element of '#type' => 'vertical_tab'
has '#access' => FALSE
, none of the elements contained by that vertical tab are present in the submitted values.
The docs for #access
say:
Whether the element is accessible or not; when FALSE, the element is not rendered and the user submitted value is not taken into consideration.
They don't clarify what happens to the #default_value
, but after consulting chx and davereid on IRC, I was assured that the default values would still be in the submitted values:
davereid: I would assume any element output with #access = FALSE to still retain it's FAPI #default_value on submission.
Steps to Reproduce
Write a custom module called access_false.module, with the following code:
/**
* Implements hook_form_BASE_FORM_ID_alter() for node_form.
*/
function access_false_form_node_form_alter(&$form, &$form_state, $form_id) {
$form['additional_settings']['#access'] = FALSE;
}
And then try to save a node. It will be unpublished, regardless of the default publishing settings for that content type.
Proposed resolution
TBD
Remaining tasks
Write test- TBD
User interface changes
N/A
API changes
Any custom code relying on this bug (denying access to a vertical tab to suppress all values including defaults) will break.
Comment | File | Size | Author |
---|---|---|---|
#15 | drupal-1611954-15.patch | 7.36 KB | tim.plunkett |
#24 | drupal8.vertical-tabs-access.24.patch | 536 bytes | sun |
#32 | drupal8.vertical-tabs-access.32.patch | 561 bytes | sun |
#38 | drupal8.vertical-tabs-access.38.patch | 752 bytes | mgifford |
#42 | drupal8.vertical-tabs-access.40-test-only.patch | 7.44 KB | samuel.mortenson |
Comments
Comment #2
tim.plunkettOkay, I moved this out of FormsElementsVerticalTabsFunctionalTest and into its own class, and added checks for fieldset and container as well to prove this isn't broken elsewhere.
Comment #3
tim.plunkettJust a hunch...
Comment #5
tim.plunkettHeh, well it made MY testcase pass. Here's another attempted fix.
I added a test for a textfield within the same vertical_tab that the checkbox fails in, and it works fine.
So the combination of a checkbox inside a vertical tab fails.
Despite that being a very specific combination, it seems to be indicative of a deeper problem, so leaving at major.
Comment #7
tim.plunkettThis makes this specific test pass, but will likely make others fail.
This boils down to a conflict between _form_builder_handle_input_element and form_type_checkbox_value.
Comment #9
tim.plunkettOkay, I officially have no idea what I'm doing.
I blame _form_builder_handle_input_element though, it passes NULL for a checkbox when there wasn't actually input.
Comment #12
tim.plunkettThis is still expected to fail, just rerolling after the forms tests went PSR-0.
Comment #14
BerdirOk, identified the problem and I also have a patch that fixes the tests.
This is not specific to checkboxes in any way. The problem is that setting #access on a vertical tab is not passed through to the fieldsets that are displayed within that vertical tab. Which makes totally sense, because they are, at that point (in form_builder()) not children of the vertical tab. They're only moved around later.
The result is that denying access does prevent rendering of those fieldsets but it does not prevent that the form elements within them go through input processing. Only now the difference between checkboxes and textfields shows because FAPI can not differentiate between not submitted and not checked checkboxes, so it assumes unchecked. While it can differentiate for textfields where it uses, in that case, the default value.
What the patch does is check for a #group during form_builder() and if there is one, check the #access of that. The result is that this works as expected by the test.
IMHO, the #access behavior of a vertical_tab is completely wrong. It is a visual thing, setting #access => FALSE on it should not prevent the fieldset from being rendered, it should only prevent them from being rendered as a vertical tab. Why should $form['vertical_tab']['#access'] affect $form['tab1']['#access'], this is not like anything else in FAPI works. That the fieldsets are hidden is more a side effect of them being moved around in process functions.
However, the problem is that it currently looks as if #access would work correctly, which probably means that we have to make it work the way it seems to do. For security reasons in 7.x, if nothing else.
Comment #15
tim.plunkettHere's an added test, for when you explicitly set the fieldset to #access TRUE, while the vertical_tab is #access FALSE.
Also, a tweak to that comment.
Thanks so much Berdir!
Comment #16
chx CreditAttribution: chx commentedThanks guys this is great.
Comment #17
David_Rothstein CreditAttribution: David_Rothstein commentedHm, hold on a second. This isn't specific to vertical tabs (or things with #group), is it?
Basically, won't the same issue occur for any other situation where a pre-render callback moves the elements around in the form array?
If so, maybe we don't want to special-case vertical tabs here, but rather have it behave consistently. In which case, documentation may be the correct way to solve it. (As Berdir said above, it doesn't seem like an actual bug that #access changes made in the rendering phase only affect how the form is rendered, not how it's processed.)
Although I can see how this is pretty confusing (especially in the case of vertical tabs, since they're used so often and the pre-render stuff happens for you behind the scenes)...
Comment #18
tim.plunkettAFAIK, vertical_tabs DON'T move anything around in the form array. Which is precisely why it's a bug.
I can add a test case with a #pre_render to try it out, later.
Comment #19
BerdirThey do. That's exactly the problem here. As a side effect of the moving things around during pre-render, the fieldsets actually aren't rendered. Otherwise there would be no reason for them to be not shown, which is what I would have expected from looking at the form structure.
Agreed that the #group check in form_builder() is not a perfect solution. Maybe we can add a recursive function that is triggered *after* the pre-render callbacks that takes care of enforcing #access recursively?
Comment #20
tim.plunkettHm. Worth trying, I guess. Not me though. :)
Comment #21
tim.plunkettWell, on second thought. In every #pre_render callback I've written, I've considered myself completely responsible for handling the elements I move around. Vertical tabs are a core thing, so I'd expect core to deal with it, which this patch does.
Comment #22
sunThe summary states an API change as a result of this patch, so tagging accordingly.
I disagree with the problem statement, and thus also with the proposed fix.
When setting #access to FALSE on a vertical tabs element, then the only expectation one might reasonably have is that there won't be vertical tabs. That is, because the form structure is detached from rendering. That is also why vertical tabs are processed and applied during rendering.
If you want to make form input elements inaccessible, then you have to set #access on the individual elements (or their parent structure).
In turn, the only bug I could agree with is that neither form_process_fieldset() nor form_process_vertical_tabs() checks for the #access property value at all.
In turn, I think this entire issue needs to be relabeled and re-classified.
Comment #23
chx CreditAttribution: chx commentedWell form_process_fieldset does not need to check for #access cos the generic #access inheritance takes care of that. I have no opinion on what #access on vertical tabs should or should not do.
Comment #24
sunAttached patch fixes the bug in the way I explained in #22.
#process is unaware of #access, so form_process_vertical_tabs() needs to be stopped from doing unholy things if the element it operates on (#type vertical_tabs) is not permitted to proceed.
In essence, this basically hints at another, different architectural issue, which doesn't seem to be the topic of this issue though. My "last known" refactoring of vertical tabs (#657668: Vertical tabs break Form API) attempted to make sure that most of the logic stays in the rendering layer and does not affect form building/processing. That does not seem to be sufficient though, as this patch proves, since the #process still runs during processing. In turn, I guess that the injection of the "new fieldset", as being performed in this #process, actually needs to be moved into a separate #pre_render, so that the rendering properly takes #access into account. Or similar. Anyway, that shouldn't be part of this issue.
Comment #25
tim.plunkettShouldn't that be
Otherwise, you'd have to explicitly set #access to TRUE, right?
Comment #26
chx CreditAttribution: chx commentedRight.
Comment #27
BerdirIf it wasn't clear, I agree with sun's opinion (that was my suggestion from the beginning, I just wanted to show how it could be fixed). I think we should keep the tests and just change the assertions a bit (make sure that the fieldsets actually are there but no vertical-tab stuff, I'm sure there are some classes or something that we can look for.
A comment wouldn't hurt either :)
I guess the patch as it stands now actually proves that we don't have any test coverage for vertical tabs (Not talking about client side JS tests, just tests that the vertical tab markup is there) because doesn't the empty() check mean that we wouldn't have any vertical tabs?
So maybe add a second vertical tab to the form and assert that that is shown?
Comment #27.0
Berdirsteps to reproduce
Comment #28
tim.plunkettBump ;)
Comment #29
tim.plunkettI'm fine with @sun's new approach, but I do think this is still major.
Comment #30
sun24: drupal8.vertical-tabs-access.24.patch queued for re-testing.
Comment #32
sunHere's a re-roll for now.
I wanted to add a comment, but I'm actually no longer sure what this issue is trying to fix — even after reading comments.
I also don't know whether the proposed fix is still compatible with recent changes to
#group
in D8... quite a lot has changed in that field.I think we need a proper test + actual expectations first. For D8, please take inspiration from the DUTB test in #2214055: Programmed form submission does not get a triggering_element
Comment #33
Anonymous (not verified) CreditAttribution: Anonymous commentedI'm not sure if this has it's own issue but I just want to note that this same bug is present with standalone details(no vertical_tabs).
I think it may be a problem with FAPI rendering child elements of parent wih access set to false if they themselves do not have access set to false.
Comment #36
mgiffordWhat happened to form_process_vertical_tabs()? Can't grep it any more.
Comment #37
tim.plunkettIt's now processVerticalTabs(), in \Drupal\Core\Render\Element\VerticalTabs
Comment #38
mgiffordOk, then let's see what the bot thinks of this.
Comment #39
jhedstromThe patch in #38 is missing the test from the previous patches (most recent in #15).
Comment #40
samuel.mortensonRefactored the test and form from #15 combined with the patch in #38. Didn't change any logic, just made sure the code was up to date with the newest Core API.
Putting issue back in review.
Comment #41
jhedstromThanks @samuel.mortenson! Could you please also upload the test-only portion of this (eg, everything but the fix) to illustrate that the tests fail without the fix?
Comment #42
samuel.mortensonDone
Comment #44
jhedstromFix makes sense, and the tests look good!
Comment #47
xmacinfoComment #48
samuel.mortenson@xmacinfo You queued the test-only patch for re-testing, which was meant to fail. The full patch from #40 succeeds normally, as it includes the fix. Please add a comment to the issue next time you change the status. Setting back to RTBC.
Comment #49
xmacinfoIndeed! Re-tested the wrong patch. Sorry!
Comment #50
samuel.mortensonNo problem, just making sure we aren't missing something in the current patch. :-)
Comment #51
tim.plunkettpublic function
Everything in this function is indented wrong
Missing blank line
Comment #52
alexpottThis issue addresses a major bug and is allowed per https://www.drupal.org/core/beta-changes. Committed ce45b6e and pushed to 8.0.x. Thanks!
All that fixed on commit.
Comment #55
David_Rothstein CreditAttribution: David_Rothstein as a volunteer commentedThere was something we wanted to backport here... not sure if it was the final committed D8 patch or not.