Make WordPress Core

Opened 8 years ago

Last modified 7 weeks ago

#44805 assigned defect (bug)

Resurrecting post from trash reverts its slug

Reported by: ajmccluskey Owned by:
Priority: normal Milestone: Future Release
Component: Posts, Post Types Version: 4.5
Severity: normal Keywords: good-first-bug has-test-info has-patch has-unit-tests
Cc: Focuses:

Description

Steps to reproduce

Using the REST API:

  • Create a post
  • Delete the post (not forced)
  • Update the post's slug
  • Update the post's status to publish

Create

POST wp-json/wp/v2/posts

{
  "status": "publish",
  "slug": "a",
  "title": "a"
}

Delete

DELETE wp-json/wp/v2/posts/<id>

Update slug

POST wp-json/wp/v2/posts/<id>

{
  "slug": "foo"
}

Update status

POST wp-json/wp/v2/posts/<id>

{
  "status": "publish"
}

Expected

Post should be published with a slug of foo (the updated value)

Actual

Post is published with a slug of a (the old value)

Attachments (3)

post.php.diff​ (1.7 KB ) - added by schimmilol 12 months ago.
Add a case for trashed posts in the 'wp_insert_post' function and modify the 'wp_add_trashed_suffix_to_post_name_for_post' function with optional $post_name parameter
44805.diff​ (1.7 KB ) - added by schimmilol 12 months ago.
Corrected the filename
44805_1.diff​ (1.9 KB ) - added by schimmilol 12 months ago.
Forgot some logic in 'wp_add_trashed_suffix_to_post_name_for_post' function. (Sorry, its my first patch)

Download all attachments as: .zip

Change History (12)

#1 follow-up: @JPry
8 years ago

  • Component General → Posts, Post Types
  • Focuses rest-api removed
  • Keywords 2nd-opinion added

This isn't unique to the REST API. You can observe the same behavior by using wp_update_post() directly with PHP.

<?php
$id = wp_insert_post( [
    'post_status' => 'publish',
    'post_name'   => 'a',
    'post_title'  => 'a',
] );

wp_update_post( [
    'ID'          => $id,
    'post_status' => 'trash',
] );

wp_update_post( [
    'ID'        => $id,
    'post_name' => 'foo',
] );

wp_update_post( [
    'ID'          => $id,
    'post_status' => 'publish',
] );

$post = get_post( $id );

// $post->post_name will be 'a' instead of 'foo'.

This is caused by this block within wp_insert_post():

<?php
        /*
         * If the post is being untrashed and it has a desired slug stored in post meta,
         * reassign it.
         */
        if ( 'trash' === $previous_status && 'trash' !== $post_status ) {
                $desired_post_slug = get_post_meta( $post_ID, '_wp_desired_post_slug', true );
                if ( $desired_post_slug ) {
                        delete_post_meta( $post_ID, '_wp_desired_post_slug' );
                        $post_name = $desired_post_slug;
                }
        }

When the slug is changed for a trashed post, the _wp_desired_post_slug meta is never updated to match the new slug. This is a bit of an edge case, because once a post is trashed, the slug is modified, as seen just a few lines below the previous snippet:

<?php
        // When trashing an existing post, change its slug to allow non-trashed posts to use it.
        if ( 'trash' === $post_status && 'trash' !== $previous_status && 'new' !== $previous_status ) {
                $post_name = wp_add_trashed_suffix_to_post_name_for_post( $post_ID );
        }

In the case of this example, it will be set to a__trashed. The expectation is that a trashed post's slug is not canonical, and the slug is able to be used by other posts and is not blocked from reuse by a trashed post.

With that in mind, I'm not convinced that this is actually a bug. But I think it's still worth some discussion to determine what should be the correct behavior for this edge case.

Out of curiosity, why would you want to change the slug of a trashed post in the first place?

#2 in reply to: ↑ 1 @ajmccluskey
8 years ago

Thanks for the quick reply @JPry.

As a user who is not familiar with WordPress' codebase, this seems like quite strange behaviour to me. I would generally expect that whenever I change content, especially anything user facing, the change sticks.

I agree that editing a trashed post might happen less frequently than other scenarios, however that doesn't alter my expectations. Given the size of WordPress' user base, I doubt that I'm the first or last person to do this either.

This behaviour is also seen when you update the post to published and change its slug in the same update. This might be a more likely scenario, and I would argue that a user's expectations in taking this action are very likely "publish this post with the changes that I'm making".

Last edited 8 years ago by ajmccluskey (previous) (diff)

#3 @johnbillion
8 years ago

  • Keywords reporter-feedback added

Thanks for the report @ajmccluskey! What's the use case for editing the slug of a post while it's in the trash?

#4 @ajmccluskey
8 years ago

Hi @johnbillion ,

I don't have a use case. I was testing out the API and, in lieu of a spec, I went with my expectations of what the API should do. In some cases I've adjusted my expectations, but in this case I can't think of a good reason for user changes to not be persisted, so I filed it as a bug.

I agree that this might be rare or unlikely, and therefore a low priority, but it's still part of the public API so I think it should be addressed.

#5 @SirLouen
12 months ago

  • Keywords needs-patch good-first-bug has-test-info added; 2nd-opinion reporter-feedback removed
  • Milestone Awaiting Review → Future Release
  • Version 4.9.7 → 4.5

Reproduction Report

Description

✅ This report validates whether the issue can be reproduced.

Environment

  • WordPress: 6.9-alpha-60093-src
  • PHP: 8.2.29
  • Server: nginx/1.29.1
  • Database: mysqli (Server: 8.4.6 / Client: mysqlnd 8.2.29)
  • Browser: Chrome 140.0.0.0
  • OS: Windows 10/11
  • Theme: Twenty Sixteen 3.6
  • MU Plugins: None activated
  • Plugins:
    • BBB Testing Dolly
    • Classic Editor 1.6.7
    • Test Reports 1.2.0

Testing Instructions

Actual Results

  1. ✅ Error condition occurs (reproduced).

Additional Notes

  • I can confirm this is happening, the logic brought in #11863/[36607] was not complete. This is a very edge case, but still, as @ajmccluskey and @JPry have suggested, it's happening, and it's relatively simple to be sorted.

@schimmilol
12 months ago

Add a case for trashed posts in the 'wp_insert_post' function and modify the 'wp_add_trashed_suffix_to_post_name_for_post' function with optional $post_name parameter

#6 @schimmilol
12 months ago

  • Keywords has-patch added; needs-patch removed

@schimmilol
12 months ago

Corrected the filename

@schimmilol
12 months ago

Forgot some logic in 'wp_add_trashed_suffix_to_post_name_for_post' function. (Sorry, its my first patch)

This ticket was mentioned in ​PR #11972 on ​WordPress/wordpress-develop by ​@arkaprabhachowdhury.


4 months ago
#7

  • Keywords has-unit-tests added

## Summary
This updates the trashed-post slug flow so WordPress preserves an explicitly updated slug when a post is restored from rash, and keeps _wp_desired_post_slug in sync when the slug changes while the post remains trashed.

## Problem
In Trac ticket #44805, changing a post slug while the post was trashed could be lost on restore/publish. There was also a related case where publishing from rash with a new slug in the same update could incorrectly restore the older stored desired slug.

rtCamp tracking issue: ​https://github.com/rtCamp/wp-contributions/issues/3007

## Changes

  • Only restore _wp_desired_post_slug when the slug was not actually changed in the current update.
  • When a trashed post's slug is updated while it remains in rash, store that updated desired slug and keep the persisted trashed slug suffixed.
  • Add regression tests for:
    • updating the slug while trashed
    • publishing after updating the slug while trashed
    • publishing with a new slug in the same update

## Root cause
wp_update_post() merges the existing database row into the update payload, so simply checking whether post_name is set is not enough to determine whether the slug changed in the current request.

## Testing

  • phpcbf --standard=phpcs.xml.dist src/wp-includes/post.php tests/phpunit/tests/post/wpInsertPost.php
  • phpcs --standard=phpcs.xml.dist src/wp-includes/post.php tests/phpunit/tests/post/wpInsertPost.php

ode ./tools/local-env/scripts/docker.js run --rm php ./vendor/bin/phpunit tests/phpunit/tests/post/wpInsertPost.php --filter "test_(updating_a_trashed_posts_slug_should_update_the_stored_desired_post_name|publishing_a_trashed_post_should_keep_an_updated_slug|publishing_a_trashed_post_with_a_new_slug_in_the_same_update_should_keep_the_new_slug)"

---
This Pull Request is for code review only. Please keep all other discussion in the Trac ticket.

#8 @arkaprabhachowdhury
7 weeks ago

Opened [PR #11972](​https://github.com/WordPress/wordpress-develop/pull/11972) to address this ticket. Summary This updates the trashed-post slug flow so WordPress preserves an explicitly updated slug when a post is restored from rash, and keeps _wp_desired_post_slug in sync when the slug changes while the post remains trashed. Problem In Trac ticket #44805, changing a post slug while the post was trashed could be lost on restore/publish. There was also a related case where publishing from rash with a new slug in the same update could incorrectly restore the older stored desired slug. Changes - O The PR includes focused regression coverage, and its validation details are recorded in the PR description.

I picked this up because the ticket describes a concrete core behavior or edge case that remains relevant in current WordPress. The proposed change is intentionally scoped to the ticket and is submitted here for code review, with the technical discussion remaining on this ticket.

This ticket was mentioned in ​PR #13125 on ​WordPress/wordpress-develop by ​@wprashed.


7 weeks ago
#9

## Trac Ticket
https://core.trac.wordpress.org/ticket/44805

## Description

This PR ensures that updating the slug of a trashed post or publishing a trashed post with an updated slug preserves the new slug instead of reverting to the original slug stored when the post was first trashed.

### The Problem

When a post is trashed, wp_add_trashed_suffix_to_post_name_for_post() stores the original slug in _wp_desired_post_slug post meta and appends __trashed to the post name in the database.

However:

  1. If a post's slug is updated while it remains in trash, _wp_desired_post_slug was not updated to reflect the new desired slug.
  2. When the post is untrashed/published, wp_insert_post() unconditionally restored _wp_desired_post_slug, overwriting any new slug that was provided during the update or while the post was in trash.

### The Fix

  1. In wp_insert_post(): When untrashing a post, only reassign _wp_desired_post_slug if no new slug was passed during the update (i.e. ! $post_before || $post_name === $post_before->post_name).
  2. In wp_insert_post(): When updating a trashed post that remains trashed, update the desired slug meta using wp_add_trashed_suffix_to_post_name_for_post( $post_id, $post_name ).
  3. In wp_add_trashed_suffix_to_post_name_for_post(): Added an optional $post_name parameter to allow passing the new desired slug and updating _wp_desired_post_slug meta via update_post_meta().
  4. Unit Tests: Added tests in tests/phpunit/tests/post/wpInsertPost.php covering:
    • Updating a trashed post's slug updates the stored desired post name.
    • Publishing a trashed post after updating its slug keeps the updated slug.
    • Publishing a trashed post with a new slug in the same update keeps the new slug.

Fixes #44805.

Note: See TracTickets for help on using tickets.