Closed (fixed)
Project:
Drupal core
Version:
6.x-dev
Component:
forms system
Priority:
Critical
Category:
Bug report
Assigned:
Reporter:
Created:
25 May 2007 at 00:08 UTC
Updated:
9 Sep 2007 at 07:36 UTC
Jump to comment: Most recent file
using updated HEAD, PHP 4.4.4
Try this path: admin/build/modules/1
error message:
* notice: Uninitialized string offset: 1 in /Users/Shared/www/drupal6/modules/system/system.module on line 1570.
* warning: Invalid argument supplied for foreach() in /Users/Shared/www/drupal6/modules/system/system.module on line 1574.
* warning: Invalid argument supplied for foreach() in /Users/Shared/www/drupal6/modules/system/system.module on line 1577.
Something odd is going on with the new $form_state variable - this is the code in system module that calls the function throwing the error:
function system_modules($form_state = array()) {
// Get current list of modules.
$files = module_rebuild_cache();
if (!empty($form_state['storage'])) {
return system_modules_confirm_form($files, $form_state['storage']);
}
The '1' is getting passed in from the URL as $form_state. However, the !empty() is being evaluated as TRUE!
if I add to the above code before 'return':
var_dump(!empty($form_state['storage']));
var_dump($form_state);
I get: bool(true) string(1) "1"
Which doesn't seem how PHP is supposed to work? Adding an is_array() check avoids the error, but doesn't explain it:
function system_modules($form_state = array()) {
// Get current list of modules.
$files = module_rebuild_cache();
if (!empty($form_state['storage']) && is_array($form_state)) {
return system_modules_confirm_form($files, $form_state['storage']);
}
| Comment | File | Size | Author |
|---|---|---|---|
| #37 | form_args.patch | 1.05 KB | coofercat |
| #36 | testcase.tgz | 2.75 KB | coofercat |
| #31 | fapi_errors_2.patch | 9.15 KB | eaton |
| #30 | fapi_errors_1.patch | 7.57 KB | eaton |
| #29 | fapi_errors_0.patch | 4.6 KB | eaton |
Comments
Comment #1
eaton commentedThat's just plain bizarre. We need to 1) find out why !empty() is returning TRUE, and 2) find out what's passing a TRUE into that function to begin with. I suspect that our real solution will be found in 2).
Comment #2
heine commentedWell, as pwolanin showed, $form_state is a string. Strings may be accessed as _if_ they were arrays (they are not, however). Now, PHP tries to convert the key to an numeric index, which is, for 'storage' is 0, and returns the first character of the string (1).
Comment #3
heine commentedThe function passing this string is drupal_get_form('system_modules', [URL arg1, arg2, ...]) thanks to the menu system.
Comment #4
eaton commentedWell, that's forehead-slappingly simple. Thanks, Heine.
The question now is what's passing '1' into the function. Let's track that down -- it's a legit bug.
Comment #5
eaton commentedHeine and chx pointed out what's actually going on -- I'd missed the '1' at the end of the url. This appears to have flushed out a conceptual problem: since the menu handler passes in all path bits as arguments, it arbitrarily pushes $form_state out to the second parameter if you put in dummy URL segments. That's... bad, to say the least.
My proposed solution is to rearrange the arguments so that $form_state is always the FIRST argument passed to builder functions. Since form_state has become pretty central to the form workflow, I think it's justifiable, and it would result in the param ordering always staying predictable. Thoughts? Anyone?
Comment #6
pwolanin commented@eatin - that was exactly what I was going to suggest- much as the form_id is handled now.
Comment #7
eaton commentedHere's a patch that reshuffles things so that $form_state is always the first parameter in any form builder function. An exception is any implementation of hook_form() for node forms. Since that's handled through nodeapi rather than formAPI, the params are still $node, $form_state. Can't be helped, AFAIK.
This definitely needs testing, but I agree it makes things more consistent.
Comment #8
eaton commentedAs a quick note, what this means is:
EVERY form's builder function will receive $form_state. Whether it has any data in it or not determines whether you're starting out fresh or in a middle-step of a multistep scenario.
If you pass ad hoc parameters into drupal_get_form('my_form_id', $like, $this)... those params will show up like this:
my_builder_function($form_state, $adhoc1, $adhoc2)If you use hook_forms() to add additional default parameters, you'll get them as:
my_builder_function($form_state, $default1, $default2, $adhoc1, $adhoc2)Comment #9
floretan commentedThe patch did indeed fix the problem for the path: admin/build/modules/1
but it generated the following errors when going to admin/build/themes/settings:
I'm not entirely clear on what this is due to though.
Comment #10
eaton commentedThanks! A function signature had been missed. This is why testing is important on this one ;-)
Comment #11
pwolanin commentedlooks like these are missing too:
http://api.drupal.org/api/HEAD/function/book_outline
http://api.drupal.org/api/HEAD/function/book_admin_edit
Comment #12
eaton commentedFixes the previously mentioned missing forms, corrects a number of other missed forms, and also eliminates the redundant $form_values variable in favor of $form_state['values'].
Comment #13
eaton commentedAn FYI: the reason for eliminating form_values is simple. form_state contains it, and form_values was already just a reference to form_state['values']. If people want to save keystrokes in large functions, they can always $fv &= $form_state['values'].
This has been requested by almost every dev who's actually tackled porting stuff, as there is confusion over whether to use $form_state or $form_values.
Comment #14
yched commentedEaton : I can't roll a patch right now, but 'delayed' submit handler execution in batch.inc needs to be updated as well to reflect new submit handler arguments.
Something like : (batch.inc - line 235)
Comment #15
eaton commentedThanks for the heads up. Re-rolled. :-)
Comment #16
eaton commentedtypo in _form_set_value() flushed out by frando. thanks!
Comment #17
Frando commentedThe installer's site config page is still not working - lots of notices from user.module (mail, name, pass missing indexes) and it doesn't redirect. Eaton is fixing it.
Comment #18
drewish commentedthere's a bug in the book.module:
function book_submit(&$form_state['values']) {Comment #19
eaton commented...Aaaaaand fixed. Thanks, guys. :) Apologies for the book related issue as well, that was in one of the earlier patches but a file was lost from one of the re-rolls due to an editor crash.
Comment #20
eaton commentedFixed some remaining issues in the locale forms, an input filter configuration form, and search forms.
Comment #21
eaton commented....Aaaaand a PHP4 compatability issue. Woo!
Comment #22
chx commentedAs far as I can see we are ready.
Comment #23
eaton commentedEeeexcept for one issue with the installer I introduced. Missing function param. Thanks, hunmonk!
Comment #24
eaton commentedColor module issue fixed. Right now we're down to straggler forms that bypass the normal FAPI processing (by manually calling their own builder functions in hook_form_alter(), stuff like that).
Comment #25
eaton commentedAnd finally, changing the title of this patch to more accurately reflect its current state.
Comment #26
dries commentedCommitted to CVS HEAD. Marking this as 'code needs work' until this the documentation has been updated to reflect this.
Thanks Eaton. :)
Comment #27
yched commentedMissing one-liner in batch.inc - see my comment in #14 (apparently Eaton was supposed to include it in #15 but seems to have left it out, and I did not actually check...)
Comment #28
eaton commentedWhoops. Missed the taxonomy editing form and the... um.
User login form.
That was working earlier. Honest. ;)
There are still some issues with confirm form that hunmonk and I are tracking down, but these two fixes are pretty critical.
Comment #29
eaton commentedThis fixes user login, taxonomy, and the assorted confirm forms that were missed around the site.
Comment #30
eaton commentedHandful of files missed that cvs diff. This has it all.
Comment #31
eaton commented....Aaaaand an issue with comment previews.
Comment #32
dries commentedCommitted! Thanks Jeff.
Comment #33
eaton commentedUpgrade documentation and code snippets at http://drupal.org/node/144132 have been updated to match the latest and greatest. Thanks!
Comment #34
pwolanin commentedmissed one in contact.module:
I fixed it in this larger contact module patch: http://drupal.org/node/58224 which seems about RTBC.
Comment #35
(not verified) commentedComment #36
coofercat commentedIs this really fixed? I've attached a small testcase module that shows some odd parameter duplication.
In my testing, the second page of a multi-step form seems to have a weirdly duplicated $form_state passed to it as a parameter. In short, it's like this:
First Page: form_builder(&$form_state)
Second Page: form_builder(&$form_state, &$wierd_state)
Third Page: form_builder(&$form_state)
I think $wierd_state is actually the previous $form_state, but I might be wrong about that.
Clearly, this problem presents some challenges when dealing with multistep forms that have extra parameters passed to them!
About the testcase:
It's a small Drupal 6 module that asks a few things over two pages of a multi-step form. It uses drupal_set_message to tell you what the form builder function received as arguments. On the first page, it only reports form_state. On the second page, the form builder is called once to validate the submitted data (so reports as before), but is called again to display page 2 of the form. It's here that it reports the extra parameter.
Comment #37
coofercat commentedYep - there's a bug in the way FAPI calls form builders in multi-step forms. I've attached a patch that fixes the problem (more talented devs than me may prefer to solve the problem another way, I don't know...?).
The bug is actually quite a simple one. On submit, FAPI looks to see if it's got the form cached, if not, it has to create it from scratch. To do this, it calls the form builder using the same arguments it was called with when it first generated the page the user's just filled in. In so doing, it manipulates the $args array (created when the drupal_get_form() function starts). It then correctly calls the form builder and moves on.
The next stage is to deliver the second page of the form to the user. Again, drupal_get_form() manipulates $args, but this time they're not as they were when the function was called (because of the above). This is where the bug can be seen in action.
On third or later calls, the form is cached, so the first manipulation of $args does not take place. As such, the next stage is unaffected, and so the bug is not visible.
My patch just makes a copy of the $args array before doing the manipulations on it. That way, $args itself is left as it intended for later use.
Comment #38
eaton commentedJust ran this through, and existing complex forms (like module uninstallation, poll, etc) work smoothly. This was a very good catch -- thanks, coofercat!
Comment #39
dries commentedCommitted to CVS HEAD. Thanks.
Comment #40
(not verified) commented