Repository navigation
Menus: Do not gate wp_get_nav_menu_items() on the menu term count - #13596
rashmicodewing wants to merge 1 commit into
Conversation
`wp_get_nav_menu_items()` short-circuited to an empty array whenever the `nav_menu` term count was zero, before running its item query. The term count is not a safe proxy for "no items match these args". `_update_post_term_count()` counts published items only, so a menu holding nothing but drafts reports a count of zero. The count can also be stale: `wp_defer_term_counting( true )` leaves it at zero while items are already attached, and `wp_get_nav_menu_object()` returns a passed `WP_Term` object verbatim, so a snapshot taken before items were added gates the query too. Since `post_status` is a documented argument of the function, this returned an empty array for a draft-only menu queried with `draft`, `any`, or `publish,draft`, for a trash-only menu queried with `trash`, and for published items attached while term counting was deferred. In core this made `wp_update_nav_menu_item()` position every new item at `menu_order` 0 on a menu with no published items, and made `_wp_auto_add_pages_to_menu()` insert a duplicate item for a page already present as a draft. `wp_get_nav_menu_to_edit()` and `wp_nav_menu_update_menu_items()` were affected the same way. Always run the item query and let it decide whether matching items exist, restoring the behaviour from before [53010]. Adds regression tests for each case. Fixes #66102. See #55372. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the Unlinked AccountsThe following contributors have not linked their GitHub and WordPress.org accounts: @rashmicodewing. Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
Test using WordPress PlaygroundThe changes in this pull request can previewed and tested using a WordPress Playground instance. WordPress Playground is an experimental project that creates a full WordPress instance entirely within the browser. Some things to be aware of
For more details about these limitations and more, check out the Limitations page in the WordPress Playground documentation. |
|
Closing this — it duplicates #13513, which was opened four days earlier by @MarcinDudekDev and makes the same change to #13513 already has a test report on it and should be the one reviewed for #66102. Deferring to it. |
Trac ticket: https://core.trac.wordpress.org/ticket/66102
Problem
wp_get_nav_menu_items()short-circuits before running its item query whenever the menu term's count is0:$menu->countis not a safe proxy for "no items match these args":_update_post_term_count()counts posts withpost_status = 'publish'(the default of theupdate_post_term_count_statusesfilter), so a menu whose items are all draft or trash reportscount = 0even though a caller querying'draft','any','publish,draft', or'trash'has matching items.wp_defer_term_counting( true )leaves the count at0while items are already attached, andwp_get_nav_menu_object()returns a passedWP_Termobject verbatim, so a caller holding a term snapshot taken before items were added is gated as well.post_statusis a documented argument ofwp_get_nav_menu_items(), so all of the following returnarray()whileget_posts()with the same arguments returns real items:post_status'draft','any', or'publish,draft';'trash'(trashing the last published item returns the count to0);'publish'query;WP_Termsnapshot passed as$menu.In-core consequences
menu_order.wp_update_nav_menu_item()positions a new item by querying'publish,draft'items and using1 +the last item'smenu_order. On a menu with no published items — e.g. one being assembled as drafts — the gate empties the list, so the new item getsmenu_order = 0; every subsequent item collides at position 0 instead of being appended._wp_auto_add_pages_to_menu()queries the'publish,draft'item list to skip pages already in the menu. On a menu with no published items the check sees an empty list and inserts a second item for the same page.wp_get_nav_menu_to_edit()queries'any'andwp_nav_menu_update_menu_items()queries'draft,publish', so a draft-only menu lists empty on the edit screen and its items cannot be updated or deleted there.Steps to reproduce
Root cause
The gate was introduced in [53010] (556492a) for #55372, which replaced
get_objects_in_term()+$args['include']with atax_queryto save one query. The previous! empty( $items )check tested whether any objects were attached to the term at all — it was status-agnostic and reflected actual attachments. The replacement$menu->count > 0tests the published count only and can additionally be stale, so the gate now fires in cases the old check never hit.Fix
Remove the short-circuit and always run
get_posts( $args )— restoring the pre-[53010] approach of letting the query, not the term count, decide whether matching items exist.get_posts()already returnsarray()for a genuinely empty menu.A narrower alternative — keeping the gate only for the default
post_status === 'publish'query — was considered and rejected: deferred term counting and staleWP_Termsnapshots both leavecount = 0while published items exist, so a publish-only gate would still return a wrongarray()in precisely the cases this ticket covers.Performance impact of removing the gate
post-queriesgroup. (That result cache did not yet exist when the gate was added in 6.0.)SELECTper empty rendered menu per request — it joinsterm_relationshipsand filters onterm_taxonomy_idplus thetype_status_dateindex, returning 0 rows.Testing instructions
Run the new regression tests, which fail before the change and pass after:
Or reproduce manually with the snippet under "Steps to reproduce": before the change
$via_functionis empty while$via_queryis not; after the change both return the same item.Tests
Adds 8 regression cases to
Tests_Post_Nav_Menu, each tagged@ticket 66102:test_wp_get_nav_menu_items_returns_draft_items_from_uncounted_menu(x3, data provider)'draft','any','publish,draft'test_wp_get_nav_menu_items_returns_trashed_items_from_uncounted_menu'trash'test_wp_get_nav_menu_items_returns_items_while_term_counting_is_deferredwp_defer_term_counting( true )test_wp_get_nav_menu_items_returns_items_for_stale_menu_objectWP_Termsnapshot passed as$menutest_wp_update_nav_menu_item_appends_to_a_draft_only_menumenu_orderpositions stay distinct and ascendingtest_wp_auto_add_pages_to_menu_does_not_duplicate_a_draft_itemEach of the 8 fails with the
src/change reverted and passes with it applied.Locally (single site, PHP 8.2 / MySQL 8.0) the following all pass with the change applied:
--group menu(113 tests), the customize suite (119), the REST menus controllers (79), and--group ajax(190, 1 pre-existing skip). PHPCS is clean on both changed files.AI disclosure
AI assistance was used in preparing this patch, and is disclosed per the WordPress AI guidelines.
🤖 Generated with Claude Code