Index: src/wp-admin/includes/post.php =================================================================== --- src/wp-admin/includes/post.php (revision 36504) +++ src/wp-admin/includes/post.php (working copy) @@ -1226,6 +1226,9 @@ if ( !is_null($name) ) $post->post_name = sanitize_title($name ? $name : $title, $post->ID); + if ( $post->post_status != 'trash' && $post->post_name ) { + _add_suffix_to_post_name_for_trashed_posts( $post->post_name ); + } $post->post_name = wp_unique_post_slug($post->post_name, $post->ID, $post->post_status, $post->post_type, $post->post_parent); $post->filter = 'sample'; Index: src/wp-includes/default-filters.php =================================================================== --- src/wp-includes/default-filters.php (revision 36504) +++ src/wp-includes/default-filters.php (working copy) @@ -475,4 +475,7 @@ add_filter( 'oembed_dataparse', 'wp_filter_oembed_result', 10, 3 ); add_filter( 'oembed_response_data', 'get_oembed_response_data_rich', 10, 4 ); +// Untrashed posts should get their desired post slug. +add_action( 'transition_post_status', '_modify_post_name_on_transition_post_status', 5, 3 ); + unset( $filter, $action ); Index: src/wp-includes/post.php =================================================================== --- src/wp-includes/post.php (revision 36504) +++ src/wp-includes/post.php (working copy) @@ -3237,6 +3237,10 @@ */ $post_parent = apply_filters( 'wp_insert_post_parent', $post_parent, $post_ID, compact( array_keys( $postarr ) ), $postarr ); + if ( $post_status != 'trash' && $post_name ) { + _add_suffix_to_post_name_for_trashed_posts( $post_name ); + } + $post_name = wp_unique_post_slug( $post_name, $post_ID, $post_status, $post_type, $post_parent ); // Don't unslash. @@ -5992,3 +5996,52 @@ update_post_caches( $fresh_posts, 'any', $update_term_cache, $update_meta_cache ); } } + +/** + * When a post is untrashed give a post its desired slug (if it has one). + * + * @since 4.5.0 + * @access private + * + * @param string $new_status New post status. + * @param string $old_status Old post status. + * @param WP_Post $post Post object. + */ +function _modify_post_name_on_transition_post_status( $new_status, $old_status, $post ) { + // Only modify `post_name` when untrashing a post. + if ( 'trash' === $new_status || 'trash' !== $old_status ) { + return; + } + $desired_post_slug = get_post_meta( $post->ID, '_wp_desired_post_slug', true ); + delete_post_meta( $post->ID, '_wp_desired_post_slug' ); + if ( ! $desired_post_slug ) { + return; + } + $post->post_name = $desired_post_slug; + wp_update_post( $post ); +} + +/** + * If any trashed posts have a given slug, add a suffix. + * + * @since 4.5.0 + * @access private + * + * @param string $post_name Slug. + */ +function _add_suffix_to_post_name_for_trashed_posts( $post_name ) { + $trashed_posts_with_desired_slug = get_posts( array( + 'name' => $post_name, + 'post_status' => 'trash', + 'post_type' => 'any', + 'nopaging' => true, + ) ); + if ( ! empty( $trashed_posts_with_desired_slug ) ) { + foreach ( $trashed_posts_with_desired_slug as $_post ) { + // Store the desired slug so it can be reapplied if it is untrashed. + add_post_meta( $_post->ID, '_wp_desired_post_slug', $_post->post_name ); + $_post->post_name = _truncate_post_slug( $_post->post_name, 198 ) . "-2"; + wp_update_post( $_post ); + } + } +} Index: tests/phpunit/tests/post.php =================================================================== --- tests/phpunit/tests/post.php (revision 36504) +++ tests/phpunit/tests/post.php (working copy) @@ -1258,4 +1258,44 @@ $this->assertEquals( 0, get_post( $page_id )->post_parent ); } + /** + * @ticket 11863 + */ + function test_trashed_post_slugs_should_move_for_non_trashed_posts() { + $trashed_about_page_id = self::factory()->post->create( array( + 'post_type' => 'page', + 'post_title' => 'About', + 'post_status' => 'publish' + ) ); + wp_trash_post( $trashed_about_page_id ); + $about_page_id = self::factory()->post->create( array( + 'post_type' => 'page', + 'post_title' => 'About', + 'post_status' => 'publish' + ) ); + $this->assertEquals( 'about', get_post( $about_page_id )->post_name ); + $this->assertEquals( 'about-2', get_post( $trashed_about_page_id )->post_name ); + } + + /** + * @ticket 11863 + */ + function test_trashed_post_slugs_that_were_moved_should_be_reassigned_after_untrashing() { + $about_page_id = self::factory()->post->create( array( + 'post_type' => 'page', + 'post_title' => 'About', + 'post_status' => 'publish' + ) ); + wp_trash_post( $about_page_id ); + $another_about_page_id = self::factory()->post->create( array( + 'post_type' => 'page', + 'post_title' => 'About', + 'post_status' => 'publish' + ) ); + + wp_trash_post( $another_about_page_id ); + + wp_untrash_post( $about_page_id ); + $this->assertEquals( 'about', get_post( $about_page_id )->post_name ); + } }