Although mostly a node is expected to be edited through the node edit form, occasionally we update then through code. Eg, with views_bulk_operations or, in todays case, with Feeds.
Normally, that would be a case of
- $node = node_load($nid);
- $node->whatever = $changed_value;
- node_save($node);
Now, you would not expect that to throw away information. And - it doesn't.
However if you add the innocuous step
- node_object_prepare($node);
into the mix (as feeds.module does, menu.module:menu_node_save() decides to delete the existing menu item altogether.
This is bad.
Attached is an extra test case for menu.module that verifies that node forms behave correctly with menu settings (this seems to have no test case at the moment) and then demonstrates the problem - producing a fail.
The real fix (I believe) is to get menu_node_prepare() to set $node->menu['enabled'] to TRUE if a menu link was in fact loaded. Currently it's a temporary flag that only exists in FAPI state, not as real data. This would prevent the unexpected and destructive behavior.
I found this error when using feeds.module, and also when writing my own VBO action (using hook_action_info()) that needed to load menu info for a node in order to inspect it. Just loading node menu data to look at it causes that menu to go away.
It's a rare edge case, and can be worked around by the caller (who has to add $node->menu['enabled'] to the node before saving it again) but it's unintuative and wrong to allow this to happen as a side-effect here.
To replicate:
// Programatically load the node.
$node = node_load($nid1);
// If we run this, data loss happens:
node_object_prepare($node);
// 'Preparing' an object should be a safe act.
// I needed to run prepare to READ the $node->menu data.
// Make a small change to the node. (dummy example)
$node->title = "Node title changed again";
// Save.
node_save($node);
// Your menu item is now deleted
Even if nobody thinks this data loss is a problem, here's a new test case that exercises the node form menu settings.
| Comment | File | Size | Author |
|---|---|---|---|
| #2 | menu_test-data_loss_on_programmatic_node_save-1534356.patch | 1.6 KB | dman |
| menu_test-data_loss_on_programmatic_node_save.patch | 4.75 KB | dman |
Comments
Comment #1
dman commentedOh boy.
I go looking at D8 to see if I can replicate, and finally spot the MenuNodeTestCase 8-/
It was there all along, I could have built my test case on top of that. What a blind-spot... *sigh*
Still, I'll see if this data loss issue is in d8 anyway ...
Seems I missed it because I was working in modules/simpletest/tests/menu.test
... And there is more over in modules/menu/menu.test
Gah.
Comment #2
dman commentedProblem still exists on D8 - this (much smaller than the last one) test case demonstrates it. This patch applies to both D7 and D8 (with fuzz).
Comment #3
marcingy commentedThis is not a bug node_object_prepare is meant to be called to prepare a node for display on an edit form. If you are using the api directly you are not meant to use it see http://api.drupal.org/api/drupal/modules%21node%21node.module/function/n....
Comment #4
dman commentedThe documentation there says just "Prepares a node object for editing." It doesn't mention Forms.
Should that documentation be fixed?
Does this mean the
Usage example on that linked page is wrong?
And this article on Creating and Updating Nodes Programmatically in Drupal 7
and These instructions and a whole bunch of other cases have been doing this wrong always? Even though doing so really really helps when what you want to do is update a node?
menu_node_prepare() is the most effective (and only, short of rolling your own DB queries) way to load the menu item detail for the current node.
Even token.module calls menu_node_prepare() in order to build its info. Though it has hit this same problem too, was losing data and had to work around it.
This time I found that Feeds Node Processor is using 'prepare' as it seems like the right thing to do.
If they are all wrong too, so be it.
But if the intended use of that function is unclear to the rockstars that develop Feeds and Token, and the deletion of user data is by design - then it should probably be reviewed. At the very least, the docs could make it clear that this intentionally destroys things.
My proposal is - tweak it (easily) so that it doesn't throw away user data and cause this unexpected, unwanted behavior. Menu.module is the only one (that I've found) that uses hook_node_prepare as a reason to remove data.
Re-opening to get at least a second opinion on this. I don't really thing that unexpected data loss is "fixed, by design"
Comment #5
dman commentedThis problem has been surfaced before (WRT Feeds)
Core issue : #1247506: menu link deleted on programmatically updated nodes
Feeds issue: #1245094: Node menu link deleted on update
Comment #6
marcingy commentedIf anything yes it is a docs issue closing this issue this time as a duplicate of #1247506: menu link deleted on programmatically updated nodes.
Comment #7
dman commentedAdded a warning about this to The api docs page