Opened 2 years ago
Last modified 5 months ago
#61996 new defect (bug)
Slug Conflict When a Published and Draft Page Share post_name
| Reported by: | brookedot | Owned by: | |
|---|---|---|---|
| Priority: | normal | Milestone: | Future Release |
| Component: | Posts, Post Types | Version: | |
| Severity: | normal | Keywords: | good-first-bug has-test-info has-patch has-unit-tests |
| Cc: | Focuses: |
Description
It is possible (although it takes some work) to have a published page and a draft page that shares the same post_name. When this occurs if the draft page has a lower ID then it is retrieved by get_page_by_path in place of the published page. This is due at least in part to get_page_by_path not checking the page status and .
- Ensure permalinks are anything other than
Plain - Publish three nested pages, the first page here is needed to avoid a slug conflict when you later create pages with the same name. For example, to prevent
parentfrom becomingparent-2. The posts must be published so they can be selected as parent pages. Consider setting the page content to "this is draft" so it's easy to tell these apart later.
- Placeholder (slug
placeholder)- Parent (slug
/placehoder/parent- Child ( slug
placeholder/parent/child
- Child ( slug
- Parent (slug
- Using the Bulk Action, set all three newly created published posts to draft. There may be other ways to do this, as well.
- Publish two addition new pages with the same slugs as
parentandchild
- Parent ( slug
parent)- Child (slug
parent/child)
- Child (slug
- Using Quick Edit remove the parent from the draft page
parent(/placeholder/parent) by setting its parent page to `Main (no parent) keeping it as a draft.
You should now have parent and parent/child as drafts alongside your other two published pages.
- Ensure you don't have a
-2in eitherparent-2orchild-2
- If all goes well, when you visit
parent/childyou will now see a 404 instead of the pages you published in step 4.
There is a screen recording of these steps here:
https://cloudup.com/cxkwjscxDfB
They can be replicated in Multisite, Single site, and WordPress playground.
Some insight into what is happening is when you return the array used to get_page_by_path. For WordPress 6.6.1 that is https://github.com/WordPress/WordPress/blob/6.6.1/wp-includes/post.php#L5881
Using var_dump to return the array:
<?php $sql = " SELECT ID, post_name, post_parent, post_type FROM $wpdb->posts WHERE post_name IN ($in_string) AND post_type IN ($post_type_in_string) "; $pages = $wpdb->get_results( $sql, ARRAY_A ); $revparts = array_reverse( $parts ); var_dump( $pages );
This returns post ID 63 first which is our draft child page thus a 404 on the front end. ID 69 is the published child page which is never displayed on the front end as ID 63 is returned first.
Here is the output of the above var_dump()
<?php array (size=4) 0 => array (size=4) 'ID' => string '63' (length=2) 'post_name' => string 'child' (length=5) 'post_parent' => string '61' (length=2) 'post_type' => string 'page' (length=4) 1 => array (size=4) 'ID' => string '69' (length=2) 'post_name' => string 'child' (length=5) 'post_parent' => string '65' (length=2) 'post_type' => string 'page' (length=4) 2 => array (size=4) 'ID' => string '61' (length=2) 'post_name' => string 'parent' (length=6) 'post_parent' => string '0' (length=1) 'post_type' => string 'page' (length=4) 3 => array (size=4) 'ID' => string '65' (length=2) 'post_name' => string 'parent' (length=6) 'post_parent' => string '0' (length=1) 'post_type' => string 'page' (length=4)
A few other related trac issues were found but nothing that matched this exactly. Please update the Component to match the closest, I almost set this as permalinks
Related to #13459 (but not a duplicate as this is the same post type)
Props @trepmal who was instrumental in debugging and reporting this bug.
Change History (5)
#1
@
13 months ago
- Keywords needs-patch good-first-bug has-test-info added
- Milestone Awaiting Review → Future Release
This ticket was mentioned in PR #9675 on WordPress/wordpress-develop by @sahilgidwani.
13 months ago
#2
- Keywords has-patch added; needs-patch removed
This PR updates get_page_by_path() to exclude non-public post statuses (such as draft, pending, and trash) from path resolution. Previously, unpublished pages sharing a slug with a published one could incorrectly hijack URL resolution and return the wrong post.
### Changes Made
- Added filtering by valid
post_statususingget_post_stati( array( 'public' => true, 'private' => true ) ). - Ensured only published and intentionally viewable posts are considered when resolving paths.
- Prevented draft or pending posts from conflicting with live URLs.
This ticket was mentioned in PR #11414 on WordPress/wordpress-develop by rdelbem.
5 months ago
#3
- Keywords has-unit-tests added
Summary
Fixes #61996 where get_page_by_path() could return a draft page instead of a published page when both share the same path. The function now prefers viewable statuses when multiple candidates match the same path.
Details
Includes post_status in the get_page_by_path() query.
Prefers viewable (published) pages over non-viewable (draft) pages when slugs conflict.
Keeps existing behavior of preferring the requested post type over attachments.
Tests
phpunit tests/phpunit/tests/post/getPageByPath.php new regression test added
Trac ticket:
https://core.trac.wordpress.org/ticket/61996
## Use of AI Tools
AI assistance: Yes
Tool(s): Codex
Model(s): GPT-5.2-Codex
Used for: code suggestions and test skeleton; final implementation and tests were reviewed and edited by me, I also performed e2e tests to guarantee the expected result.
This ticket was mentioned in PR #11481 on WordPress/wordpress-develop by @vijendrajat.
5 months ago
#4
Introduces the post_title_child_separator filter in WP_Posts_List_Table::column_title(), allowing the hardcoded em-dash hierarchy indicator to be replaced with a custom string. The separator string is passed through the filter once and then repeated per hierarchy level.
Renames the internal $pad variable to $separator for clarity.
Trac ticket: https://core.trac.wordpress.org/ticket/61996
## Use of AI Tools
This ticket was mentioned in PR #11509 on WordPress/wordpress-develop by @vijendrajat.
5 months ago
#5
Added post_status to the SQL SELECT in get_page_by_path() and replaced the first-match-wins loop with a rank-scoring system using $type_rank (requested type vs attachment) and $status_rank (publish vs everything else), so a published page always wins over a draft sharing the same slug. Also fixed $post_type array handling using in_array() instead of ===.
Trac ticket: https://core.trac.wordpress.org/ticket/61996
## Use of AI Tools
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)
Reproduction Report
Description
✅ This report validates that the issue can be reproduced.
Environment
Test Instructions
Actual Results
Additional Notes
post_namewhich causes a collision. Same happens for the First Parent and Second parent as they also share the samepost_name. When changing the hierarchy, the system is not checking for collisions inpost_namehence adding the corresponding-2,-3like when we create a new post with the exact same slug.wp_unique_post_slugfunction and could be somewhere around these lines.