Please see #684846-55: AJAX triggered by non-submit element fails if any elements are validated. While working on that issue, I discovered a FAPI bug where #access wasn't being checked in the code that determines which button was clicked. But that bug can't be fixed until file_managed_file_process() is fixed. Here's what I have so far written in that patch:

  // @todo We need to add a #access check here, so that someone can't fake the
  //   click of a button they shouldn't have access to, but first we need to
  //   fix file.module's managed_file element pipeline to handle the click of
  //   the remove button in a submit handler instead of in a #process function.
  //   During the first run of form_builder() after the form is submitted,
  //   #process functions need to return the expanded element with child
  //   elements' #access properties matching what they were when the form was
  //   displayed to the user, since that is what we are processing input for.
  //   Changes to the form (like toggling the upload/remove button) need to wait
  //   until form rebuild.

Changing the file upload/remove pipeline to use submission followed by rebuild correctly will also enable the removal of this code from file_managed_file_process():

  // Because the output of this field changes depending on the button clicked,
  // we need to ask FAPI immediately if the remove button was clicked.
  // It's not good that we call this private function, but
  // $form_state['clicked_button'] is only available after this #process
  // callback is finished.
  if (_form_button_was_clicked($element['remove_button'], $form_state)) {

because during a submit handler, you have access to $form_state['clicked_button'], or better yet, you can just have the upload and remove buttons specify different submit handlers.

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

rfay’s picture

subscribing

sun’s picture

Category: task » bug
Priority: Normal » Critical

This is critical and needs to be fixed urgently.

eojthebrave’s picture

This is new territory for me so I'm not sure if I'm even on the right track but thought I would take a stab at this.

Haven't run any tests other than some manual clicking but wanted to know if this is the right direction to take before putting more time into it.

This patch removes the processing of file removal from file_managed_file_process(), implements #ajax callbacks for the upload and remove file buttons, and moves the processing of the actual upload/remove of a file into the appropriate submit handlers.

sun’s picture

Status: Active » Needs review

Status: Needs review » Needs work

The last submitted patch, 736298-3-eojthebrave-file_managed_file_process.patch, failed testing.

eojthebrave’s picture

Status: Needs work » Needs review
FileSize
11.24 KB

Lets see if this fixes the tests. Previous patch didn't completely support remove/upload a files with JS disabled.

Again, any feedback on if this is even moving in the right direction would be nice.

Status: Needs review » Needs work

The last submitted patch, 736298-6-eojthebrave-file_managed_file_process.patch, failed testing.

eojthebrave’s picture

Status: Needs work » Needs review
FileSize
9.49 KB

Re-roll.

Status: Needs review » Needs work

The last submitted patch, 736298-8-eojthebrave-file_managed_file_process.patch, failed testing.

eojthebrave’s picture

Status: Needs work » Needs review
FileSize
11.18 KB

Again. Adds a callback specific to the file field upload/remove buttons so that the entire file field widget gets replaced on ajax form submissions not just the #managed_file element.

effulgentsia’s picture

Thanks for the work on this!

I still want to review it in more detail. In the meantime, this patch doesn't change what's already in #10, but adds new hunks: the removal of the @todo in form.inc (the security issue for why this issue is critical) and a test to go with it. If the file tests still pass, then we're making good progress and just need to do some cleanup.

effulgentsia’s picture

Awesome, I'm really happy #11 passes! This one removes a bunch of hunks that aren't within the scope of this issue. @eojthebrave: I admire your attempt to clean up some of the mess in file_ajax_upload(), and I hope we're able to clean that function up more before D7 is released, but let's keep this issue focused to just what is needed for FAPI security. That will make it easier to review. This patch just leaves #10's critical refactoring of file.module's #process and #submit functions and #11's fix to form.inc and tests.

This should still pass tests and be a manageable patch to review.

Dries’s picture

+++ modules/file/file.module	4 Apr 2010 20:15:52 -0000
@@ -566,12 +551,53 @@ function file_managed_file_validate(&$el
+  // Set the form to rebuild and run submit handlers.
+  if (isset($form['#builder_function']) && function_exists($form['#builder_function'])) {
+    $form['#builder_function']($form, $form_state);
+  }

I'm a bit puzzled by this. Why don't the submit handlers get called by form API?

+++ modules/file/file.module	4 Apr 2010 20:15:52 -0000
@@ -566,12 +551,53 @@ function file_managed_file_validate(&$el
+  // Get the current element and count the number of files.
+  $clicked_button = array_pop($form_state['triggering_element']['#parents']);
+  $current_element = $form;
+  foreach ($form_state['triggering_element']['#parents'] as $parent) {
+    $current_element = $current_element[$parent];
+  }

This doesn't look like counting the number of files to me (re: code comment).

+++ modules/file/file.module	4 Apr 2010 20:15:52 -0000
@@ -566,12 +551,53 @@ function file_managed_file_validate(&$el
+  if (isset($form['#builder_function']) && function_exists($form['#builder_function'])) {
+    $form['#builder_function']($form, $form_state);
+  }

Ditto above. I'm a bit puzzled by this. Why don't the submit handlers get called by form API? Can this be clarified in the code?

+++ modules/file/file.module	4 Apr 2010 20:15:52 -0000
@@ -566,12 +551,53 @@ function file_managed_file_validate(&$el
+  if ($element['#file'] && $element['#file']->status == 0) {

Would be nice to use defines for this instead of 0. Outside the scope of this patch though.

+++ modules/file/file.module	4 Apr 2010 20:15:52 -0000
@@ -566,12 +551,53 @@ function file_managed_file_validate(&$el
+
+  form_set_value($element, NULL, $form_state);
+  form_set_value($element['fid'], NULL, $form_state);

This could use some code comments.

effulgentsia’s picture

Man, this issue is quite tricky. This patch cleans it up a little more, but even though it passes tests, it does not work. More tests need to be added.

Here's a remaining bug that needs to be added as a test: go to node/add/article, type in a title, browse for an image, click upload. You will get a message saying the article node was saved, even though you did not click "save" but "upload".

The problem is that in the 1st run through form_builder() (not the one triggered by drupal_rebuild_form()), when $form_state['triggering_element'] is not yet determined and no submit handlers have yet run, file_managed_file_value() runs before file_managed_file_process(). file_managed_file_value() has the code to save the uploaded file (something I expected to be movable to the #submit function). But it can't be moved to file_managed_file_submit(), because we want the user to be able to click "save" without clicking "upload" and still have the file saved. But that means by the time file_managed_file_process() is called the first time, there exists a fid, and the upload button is given #access=FALSE. And that prevents the upload button from being a candidate for $form_state['triggering_element'] even though it was clicked (see the @todo in form.inc that this patch tries to remove). So as far as FAPI is concerned, nothing matches $form_state['triggering_element'], which makes it default it to the "Save" button because that's the first one (this defaulting is needed to handle the fact that Internet Explorer sometimes doesn't submit button information in the POST).

Anyway, some help here from fago, chx, or another multistep form guru would be very nice. Our normal FAPI workflow works well when #value callbacks don't do #submit actions. But here we have a case where that's exactly what's happening. Do we try to find a way to make this form work despite that? Or do we find a way to move the file saving to a #submit, but make sure it runs for the "save" button too?

effulgentsia’s picture

Issue tags: +D7 Form API challenge

Tagging this since it touches on deep FAPI questions.

effulgentsia’s picture

Another question related to this. Is the following behavior of HEAD a bug or a feature?

Go to node/add/article, leave title blank, browse for an image (but do not click upload), click "save". You get a validation error (missing title), but the image has been uploaded, so now the "upload" button has been changed to "remove".

Does this make sense? That even though you got a validation error on the form, your image has been uploaded and saved on the server, and in the redisplay of the form, the "upload" button changed to "remove"? If this is a feature, it points to additional reasons for the file saving to not be movable to the #submit phase.

effulgentsia’s picture

Hm, if #16 is a feature, then it's very similar to the CAPTCHA example in #642702: Form validation handlers cannot alter $form structure. Perhaps we just need to move the file saving from file_managed_file_value() to file_managed_file_validate() or something like that...

effulgentsia’s picture

This one is ready for review.

It's a little ugly (read the phpdoc for file_managed_file_after_build()), but I haven't thought of an alternative. It seems reasonable in the case of file.module that the value callback handles the upload. And FAPI is setup to run #process functions after #value is determined (and that is by design, so no changing that for D7), so we don't have a nice mechanism to get back the original fully built form that was initially presented to the user to use for #access checking. Normally, it's not a problem as most #process functions don't change structure/#access based on what's in #value, but for those that do, we have a problem. Seems like an unfortunate limitation of FAPI, but better to work around it in the way done in this patch than to not implement any #access security for button click determination. If anyone has a better idea, please post it.

sun’s picture

+++ modules/file/file.module	5 Apr 2010 02:46:38 -0000
@@ -464,6 +445,36 @@ function file_managed_file_process($elem
+ * being secure, since input has already been parsed by this time. However, the
+ * particular use of #access here is primarily for UI and not for security, so
+ * it's okay.
+ *
+ * @see file_managed_file_process()
+ */
+function file_managed_file_after_build($element, &$form_state) {

The reasoning here reminds me of #pre_render.

Aside from that, I wanted to mention that #element_validate handlers are now also able to alter the form structure - if that helps in any way. (Form validation runs last.)

129 critical left. Go review some!

effulgentsia’s picture

The reasoning here reminds me of #pre_render.

Thanks! That makes much more sense. Updated patch and added more documentation explaining the situation.

Aside from that, I wanted to mention that #element_validate handlers are now also able to alter the form structure - if that helps in any way. (Form validation runs last.)

There's potentially a separate need unrelated to this issue to evaluate what is done in file_managed_file_(value|process|validate)(). For example, does it make sense that we save the file in _value() rather than in _validate()? And if we decide to change some of the workflow of managed_file around, then #element_validate being able to change $form is awesome! But with the workflow as it is, #pre_render is the correct place for toggling the display of the buttons.

fago’s picture

>For example, does it make sense that we save the file in _value() rather than in _validate()?
I think so. It doesn't make much sense to me to do that during validate(). Imho changing values during validate() isn't logical at all and should be avoided if possible.

With moving the display toggles to #pre_render I think it's the right approach.

>And FAPI is setup to run #process functions after #value is determined (and that is by design, so no changing that for D7), ..
Is there a cause for that? Imo that doesn't make much sense.

Sometimes we miss a kind of "value extraction" step in the form API, e.g. to build the entity object (which the #builder_function tries to emulate). If moved to the form API, I'd say the value functions are the right place and needs to be improved to handle it. But for that, first the form values have to be processed, then the values for the processed form elements need to be assigned such that the "main element" is able to build its value/object out of those element values. With that scenario in mind, processing obviously needs to happen first.

effulgentsia’s picture

Sometimes we miss a kind of "value extraction" step in the form API...

So currently, we have #value_callback followed by #process in pre-order traversal, and #after_build in post-order traversal. If I'm understanding correctly, you're suggesting we may want some kind of #preprocess followed by #value_extract in post-order traversal to happen before the existing stuff. Seems like good stuff to consider for D8, but are you suggesting pipeline changes for D7? Currently, #process functions all over the place (certainly in contrib) do conditional stuff on #value, so I don't think it would be wise to change that order for D7, but I do think it makes sense to re-architect the entire pipeline in D8.

effulgentsia’s picture

Though if adding new callbacks like #preprocess to run before #value_callback would solve the problems with #builder_function, perhaps that should be considered even for D7, since adding new stuff wouldn't break BC.

[EDIT: Seems like material for a separate issue, though, as I don't think even that would help us solve this issue better than how #20 does it. The only thing I don't like about #20 is that it doesn't offer a way to correctly enforce the prior page request's #access of the upload/remove buttons. Not a big deal for this particular use-case, but potentially frustrating for other use-cases (note that in D6 and in D7 HEAD, #access isn't enforced AT ALL for button click detection, so #20 is a huge improvement, just leaves this unsolved problem in these kinds of dynamic #process workflows). But solving this remaining problem would require retaining the prior step's input or values in $form_state or retaining the prior step's fully built $form in addition to the $original_form, not just adjusting the sequence of callbacks.]

effulgentsia’s picture

Oh, and by the way, @fago, thanks so much for reviewing this issue! It helps a lot to have someone with your insight of multistep forms helping out on this and the other FAPI WTF issues that you've been giving attention to!

yched’s picture

Could #737686: Image field 'Remove' button broken be a dupe of this one, or is it unrelated ?

effulgentsia’s picture

Marked #737686: Image field 'Remove' button broken a duplicate, because #20 fixes the problem (I tested both with JavaScript disabled and enabled). But, would be great to add a test for it as part of this issue, since HEAD is broken in this regard but not failing any tests. I think we're missing quite a bit of test coverage for file.module. So, I'd like to use the occasion to shamelessly plug #384992: drupal_html_id does not work correctly in AJAX context with multiple forms on page, which in addition to solving what the title states, also improves our ability to add tests for multistep AJAX workflows.

fago’s picture

>Currently, #process functions all over the place (certainly in contrib) do conditional stuff on #value, so I don't think it would be wise to change that order for D7, but I do think it makes sense to re-architect the entire pipeline in D8.

Hm, do they? Stuff like checkboxes should be processed independent of the value. But it makes much sense to me to build the element's value depending on the value of the child elements? Anyway if some form elements already work that way, it might make more sense to add another, new "extract value" step. Ideally this wouldn't be tied to "input" or processed for elements add all - as then one could use that "value builder" without having to use a separate form element + processing.

Anyway, I don't think it's necessary to change it for D7 now, while of course adding a new feature won't hurt anyone. But I guess we would hit the same problem here as we have with #builder_function: #735800: node form triggers form level submit functions on button level submits, without validation - partial validation.

Dries’s picture

I like this patch -- it looks like a good clean-up. Some minor comments:

+++ modules/file/file.module	5 Apr 2010 16:01:34 -0000
@@ -566,13 +547,44 @@ function file_managed_file_validate(&$el
+  // fields in the entity when submitting with JavaScript disabled.

Should be 'is disabled'.

+++ modules/file/file.module	5 Apr 2010 16:01:34 -0000
@@ -566,13 +547,44 @@ function file_managed_file_validate(&$el
+  // The triggering element is either the upload or remove button. Get the
+  // 'managed_file' element it controls: its parent in $form.

The second part of this code comment is confusing me.

Powered by Dreditor.

sun’s picture

Status: Needs review » Needs work
+++ includes/form.inc	5 Apr 2010 16:01:33 -0000
@@ -1477,17 +1506,10 @@ function _form_builder_handle_input_elem
+  if (!empty($form_state['input']) && ($form_state['programmed'] || !isset($element['#access']) || $element['#access'])) {

Is #access ever unset in _form_builder_handle_input_element()?

+++ modules/file/file.module	5 Apr 2010 16:01:34 -0000
@@ -566,13 +547,44 @@ function file_managed_file_validate(&$el
+  // This is unfortunately necessary for the form to retain the input of other
+  // fields in the entity when submitting with JavaScript disabled.
+  // @see http://drupal.org/node/367006
+  // @see http://drupal.org/node/735800
+  if (isset($form['#builder_function']) && function_exists($form['#builder_function'])) {
+    $form['#builder_function']($form, $form_state);
+  }

Can we remove the emotions here + be more concrete about _why_ this code is needed and what it does? (consequences of not doing it)

+++ modules/file/file.module	5 Apr 2010 16:01:34 -0000
@@ -566,13 +547,44 @@ function file_managed_file_validate(&$el
+      // No action needed, because file_managed_file_value() handles the upload
+      // which it must so that the form works as expected if the user clicks
+      // "Save" without ever clicking "Upload".

The "which it must so that the" construct reads weird here.

+++ modules/file/file.module	5 Apr 2010 16:01:34 -0000
@@ -607,6 +619,30 @@ function file_managed_file_save_upload($
+ * See form_builder() for why this is done here instead of in
+ * file_managed_file_process(). This means that the use of #access here is for

Is the first sentence still valid with this patch? --- either way, I guess it doesn't help anyone to refer generally to form_builder() in a way like this. Perhaps just remove that first sentence.

Powered by Dreditor.

effulgentsia’s picture

Status: Needs review » Needs work
FileSize
20.57 KB

With #28 and #29.

Is #access ever unset in _form_builder_handle_input_element()?

It's never set to begin with most of the time. Things only set it to FALSE. Not set needs to be interpreted as TRUE. I changed the if statement to be exactly the same as what is used above in the same function for deciding whether to process input for an input control. Same principle here: a button is a kind of input control.

effulgentsia’s picture

Status: Needs work » Needs review

.

effulgentsia’s picture

Status: Needs work » Needs review
FileSize
22.45 KB
+++ includes/form.inc	11 Apr 2010 02:07:53 -0000
@@ -1477,17 +1506,10 @@ function _form_builder_handle_input_elem
+  if ($form_state['programmed'] || ($form_state['process_input'] && (!isset($element['#access']) || $element['#access']))) {

#426056: Server-side enforcement of #disabled is inconsistent landed so this patch applies the #disabled enforcement for triggering_element determination as well. Since this if statement is getting quite long, this patch cleans it up to only exist once in the function.

effulgentsia’s picture

I think this is ready to fly. Any chance of getting an RTBC or additional feedback before DrupalCon?

andypost’s picture

Cant apply patch

patching file modules/file/file.module
Hunk #5 FAILED at 619.

Also inform about #770880: Files and images are not deleted after node saved

So glad to test patches

andypost’s picture

FileSize
22.42 KB

Re-roll the same patch, after this patch files and images works fine so going to mark as duplicate #770880: Files and images are not deleted after node saved

andypost’s picture

FileSize
22.42 KB

Extended test (file.test) for file removing.
$new_revision is checkbox so it should be excluded from $edit to not produce new node revision

andypost’s picture

FileSize
24.52 KB

Wrong patch was attached, fixed

andypost’s picture

doc-blocks should be changed to 80 chars limit to conform coding-standards

effulgentsia’s picture

+++ modules/file/tests/file.test	14 Apr 2010 11:25:14 -0000
@@ -329,6 +331,10 @@ class FileFieldDisplayTestCase extends F
+    // Check file removal.
+    $this->removeNodeFile($nid, FALSE);
+    $this->assertFileNotExists($node_file, t('File is deleted.'));
+    $this->assertFileEntryNotExists($node_file, t('File entry is deleted.'));

Unfortunately, this test does not fail in HEAD. The "remove" button works in HEAD with JS disabled. It is the AJAX submission that's broken due to missing test coverage. We need to improve file.module testing to cover both AJAX and non-AJAX variants of the upload and remove buttons to avoid future regressions.

112 critical left. Go review some!

andypost’s picture

Agreed that tests should be extended. What do you mean about AJAX and non-AJAX remove buttons?
"Remove" button should work always and do not depend on JS availability

effulgentsia’s picture

Re #40: The "remove" button *should* always work regardless of JS availability, but the way that's implemented involves two different workflows. Without JS availability, the form is submitted to its normal "action". With JS availability, it is submitted to 'file/ajax' which runs file_ajax_upload(). The latter is where the bug is, but the test coverage is only for the former, which is how the bug made it into HEAD without bot catching it, so we need to add test coverage for the latter, which we should do as part of this issue. There's also #756762: AJAX should follow same rules for whether to call drupal_rebuild_form() as non-AJAX submissions for helping to keep #ajax['path'] callbacks correctly implemented, to reduce these kinds of bugs in the future by eliminating needless deviation between non-AJAX and AJAX FAPI processing.

Because both this issue and #756762: AJAX should follow same rules for whether to call drupal_rebuild_form() as non-AJAX submissions will take some time to move through the process, I posted a simple fix to the broken remove button to #737686: Image field 'Remove' button broken.

MichaelCole’s picture

Status: Needs review » Needs work

From #38, update doc-blocks to 80 chars
From #39 and #40, tests need to be extended

Marking "needs work"

The original bug #684846: AJAX triggered by non-submit element fails if any elements are validated that spawned this bug has been closed. Is this bug still critical?

Critical: When a bug renders a module, a core system, or a popular function unusable.

effulgentsia’s picture

Yes, this is critical, as it's preventing an important security improvement, and also solves the problem of the image field "remove" button not working. What's holding it up is a desire to roll in some better AJAX test coverage (since the "remove" button is being fixed, we need a test for that), but I'll try to work on that and post a new patch this week.

effulgentsia’s picture

I posted a patch that makes it easier to write AJAX tests in a separate issue: #789186: Improve drupalPostAJAX() to be used more easily, leading to better AJAX test coverage. I still haven't gotten around to writing the necessary AJAX tests for file.module, but I think it makes sense for those to be included with this issue. Meanwhile, the other issue is ready for review.

JohnAlbin’s picture

Subscribe.

effulgentsia’s picture

Sorry to not have gotten this done yet: I've been giving attention to some other issues. I do plan on resuming this one soon. As an FYI, this issue also fixes #disabled buttons to not be recognized as clicked as per #803212-12: Protection against forgery of input selection value doesn't work with checkboxes.

effulgentsia’s picture

Title: Refactor file_managed_file_process() to use proper form submission / rebuilding sequence » Refactor file.module to use proper button click detection, enabling FAPI to improve button click detection security
Issue tags: +Security improvements

Changing title and tagging to clarify what the point of this issue is and why it's critical. See issue description and early comments for details.

arpeggio’s picture

Title: Refactor file.module to use proper button click detection, enabling FAPI to improve button click detection security » Refactor file_managed_file_process() to use proper form submission / rebuilding sequence
Issue tags: -Security improvements

I tried to apply the latest patch in this thread and when I tried to refresh my drupal test site I got this error:
Fatal error: require_once(): Failed opening required 'C:\xampp\htdocs\includes\form.inc' (include_path='.;C:\xampp\php\PEAR') in C:\xampp\htdocs\includes\common.inc on line 4470
What could be the problem? Please help. I am using Drupal 7 CVS updated.

effulgentsia’s picture

Title: Refactor file_managed_file_process() to use proper form submission / rebuilding sequence » Refactor file.module to use proper button click detection, enabling FAPI to improve button click detection security
Status: Needs work » Needs review
Issue tags: +Security improvements
FileSize
24.53 KB

Re #48: not sure. Here's a re-roll in case that helps.

Setting to "needs review" for a bot-check. If bot comes back green, I will reset to "needs work".

Status: Needs review » Needs work
Issue tags: -Security improvements, -D7 Form API challenge

The last submitted patch, file_managed_file_process-736298-49.patch, failed testing.

effulgentsia’s picture

Status: Needs work » Needs review
Issue tags: +Security improvements, +D7 Form API challenge
effulgentsia’s picture

Status: Needs review » Needs work

back to "needs work" as per #49.

Dave Reid’s picture

Subscribing...

effulgentsia’s picture

Status: Needs work » Needs review
FileSize
27.35 KB

Sorry for the delay on this. Other issues have been eating my time. Anyway, we're hopefully close to beta 1, so time to get moving on this again. There are still a couple issues outstanding that could simplify this patch once they land: #735800: node form triggers form level submit functions on button level submits, without validation and #789186: Improve drupalPostAJAX() to be used more easily, leading to better AJAX test coverage. This patch comments where there's WTFs due to those issues. But, although the WTF cleanup once those issues land will be nice, they don't have to hold up this patch, which is otherwise ready I think (though, perhaps the comments can still use some polish).

This adds tests for the "remove" button, both under non-JS and JS emulation, because the JS one fails with HEAD, and since this issue happens to fix that, #770880: Files and images are not deleted after node saved and #737686: Image field 'Remove' button broken were marked as duplicates of this issue, and therefore, this issue is responsible for adding the test to prevent regression. More test coverage of the file field widget (i.e., the "upload" button under JS, and various combinations of clicks with a multi-valued field) would be nice, but requires effort, and I think would be best handled as a separate issue, and after #789186: Improve drupalPostAJAX() to be used more easily, leading to better AJAX test coverage lands.

effulgentsia’s picture

+++ modules/file/file.module	16 Jun 2010 19:32:46 -0000
@@ -565,13 +546,44 @@ function file_managed_file_validate(&$el
+  // When setting an entity editing form to rebuild, the form's
+  // #builder_function must be executed, or else other fields in the entity form
+  // won't retain user input correctly: http://drupal.org/node/735800.
+  if (isset($form['#builder_function']) && function_exists($form['#builder_function'])) {
+    $form['#builder_function']($form, $form_state);
+  }

Yay, the other issue landed, so this patch removes this little bit of nastiness.

Also, I improved the prior patch's PHPDoc of form_builder() and file_managed_file_pre_render(). I think this is ready to fly. Any chance of this making it in before alpha6/beta1?

56 critical left. Go review some!

Status: Needs review » Needs work

The last submitted patch, file_managed_file_process-736298-55.patch, failed testing.

effulgentsia’s picture

Status: Needs work » Needs review
FileSize
28.1 KB

Awesome! The change in #735800: node form triggers form level submit functions on button level submits, without validation that made entity forms follow normal FAPI persistence of $form_state['input'] exposed a bug in the patch's file_managed_file_submit() function. This is great, because it means the bug existed for non-entity forms using the managed_file element, and wasn't caught by bot, because we don't have test coverage for that. But now that entity forms follow the same rebuild rules as all other forms, we have the necessary test coverage. This patch fixes the bug by updating $form_state['input'] to reflect the file deletion. Note, most elements aren't so delicate with the need to update $form_state['values'] and $form_state['input'] for rebuilding to function correctly, so I added code comments explaining why this is needed for the managed_file element.

pwolanin’s picture

subscribe

effulgentsia’s picture

This merges in additional form_builder() documentation that is being dropped from #266246-79: Add smart defaults for system_settings_form. The documentation being added is not related to this issue, but because this issue also adds a lot of form_builder() documentation, I think it makes sense for all of it to be reviewed together. Plus because of the new documentation, I made some tweaks to the documentation that was in #57. It's quite a lot of explanation about form_builder(), but let's face it, form_builder() is a complex function that does a lot, with implications that aren't all that intuitive for most people.

Frando’s picture

Wow, no time right now to actually review the patch, but: Your work on documenting FAPI is absolutely awesome, effulgentsia! The core function of FAPI finally gets a proper docblock, unbelievable.
When I started diving into FAPI internals (guess some two years ago) that would've been tremendously helpful. Thanks so much.

aspilicious’s picture

+++ modules/file/file.module	25 Jun 2010 17:40:43 -0000
@@ -626,6 +644,36 @@ function theme_file_managed_file($variab
+ * #pre_render callback to hide display of the upload or remove controls depending on which is needed.
+ *

This line exceeds 80 characters is it posisble to make it shorter?

Powered by Dreditor.

robeano’s picture

robeano’s picture

Shortened line mentioned in #736298-61: Refactor file.module to use proper button click detection, enabling FAPI to improve button click detection security and added more descriptive text below it. The first hunk in modules/file/tests/file.test was failing with the most recent version core. Updated. I also successfully tested uploading and removing files, but I did not test the security of this functionality.

Status: Needs review » Needs work

The last submitted patch, file_managed_file_process-736298.patch, failed testing.

robeano’s picture

Status: Needs work » Needs review
FileSize
30.85 KB

Thoroughly tested that this patch applies properly. Let's try this again.

Shellingfox’s picture

Status: Needs review » Needs work
FileSize
8.66 KB

Why a 'Upload' button show here? I think we should have only 'Remove' button.

effulgentsia’s picture

Status: Needs work » Needs review
FileSize
33.46 KB

Thanks!

effulgentsia’s picture

Component: file.module » forms system

This patch touches the "file.module" and "forms system" components, but re-assigning to "forms system", because it touches that in a more significant way, and because even the changes to file.module should be reviewed by FAPI experts.

fago’s picture

Wow, great improvements of the docs. They explain the whole form_building process very well. From a FAPI perspective the changes to FAPI and the file module look good!

effulgentsia’s picture

Issue tags: +API change

Technically, this issue involves an "API change" in that, much like file.module is doing in HEAD, if any contrib module is setting #access=FALSE or #disabled=TRUE of a clicked button within a #process function, it will need to change that into either a proper multi-step form or move that code into a #pre_render function, as per the new form_builder() docs. I believe this is acceptable, because:

1) Using a #process function in the aforementioned way was always questionable, and only "worked" because FAPI didn't implement the security it should have.

2) This allows us to improve FAPI security, which justifies a minor BC break.

Dries’s picture

Status: Needs review » Reviewed & tested by the community

I reviewed this patch and as far as I'm concerned it is RTBC. The code is clear and easy to follow, and the documentation is simply great. Marking RTBC so I can commit this later today.

Dries’s picture

Status: Reviewed & tested by the community » Fixed

Looked over it again, and it still looked good this morning. Committed to CVS HEAD. Another critical bites the dust.

sun’s picture

Priority: Critical » Normal
Status: Fixed » Needs work

Awesome docs, great patch! :)

+++ modules/file/file.field.inc	28 Jun 2010 18:06:19 -0000
@@ -767,32 +767,45 @@ function theme_file_widget_multiple($var
+    // Delay rendering of the buttons, so that they can be rendered later in the
+    // "operations" column.
...
+        hide($element[$key][$sub_key]);
+        $operations_elements[] = &$element[$key][$sub_key];
...
+    if ($element['#display_field']) {
+      hide($element[$key]['display']);
+    }
+    hide($element[$key]['_weight']);
...
+    $information = drupal_render($element[$key]);
...
+    foreach ($operations_elements as $operation_element) {
+      $operations .= render($operation_element);
+    }
...
       $display = array(
-        'data' => drupal_render($element[$key]['display']),
+        'data' => render($element[$key]['display']),
         'class' => array('checkbox'),
       );
...
-    $weight = drupal_render($element[$key]['_weight']);
...
+    $weight = render($element[$key]['_weight']);

wowowow... show() hide() render() are supposed to be used in theme template files only. Everywhere else, we use drupal_render() only.

I can see the use-case here, but we need to stay consistent.

I'm not sure why the button elements aren't simply moved out of the renderable array into a separate one here?

Not critical anymore though.

37 critical left. Go review some!

effulgentsia’s picture

Status: Needs work » Fixed

I'm not sure why the button elements aren't simply moved out of the renderable array into a separate one here?

#843818: What pattern should we use for hide()/render() within theme functions?

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.

jhodgdon’s picture

Status: Closed (fixed) » Needs work
Issue tags: +Needs documentation updates

See #70 for summary. Was this API change announced on the module 6/7 upgrade guide, and does it need to be? If not, please revert the status/tag change.

jhodgdon’s picture

Status: Needs work » Postponed (maintainer needs more info)

I think we need to add this to the 6/7 module update guide.

However, I don't feel like I understand the API change (see #70) enough to write a coherent summary, much less an example of what you could do in Drupal 6, vs. how your module needs to change in Drupal 7.

Can someone who understands this issue better write something up and post it here please? Thanks...

jhodgdon’s picture

Status: Postponed (maintainer needs more info) » Needs work
Issue tags: -Needs documentation updates +Needs change record

Since no one has provided any information on what needs to be documented in the update docs, I'm going to try another tactic and ask that the people involved in this issue make a change record node instead. I'm adding a note to the module/theme update pages for d7 to also look at the change list page, because there are already a few d6/7 changes there anyway.

  • Dries committed f330485 on 8.3.x
    - Patch #736298 by effulgentsia, eojthebrave, andypost, robeano: fixed...

  • Dries committed f330485 on 8.3.x
    - Patch #736298 by effulgentsia, eojthebrave, andypost, robeano: fixed...

  • Dries committed f330485 on 8.4.x
    - Patch #736298 by effulgentsia, eojthebrave, andypost, robeano: fixed...

  • Dries committed f330485 on 8.4.x
    - Patch #736298 by effulgentsia, eojthebrave, andypost, robeano: fixed...

  • Dries committed f330485 on 9.1.x
    - Patch #736298 by effulgentsia, eojthebrave, andypost, robeano: fixed...