Skip to content

Commit 1c80589

Browse files
committed
Posts, Taxonomy: Skip wp_set_post_categories() on update when post_category is not provided.
`wp_insert_post()` always called `wp_set_post_categories()` on update, even when the caller did not pass `post_category`. The fallback then re-set the post's existing category list, which still goes through term-cache invalidation, term-relationship lookups, and counted toward the per-request query budget — a measurable regression for callers like REST API updates that change a single field. This commit gates that call on either (a) it being a new post or (b) `post_category` being explicitly set in the args. The `tags_input` and `tax_input` paths were already correctly guarded. Adds tests covering: * `set_object_terms` does not fire for the `category` taxonomy on a parameterless update. * Existing categories survive a parameterless update (regression guard). See #59354.
1 parent baa9f53 commit 1c80589

2 files changed

Lines changed: 70 additions & 1 deletion

File tree

src/wp-includes/post.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4997,7 +4997,7 @@ function wp_insert_post( $postarr, $wp_error = false, $fire_after_hooks = true )
49974997
clean_post_cache( $post_id );
49984998
}
49994999

5000-
if ( is_object_in_taxonomy( $post_type, 'category' ) ) {
5000+
if ( is_object_in_taxonomy( $post_type, 'category' ) && ( ! $update || isset( $postarr['post_category'] ) ) ) {
50015001
wp_set_post_categories( $post_id, $post_category );
50025002
}
50035003

tests/phpunit/tests/post/wpInsertPost.php

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1530,4 +1530,73 @@ public function test_scheduled_post_with_a_past_date_should_be_published() {
15301530

15311531
$this->assertSame( 'future', get_post_status( $post_id ) );
15321532
}
1533+
1534+
/**
1535+
* Verify that updating a post without `post_category` skips the `wp_set_post_categories()` call.
1536+
*
1537+
* `wp_set_post_categories()` triggers term-query and cache work even when its argument is the
1538+
* post's existing category list, so calling it on every update is a measurable regression.
1539+
*
1540+
* @ticket 59354
1541+
* @covers ::wp_insert_post
1542+
*/
1543+
public function test_update_post_without_post_category_does_not_call_wp_set_post_categories() {
1544+
$post_id = self::factory()->post->create(
1545+
array(
1546+
'post_title' => 'Original',
1547+
'post_category' => array( 1 ),
1548+
)
1549+
);
1550+
1551+
$category_set_calls = 0;
1552+
$callback = static function ( $object_id, $terms, $tt_ids, $taxonomy ) use ( &$category_set_calls ) {
1553+
if ( 'category' === $taxonomy ) {
1554+
++$category_set_calls;
1555+
}
1556+
};
1557+
1558+
add_action( 'set_object_terms', $callback, 10, 4 );
1559+
1560+
wp_insert_post(
1561+
array(
1562+
'ID' => $post_id,
1563+
'post_title' => 'Updated Title Only',
1564+
)
1565+
);
1566+
1567+
remove_action( 'set_object_terms', $callback );
1568+
1569+
$this->assertSame( 0, $category_set_calls, 'wp_set_post_categories() should not run on update without post_category.' );
1570+
}
1571+
1572+
/**
1573+
* Updating a post without `post_category` must preserve its existing categories.
1574+
*
1575+
* @ticket 59354
1576+
* @covers ::wp_insert_post
1577+
*/
1578+
public function test_update_post_without_post_category_preserves_existing_categories() {
1579+
$category_id = self::factory()->category->create( array( 'name' => 'Stays Assigned' ) );
1580+
1581+
$post_id = self::factory()->post->create(
1582+
array(
1583+
'post_title' => 'Original',
1584+
'post_category' => array( $category_id ),
1585+
)
1586+
);
1587+
1588+
$categories_before = wp_get_post_categories( $post_id );
1589+
1590+
wp_insert_post(
1591+
array(
1592+
'ID' => $post_id,
1593+
'post_title' => 'Updated Title Only',
1594+
)
1595+
);
1596+
1597+
$categories_after = wp_get_post_categories( $post_id );
1598+
1599+
$this->assertSame( $categories_before, $categories_after, 'Existing categories should be preserved when post_category is not passed.' );
1600+
$this->assertContains( $category_id, $categories_after, 'The originally-assigned category should still be set.' );
1601+
}
15331602
}

0 commit comments

Comments
 (0)