Environment
Drupal Version: 7.0
PHP: 5.3 (OS X 10.6.6)
Description
When calling drupal_form_submit() on a node form multiple times, the first item submitted is inserted as expected while all subsequent items are treated as updates to the first inserted node.
A couple of observations:
- I see that node data is cached in $form_state['node'] in lines 90-99 of node.pages.inc in the node_form() function:
// During initial form build, add the node entity to the form state for use
// during form building and processing. During a rebuild, use what is in the
// form state.
if (!isset($form_state['node'])) {
if (!isset($node->title)) {
$node->title = NULL;
}
node_object_prepare($node);
$form_state['node'] = $node;
}
else {
$node = $form_state['node'];
}
Forcing node_form() to build a new node object each time "fixes" the problem, but may of course create others.
To replicate the issue, loop over the drupal_form_submit function a couple of times like:
function node_import_test() {
module_load_include('inc', 'node', 'node.pages');
global $user;
$node = array(
'type' => 'page',
'language' => 'en',
);
for ($x = 0; $x <= 1; $x++) {
print $x;
$form_state['values'] = array();
$form_state['values']['language'] = "en"; // substitute for the language of the node
$form_state['values']['name'] = $user->name;
$form_state['values']['status'] = 1;
$form_state['values']['promote'] = 0;
$form_state['values']['sticky'] = 0;
$form_state['values']['op'] = t('Save');
$form_state['values']['title'] ='foo-#'. $x;
$form_state['values']['body'] = 'Body Text Here';
drupal_form_submit('page_node_form', $form_state, (object) $node);
}
return t('Submitted Two Nodes');
}
You'll have one node with the title of the second submitted item with the above code.
I've attached an example module that implements this for your convenience.
Thanks much for looking into this, please let me know if I've simply missed something in the API and this is expected behavior.
Comments
Comment #1
berdirPlease read http://drupal.org/node/45111, this is not critical.
Comment #2
cfennell commentedAh, I was looking for a guide like that. I based my priority by scanning other issues. Sorry, I'm kind of a newb w/r/t core stuff.
Comment #3
cfennell commentedNow that I've looked at the guidelines, I have to wonder of "normal" isn't selling this issue a bit short. Not being able to submit multiple nodes via the core hook for doing so seems kind of like it would have "significant repercussions" at a minimum, and it should probably have a test written for it once it's fixed, which would make it a critical. But, I'm just getting a sense for the statuses in core (obviously), so I'll leave it to others to make that call.
Comment #4
berdirNote sure if drupal_form_submit() counts as the official function for creating nodes :)
Comment #5
cfennell commentedHeh, ok, so is there an "official" core method of programmatically inserting new nodes in Drupal that is better than using drupal_form_submit()? Thanks.
Comment #6
karens commentedAnything that destroys data is critical in my book. I'm going to at least mark it 'major'. Saying "Don't use drupal_form_submit()" is not really a solution. Either the function should work correctly or it should be removed.
Comment #7
bfroehle commentedShouldn't you just be using node_save()?
If you need sample code, see DrupalWebTestCase::drupalCreateNode().
I think this is the proper behavior when submitting a form --- we don't want to accidentally create two nodes if a user accidentally clicks the submit button twice.
Comment #8
cfennell commentedBut node_save() still does not invoke all possible hooks (e.g. validation hooks. In my case, yes, I could get away with using node_save(), but offering a less functional interface for programmatic insertion of nodes seems not good.
Comment #9
morbus iffI think there's ideally two different methodologies at work: validating the "I have no idea how your site works" end-user and validating the "pff, data constraints? yawn!" programmer. If I, as a programmer, want to submit a node with 6000 words in it, I should be able to - but I may not want my end-users doing the same thing. One is the front entrance, via interactive forms (which can do the sort of call and response validation necessary for an individual node submission), and the other is the back entrance, via node_save(), which assumes that if you're at this low of a level, You Know What You're Doing and You Might Want To Do It Two Thousand Times. As another example, If I prevent a form from showing a field to an end-user, I DO NOT want this field to be inaccessible via node_save() (which would be in contest if validation and "all the things forms do" happened at the node_save() level).
Roughly, it breaks down to: drupal_form_submit() assumes bad data is coming in, and node_save() assumes good data is coming in.
You, as the direct caller of node_save(), assert that what you're passing is good data (just like every other function you might call).
Comment #10
cfennell commentedFWIW, here are a couple of tests that show how drupal_form_submit fails.
@Morbus Iff I initially had similar thoughts w/r/t "programmers should know what they're doing" but also thought there would probably be cases where they'd like to just use built-in form features rather than having to re-roll them in some other way. I can imagine programmers not wanting, for example, to write validation code on data they inherit for import when validation code already exists in the submit hook. Laziness is a virtue! :). Kind of a minor nit, but node_save also more tightly couples the node system internals to its api by forcing programmers to manually set things like timestamps.
I don't have really strong feelings on this, but instead of having to tell programmers that drupal_form_submit is now broken in a different way than it was as drupal_execute in Drupal 6 that we might not simply find a way to make both the web use-case and the programmatic use-case work?
Thanks for looking at this.
Comment #11
bfroehle commentedYour code will work fine if you remember to reset all of $form_state in each form submit process. That is, you'll need a line like
$form_state = array();.drupal_form_submit() alters the $form_state variable. When you resubmit the form without clearing what it returned, it's not surprising that the behavior is not what you would expect.
Don't forget to check for errors with form_get_error().
Comment #12
cfennell commentedD'oh! Can't believe I didn't catch that, thank you @bfroehle - so many side-effects with the references. My test does indeed pass when I wipe the $form_state each time.
Comment #13
dgtlmoon commentedIf this is not the best way to create a new node, then why does the Services module use this?
Comment #14
william.lai commentedI have similar issue when using drupal_form_submit in services when i create multiple node with files attached. The first node can be saved correctly. For the second node and latter node in the for loop, the node array is look fine and attached with new saved fid for new file until drupal_process_form's form_builder.
After drupal_process_form's form_builder, you fid is overridden with previous old fid (the first node's fid). It cause all latter node pointing to the same fid.
i tried set form_state['no_cache'] = true, but still not work.
Thanks,