From 07f913d996e46c6aa55df878f2d8f56925a4d0fd Mon Sep 17 00:00:00 2001 From: Kushagra Goyal Date: Fri, 11 Jul 2025 17:53:02 +0530 Subject: [PATCH 1/3] feat: add filter for customizable post statuses using plain permalinks --- src/wp-includes/link-template.php | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/link-template.php b/src/wp-includes/link-template.php index cf41630c4a8c5..c50cc31cc70fe 100644 --- a/src/wp-includes/link-template.php +++ b/src/wp-includes/link-template.php @@ -124,6 +124,29 @@ function wp_force_plain_post_permalink( $post = null, $sample = null ) { return true; } + /** + * Filters the post statuses that should use plain permalinks instead of pretty permalinks. + * + * By default, posts with 'draft', 'pending', 'auto-draft', and 'future' statuses + * will use plain permalinks (e.g., /?p=123) for security and consistency reasons. + * + * To allow scheduled posts to use pretty permalinks, you can remove 'future' from this array: + * + * add_filter( 'wp_force_plain_post_permalink_statuses', function( $statuses ) { + * return array_diff( $statuses, [ 'future' ] ); + * }); + * + * @since 6.9.0 + * + * @param string[] $statuses Array of post status names that should use plain permalinks. + * @param WP_Post $post The post object. + */ + $plain_permalink_statuses = apply_filters( + 'wp_force_plain_post_permalink_statuses', + array( 'draft', 'pending', 'auto-draft', 'future' ), + $post + ); + if ( // Publicly viewable links never have plain permalinks. is_post_status_viewable( $post_status_obj ) || @@ -133,7 +156,9 @@ function wp_force_plain_post_permalink( $post = null, $sample = null ) { current_user_can( 'read_post', $post->ID ) ) || // Protected posts don't have plain links if getting a sample URL. - ( $post_status_obj->protected && $sample ) + ( $post_status_obj->protected && $sample ) || + // Allow customization of which post statuses should use plain permalinks. + ! in_array( $post->post_status, $plain_permalink_statuses, true ) ) { return false; } From f63783f8d538175dc9268d92f7d6958bc6a91f32 Mon Sep 17 00:00:00 2001 From: Kushagra Goyal Date: Wed, 16 Jul 2025 00:40:16 +0530 Subject: [PATCH 2/3] feat: add tests for wp_force_plain_post_permalink statuses filter functionality --- tests/phpunit/tests/link.php | 154 +++++++++++++++++++++++++++++++++++ 1 file changed, 154 insertions(+) diff --git a/tests/phpunit/tests/link.php b/tests/phpunit/tests/link.php index d05a6b210f636..200c8d6687070 100644 --- a/tests/phpunit/tests/link.php +++ b/tests/phpunit/tests/link.php @@ -187,4 +187,158 @@ public function test_attachment_attached_to_non_existent_post_type_has_a_pretty_ $this->go_to( get_permalink( $attachment_id ) ); $this->assertQueryTrue( 'is_attachment', 'is_single', 'is_singular' ); } + + /** + * @ticket 32322 + */ + public function test_wp_force_plain_post_permalink_statuses_filter_future_posts_default() { + $post_id = self::factory()->post->create( + array( + 'post_status' => 'future', + 'post_date' => date( 'Y-m-d H:i:s', strtotime( '+1 day' ) ), + 'post_name' => 'future-post', + ) + ); + + $post = get_post( $post_id ); + + // Should return true (force plain permalink) for future posts by default. + $this->assertTrue( wp_force_plain_post_permalink( $post ) ); + } + + /** + * @ticket 32322 + */ + public function test_wp_force_plain_post_permalink_statuses_filter_allows_customization() { + // Add filter to allow future posts to use pretty permalinks. + add_filter( + 'wp_force_plain_post_permalink_statuses', + function( $statuses ) { + return array_diff( $statuses, array( 'future' ) ); + } + ); + + $post_id = self::factory()->post->create( + array( + 'post_status' => 'future', + 'post_date' => date( 'Y-m-d H:i:s', strtotime( '+1 day' ) ), + 'post_name' => 'future-post', + ) + ); + + $post = get_post( $post_id ); + + // Should return false (allow pretty permalink) when filter is applied. + $this->assertFalse( wp_force_plain_post_permalink( $post ) ); + + // Clean up. + remove_all_filters( 'wp_force_plain_post_permalink_statuses' ); + } + + /** + * @ticket 32322 + */ + public function test_wp_force_plain_post_permalink_statuses_filter_selective_by_status() { + // Add filter to allow only future posts to use pretty permalinks. + add_filter( + 'wp_force_plain_post_permalink_statuses', + function( $statuses ) { + return array_diff( $statuses, array( 'future' ) ); + } + ); + + $future_post_id = self::factory()->post->create( + array( + 'post_status' => 'future', + 'post_date' => date( 'Y-m-d H:i:s', strtotime( '+1 day' ) ), + 'post_name' => 'future-post', + ) + ); + + $draft_post_id = self::factory()->post->create( + array( + 'post_status' => 'draft', + 'post_name' => 'draft-post', + ) + ); + + $future_post = get_post( $future_post_id ); + $draft_post = get_post( $draft_post_id ); + + // Future post should use pretty permalink. + $this->assertFalse( wp_force_plain_post_permalink( $future_post ) ); + + // Draft post should still use plain permalink. + $this->assertTrue( wp_force_plain_post_permalink( $draft_post ) ); + + // Clean up. + remove_all_filters( 'wp_force_plain_post_permalink_statuses' ); + } + + /** + * @ticket 32322 + */ + public function test_wp_force_plain_post_permalink_statuses_filter_selective_by_post_type() { + // Add filter to allow future posts pretty permalinks only for 'post' type. + add_filter( + 'wp_force_plain_post_permalink_statuses', + function( $statuses, $post ) { + if ( 'future' === $post->post_status && 'post' === $post->post_type ) { + return array_diff( $statuses, array( 'future' ) ); + } + return $statuses; + }, + 10, + 2 + ); + + // Create future post. + $post_id = self::factory()->post->create( + array( + 'post_status' => 'future', + 'post_date' => date( 'Y-m-d H:i:s', strtotime( '+1 day' ) ), + 'post_name' => 'future-post', + ) + ); + + // Create future page. + $page_id = self::factory()->post->create( + array( + 'post_type' => 'page', + 'post_status' => 'future', + 'post_date' => date( 'Y-m-d H:i:s', strtotime( '+1 day' ) ), + 'post_name' => 'future-page', + ) + ); + + $post = get_post( $post_id ); + $page = get_post( $page_id ); + + // Future post should use pretty permalinks. + $this->assertFalse( wp_force_plain_post_permalink( $post ) ); + + // Future page should still use plain permalinks. + $this->assertTrue( wp_force_plain_post_permalink( $page ) ); + + // Clean up. + remove_all_filters( 'wp_force_plain_post_permalink_statuses' ); + } + + /** + * @ticket 32322 + */ + public function test_wp_force_plain_post_permalink_statuses_filter_published_posts_unaffected() { + $post_id = self::factory()->post->create( + array( + 'post_status' => 'publish', + 'post_name' => 'published-post', + ) + ); + + $post = get_post( $post_id ); + + // Should return false (allow pretty permalink) for published posts. + $this->assertFalse( wp_force_plain_post_permalink( $post ) ); + } + } From ccf9ae7b2b0ebc7cf2cb0c268d3595210a6724f4 Mon Sep 17 00:00:00 2001 From: Kushagra Goyal Date: Wed, 16 Jul 2025 00:51:10 +0530 Subject: [PATCH 3/3] fix: use gmdate for future post dates in permalink tests --- tests/phpunit/tests/link.php | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/tests/phpunit/tests/link.php b/tests/phpunit/tests/link.php index 200c8d6687070..b809bd15653af 100644 --- a/tests/phpunit/tests/link.php +++ b/tests/phpunit/tests/link.php @@ -195,7 +195,7 @@ public function test_wp_force_plain_post_permalink_statuses_filter_future_posts_ $post_id = self::factory()->post->create( array( 'post_status' => 'future', - 'post_date' => date( 'Y-m-d H:i:s', strtotime( '+1 day' ) ), + 'post_date' => gmdate( 'Y-m-d H:i:s', strtotime( '+1 day' ) ), 'post_name' => 'future-post', ) ); @@ -213,7 +213,7 @@ public function test_wp_force_plain_post_permalink_statuses_filter_allows_custom // Add filter to allow future posts to use pretty permalinks. add_filter( 'wp_force_plain_post_permalink_statuses', - function( $statuses ) { + function ( $statuses ) { return array_diff( $statuses, array( 'future' ) ); } ); @@ -221,7 +221,7 @@ function( $statuses ) { $post_id = self::factory()->post->create( array( 'post_status' => 'future', - 'post_date' => date( 'Y-m-d H:i:s', strtotime( '+1 day' ) ), + 'post_date' => gmdate( 'Y-m-d H:i:s', strtotime( '+1 day' ) ), 'post_name' => 'future-post', ) ); @@ -242,7 +242,7 @@ public function test_wp_force_plain_post_permalink_statuses_filter_selective_by_ // Add filter to allow only future posts to use pretty permalinks. add_filter( 'wp_force_plain_post_permalink_statuses', - function( $statuses ) { + function ( $statuses ) { return array_diff( $statuses, array( 'future' ) ); } ); @@ -250,7 +250,7 @@ function( $statuses ) { $future_post_id = self::factory()->post->create( array( 'post_status' => 'future', - 'post_date' => date( 'Y-m-d H:i:s', strtotime( '+1 day' ) ), + 'post_date' => gmdate( 'Y-m-d H:i:s', strtotime( '+1 day' ) ), 'post_name' => 'future-post', ) ); @@ -282,7 +282,7 @@ public function test_wp_force_plain_post_permalink_statuses_filter_selective_by_ // Add filter to allow future posts pretty permalinks only for 'post' type. add_filter( 'wp_force_plain_post_permalink_statuses', - function( $statuses, $post ) { + function ( $statuses, $post ) { if ( 'future' === $post->post_status && 'post' === $post->post_type ) { return array_diff( $statuses, array( 'future' ) ); } @@ -296,7 +296,7 @@ function( $statuses, $post ) { $post_id = self::factory()->post->create( array( 'post_status' => 'future', - 'post_date' => date( 'Y-m-d H:i:s', strtotime( '+1 day' ) ), + 'post_date' => gmdate( 'Y-m-d H:i:s', strtotime( '+1 day' ) ), 'post_name' => 'future-post', ) ); @@ -306,7 +306,7 @@ function( $statuses, $post ) { array( 'post_type' => 'page', 'post_status' => 'future', - 'post_date' => date( 'Y-m-d H:i:s', strtotime( '+1 day' ) ), + 'post_date' => gmdate( 'Y-m-d H:i:s', strtotime( '+1 day' ) ), 'post_name' => 'future-page', ) ); @@ -340,5 +340,4 @@ public function test_wp_force_plain_post_permalink_statuses_filter_published_pos // Should return false (allow pretty permalink) for published posts. $this->assertFalse( wp_force_plain_post_permalink( $post ) ); } - }