Full disclosure, this issue was written by an AI, with human oversight.
Problem/Motivation
I ran into this on a Drupal 10.6 site with a large main menu (~1,900 links) that we manage with BigMenu 2.1.0, with max_depth set to 1.
The symptom our content team reported: when you open a parent link's children with the "Edit child items" link and then drag one of those children to reorder it, saving the form quietly detaches that item and floats it (along with its own children) up to the top level of the menu. Editing the same item's parent from the node's Menu settings works fine, so it seems specific to the drag-and-drop overview form.
Steps to reproduce
- On a menu with at least three levels, set
bigmenu.settings:max_depthto 1. - Go to the menu overview (
/admin/structure/menu/manage/<menu>) and click "Edit child items" on a top-level parent. This loads?menu_link=<plugin_id>, a subtree view. - Drag one of the children to reorder it among its siblings and Save.
- Observe that the reordered item is no longer under its parent. It (and its own children) have moved to the menu root.
Proposed resolution
First, the cause as best I can tell. In the subtree view, BigMenuForm::getTree() calls setRoot($menu_link), so the subtree's root link itself is not rendered as a row in the table (it's only the breadcrumb at the top). That means Drupal's tabledrag has no parent row to anchor the visible children to, and on submit their hidden parent value comes back empty, i.e. the menu root.
BigMenuForm doesn't override submitOverviewForm(), so it inherits core's Drupal\menu_ui\MenuForm::submitOverviewForm(), which compares each row's submitted parent #value against its #default_value and, seeing empty vs. the real parent, dutifully re-parents the link to the root. So the detachment is really core doing what it's told with a bad parent value that BigMenu's depth-limited form produced. I confirmed the seam by invoking the two submit handlers directly against a real subtree child whose submitted parent was empty: core's handler set the child's parent to '' (detached), while a clamped version kept it in place.
The fix that worked for me is small and stays on BigMenu's side: remember which subtree is being edited, and in a submitOverviewForm() override, clamp an empty submitted parent back to the subtree root before handing off to the parent handler. A link that was only reordered then matches its #default_value and core leaves it alone; a link genuinely nested under a visible sibling still has a non-empty parent and moves as expected.
protected function submitOverviewForm(array $complete_form, FormStateInterface $form_state) {
// $root is set in buildOverviewForm() from the menu_link query arg.
$root = $form_state->get('bigmenu_root');
if (!empty($root) && isset($complete_form['links'])) {
foreach (Element::children($complete_form['links']) as $id) {
if (isset($complete_form['links'][$id]['#item'], $complete_form['links'][$id]['parent'])
&& (string) ($complete_form['links'][$id]['parent']['#value'] ?? '') === '') {
$complete_form['links'][$id]['parent']['#value'] = $root;
}
}
}
parent::submitOverviewForm($complete_form, $form_state);
}
This is the direction I went, but I'm not attached to it. Rendering the subtree root as a non-draggable anchor row so tabledrag can reference it would probably be a more thorough fix, at the cost of more UI churn. Happy to hear which the maintainers prefer.
Remaining tasks
- Decide on the approach (clamp empty parent vs. render an anchor row for the subtree root).
- I have a patch and can open an MR against
2.xif that's useful. - Automated coverage is awkward here since the trigger is JS tabledrag, but the submit handler is testable in isolation.
I looked through the queue and didn't find this exact bug reported. #3452491 is in the same area and may be related, though it describes a different symptom.
Comments