Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 21 additions & 5 deletions src/wp-includes/post.php
Original file line number Diff line number Diff line change
Expand Up @@ -6222,7 +6222,7 @@ function get_page_by_path( $page_path, $output = OBJECT, $post_type = 'page' ) {
$post_types = esc_sql( $post_types );
$post_type_in_string = "'" . implode( "','", $post_types ) . "'";
$sql = "
SELECT ID, post_name, post_parent, post_type
SELECT ID, post_name, post_parent, post_type, post_status
FROM $wpdb->posts
WHERE post_name IN ($in_string)
AND post_type IN ($post_type_in_string)
Expand All @@ -6232,7 +6232,10 @@ function get_page_by_path( $page_path, $output = OBJECT, $post_type = 'page' ) {

$revparts = array_reverse( $parts );

$found_id = 0;
$found_id = 0;
$found_type_rank = 0;
$found_status_rank = 0;

foreach ( (array) $pages as $page ) {
if ( $page->post_name === $revparts[0] ) {
$count = 0;
Expand All @@ -6255,9 +6258,22 @@ function get_page_by_path( $page_path, $output = OBJECT, $post_type = 'page' ) {
&& count( $revparts ) === $count + 1
&& $p->post_name === $revparts[ $count ]
) {
$found_id = $page->ID;
if ( $page->post_type === $post_type ) {
break;
$is_type_match = is_array( $post_type )
? in_array( $page->post_type, $post_type, true )
: ( $page->post_type === $post_type );

$type_rank = $is_type_match ? 2 : 1;
$status_rank = ( 'publish' === $page->post_status ) ? 2 : 1;

if ( $type_rank > $found_type_rank || ( $type_rank === $found_type_rank && $status_rank > $found_status_rank ) ) {
$found_id = $page->ID;
$found_type_rank = $type_rank;
$found_status_rank = $status_rank;

// Perfect match: correct type and viewable status. No need to scan further.
if ( 2 === $found_type_rank && 2 === $found_status_rank ) {
break;
}
}
}
}
Expand Down
152 changes: 152 additions & 0 deletions tests/phpunit/tests/post/getPageByPath.php
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,158 @@ public function test_cache_should_be_invalidated_when_post_name_is_edited() {
$this->assertSame( $num_queries, get_num_queries() );
}

/**
* @ticket 61996
*
* @covers ::get_page_by_path
*/
public function test_should_prefer_published_page_over_draft_with_same_slug() {
global $wpdb;

// Create a published page first so we can use it as a target.
$published = self::factory()->post->create(
array(
'post_type' => 'page',
'post_name' => 'conflict-slug',
'post_status' => 'publish',
)
);

// Force a draft to share the same post_name (bypass the uniqueness check).
$draft = self::factory()->post->create(
array(
'post_type' => 'page',
'post_name' => 'draft-slug',
'post_status' => 'draft',
)
);
$wpdb->update( $wpdb->posts, array( 'post_name' => 'conflict-slug' ), array( 'ID' => $draft ) );
clean_post_cache( $draft );

$found = get_page_by_path( 'conflict-slug' );

$this->assertSame( $published, $found->ID, 'Published page should be preferred over a draft sharing the same slug.' );
}

/**
* @ticket 61996
*
* @covers ::get_page_by_path
*/
public function test_should_prefer_published_nested_page_over_draft_with_same_path() {
global $wpdb;

// Create the published hierarchy: parent/child.
$published_parent = self::factory()->post->create(
array(
'post_type' => 'page',
'post_name' => 'shared-parent',
'post_status' => 'publish',
)
);
$published_child = self::factory()->post->create(
array(
'post_type' => 'page',
'post_name' => 'shared-child',
'post_status' => 'publish',
'post_parent' => $published_parent,
)
);

// Create draft pages and force them to share the same post_names.
$draft_parent = self::factory()->post->create(
array(
'post_type' => 'page',
'post_name' => 'draft-parent',
'post_status' => 'draft',
)
);
$draft_child = self::factory()->post->create(
array(
'post_type' => 'page',
'post_name' => 'draft-child',
'post_status' => 'draft',
'post_parent' => $draft_parent,
)
);

$wpdb->update( $wpdb->posts, array( 'post_name' => 'shared-parent' ), array( 'ID' => $draft_parent ) );
$wpdb->update( $wpdb->posts, array( 'post_name' => 'shared-child' ), array( 'ID' => $draft_child ) );
clean_post_cache( $draft_parent );
clean_post_cache( $draft_child );

$found = get_page_by_path( 'shared-parent/shared-child' );

$this->assertSame( $published_child, $found->ID, 'Published nested page should be preferred over a draft sharing the same path.' );
}

/**
* @ticket 61996
*
* @covers ::get_page_by_path
*/
public function test_should_return_draft_when_no_viewable_page_exists_with_same_slug() {
global $wpdb;

$draft_a = self::factory()->post->create(
array(
'post_type' => 'page',
'post_name' => 'only-draft-slug',
'post_status' => 'draft',
)
);

// A second draft shares the slug; the first one (lower ID) should be returned
// since neither is viewable and existing fallback behaviour is preserved.
$draft_b = self::factory()->post->create(
array(
'post_type' => 'page',
'post_name' => 'only-draft-slug-b',
'post_status' => 'draft',
)
);
$wpdb->update( $wpdb->posts, array( 'post_name' => 'only-draft-slug' ), array( 'ID' => $draft_b ) );
clean_post_cache( $draft_b );

$found = get_page_by_path( 'only-draft-slug' );

$this->assertNotNull( $found, 'A draft page should still be returned when no viewable page exists.' );
$this->assertContains( $found->ID, array( $draft_a, $draft_b ), 'The returned page should be one of the drafts sharing the slug.' );
}

/**
* @ticket 61996
*
* @covers ::get_page_by_path
*/
public function test_should_prefer_published_page_over_draft_when_post_type_is_array() {
global $wpdb;

register_post_type( 'wptests_pt' );

$published = self::factory()->post->create(
array(
'post_type' => 'wptests_pt',
'post_name' => 'array-type-conflict-slug',
'post_status' => 'publish',
)
);

$draft = self::factory()->post->create(
array(
'post_type' => 'wptests_pt',
'post_name' => 'array-type-draft-slug',
'post_status' => 'draft',
)
);
$wpdb->update( $wpdb->posts, array( 'post_name' => 'array-type-conflict-slug' ), array( 'ID' => $draft ) );
clean_post_cache( $draft );

$found = get_page_by_path( 'array-type-conflict-slug', OBJECT, array( 'wptests_pt' ) );

$this->assertSame( $published, $found->ID, 'Published page should be preferred over a draft when $post_type is passed as an array.' );
}

/**
* @ticket 37611
*/
Expand Down
Loading