Make WordPress Core

Changeset 60510


Ignore:
Timestamp:
07/25/2025 08:35:12 AM (14 months ago)
Author:
johnbillion
Message:

Posts, Post Types: Don't unnecessarily recount terms when a post transitions between statuses that don't require them to be recounted.

This accounts for transitions where:

  • Both the old and new statuses are not included in term counts.
  • Both the old and new statuses are included in term counts.

This results in term counts only being recalculated when a post transitions between a counted and an uncounted status.

If the terms of the post are changed then the term recounting is still handled by wp_update_term_count() inside wp_set_object_terms().

Props hbhalodia, johnbillion, peterwilsoncc, mukesh27.

Fixes #63562

Location:
trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/wp-includes/post.php

    r60365 r60510  
    81768176
    81778177        // Update counts for the post's terms.
    8178         foreach ( (array) get_object_taxonomies( $post->post_type ) as $taxonomy ) {
    8179                 $tt_ids = wp_get_object_terms( $post->ID, $taxonomy, array( 'fields' => 'tt_ids' ) );
    8180                 wp_update_term_count( $tt_ids, $taxonomy );
     8178        foreach ( (array) get_object_taxonomies( $post->post_type, 'objects' ) as $taxonomy ) {
     8179                /** This filter is documented in wp-includes/taxonomy.php */
     8180                $counted_statuses = apply_filters( 'update_post_term_count_statuses', array( 'publish' ), $taxonomy );
     8181
     8182                /*
     8183                 * Do not recalculate term count if both the old and new status are not included in term counts.
     8184                 * This accounts for a transition such as draft -> pending.
     8185                 */
     8186                if ( ! in_array( $old_status, $counted_statuses, true ) && ! in_array( $new_status, $counted_statuses, true ) ) {
     8187                        continue;
     8188                }
     8189
     8190                /*
     8191                 * Do not recalculate term count if both the old and new status are included in term counts.
     8192                 *
     8193                 * This accounts for transitioning between statuses which are both included in term counts. This can only occur
     8194                 * if the `update_post_term_count_statuses` filter is in use to count more than just the 'publish' status.
     8195                 */
     8196                if ( in_array( $old_status, $counted_statuses, true ) && in_array( $new_status, $counted_statuses, true ) ) {
     8197                        continue;
     8198                }
     8199
     8200                $tt_ids = wp_get_object_terms( $post->ID, $taxonomy->name, array( 'fields' => 'tt_ids' ) );
     8201                wp_update_term_count( $tt_ids, $taxonomy->name );
    81818202        }
    81828203}
  • trunk/tests/phpunit/tests/post/updateTermCountOnTransitionPostStatus.php

    r60365 r60510  
    140140         */
    141141        public function test_term_count_is_not_recalculated_when_status_does_not_change() {
     142                // Create a mock action for `edited_term_taxonomy` to prevent flaky test.
     143                $action = new MockAction();
     144                add_action( 'edited_term_taxonomy', array( $action, 'action' ) );
     145
    142146                $post_id = self::factory()->post->create(
    143147                        array(
     
    152156                        self::$taxonomy
    153157                );
    154                 $edited_term_taxonomy_count = did_action( 'edited_term_taxonomy' );
     158                $edited_term_taxonomy_count = $action->get_call_count();
    155159
    156160                // Change something about the post but not its status.
     
    162166                );
    163167
    164                 $this->assertSame( 0, did_action( 'edited_term_taxonomy' ) - $edited_term_taxonomy_count, 'Term taxonomy count should not be recalculated when post status does not change.' );
     168                $this->assertSame( 0, $action->get_call_count() - $edited_term_taxonomy_count, 'Term taxonomy count should not be recalculated when post status does not change.' );
    165169                $this->assertTermCount( 2, self::$term_id );
     170        }
     171
     172        /**
     173         * Test that the term count is not recalculated when both the old and new status are included in term counts.
     174         *
     175         * This accounts for a transition such as draft -> pending.
     176         *
     177         * @ticket 63562
     178         */
     179        public function test_term_count_is_not_recalculated_when_both_status_are_counted() {
     180                // Create a mock action for `edited_term_taxonomy` to prevent flaky test.
     181                $action = new MockAction();
     182                add_action( 'edited_term_taxonomy', array( $action, 'action' ) );
     183
     184                // Register a custom status that is included in term counts.
     185                register_post_status(
     186                        'counted',
     187                        array(
     188                                'label'  => 'Counted',
     189                                'public' => true,
     190                        )
     191                );
     192
     193                add_filter(
     194                        'update_post_term_count_statuses',
     195                        static function ( $status ) {
     196                                $status[] = 'counted';
     197                                return $status;
     198                        }
     199                );
     200
     201                // Change the post to another status that is included in term counts.
     202                wp_update_post(
     203                        array(
     204                                'ID'          => self::$post_id,
     205                                'post_status' => 'counted',
     206                        )
     207                );
     208
     209                $this->assertSame( 0, $action->get_call_count(), 'Term taxonomy count should not be recalculated both statuses are included in term counts.' );
     210                $this->assertTermCount( 1, self::$term_id, 'Term count should remain unchanged when transitioning between post statuses that are counted.' );
     211        }
     212
     213        /**
     214         * Test that the term count is not recalculated when neither the old nor new status are included in term counts.
     215         *
     216         * This accounts for a transition such as draft -> pending.
     217         *
     218         * @ticket 63562
     219         */
     220        public function test_term_count_is_not_recalculated_when_neither_status_is_counted() {
     221                // Create a mock action for `edited_term_taxonomy` to prevent flaky test.
     222                $action = new MockAction();
     223                add_action( 'edited_term_taxonomy', array( $action, 'action' ) );
     224
     225                // Change post status to draft.
     226                wp_update_post(
     227                        array(
     228                                'ID'          => self::$post_id,
     229                                'post_status' => 'draft',
     230                        )
     231                );
     232
     233                $edited_term_taxonomy_count = $action->get_call_count();
     234
     235                // Change the post to another status that is not included in term counts.
     236                wp_update_post(
     237                        array(
     238                                'ID'          => self::$post_id,
     239                                'post_status' => 'pending',
     240                        )
     241                );
     242
     243                $this->assertSame( 0, $action->get_call_count() - $edited_term_taxonomy_count, 'Term taxonomy count should not be recalculated when neither new nor old post status is included in term counts.' );
     244                $this->assertTermCount( 0, self::$term_id, 'Term count should remain unchanged when transitioning between post statuses that are not counted.' );
     245        }
     246
     247        /**
     248         * Test to ensure that the `update_post_term_count_statuses` filter is respected.
     249         *
     250         * @ticket 63562
     251         */
     252        public function test_update_post_term_count_statuses_filter_is_respected() {
     253                // Create a mock action for `edited_term_taxonomy` to prevent flaky test.
     254                $action = new MockAction();
     255                add_action( 'edited_term_taxonomy', array( $action, 'action' ) );
     256
     257                $custom_taxonomy = 'category_with_pending';
     258
     259                // Add a custom taxonomy that includes 'pending' in its term counts.
     260                register_taxonomy(
     261                        $custom_taxonomy,
     262                        self::$post_type
     263                );
     264                add_filter(
     265                        'update_post_term_count_statuses',
     266                        static function ( array $statuses, WP_Taxonomy $taxonomy ) use ( $custom_taxonomy ): array {
     267                                if ( $custom_taxonomy === $taxonomy->name ) {
     268                                        $statuses[] = 'pending';
     269                                }
     270
     271                                return $statuses;
     272                        },
     273                        10,
     274                        2
     275                );
     276
     277                // Change post status to draft and give it a term to count.
     278                wp_update_post(
     279                        array(
     280                                'ID'          => self::$post_id,
     281                                'post_status' => 'draft',
     282                        )
     283                );
     284                $custom_term_id = self::factory()->term->create(
     285                        array(
     286                                'taxonomy' => $custom_taxonomy,
     287                                'name'     => 'Hello',
     288                        )
     289                );
     290                wp_set_object_terms(
     291                        self::$post_id,
     292                        $custom_term_id,
     293                        $custom_taxonomy
     294                );
     295
     296                $edited_term_taxonomy_count = $action->get_call_count();
     297
     298                // Change the post to another status that is included in term counts for one of its two taxonomies.
     299                wp_update_post(
     300                        array(
     301                                'ID'          => self::$post_id,
     302                                'post_status' => 'pending',
     303                        )
     304                );
     305
     306                $this->assertSame( 1, $action->get_call_count() - $edited_term_taxonomy_count, 'Term taxonomy count should respect the statuses returned by the update_post_term_count_statuses filter.' );
     307                $this->assertTermCount( 0, self::$term_id, 'Term count for the default taxonomy should remain zero since "pending" is not included in its countable statuses.' );
     308                $this->assertTermCount( 1, $custom_term_id, 'Term count for the custom taxonomy should be updated to 1 because the "pending" status is included via the update_post_term_count_statuses filter.' );
    166309        }
    167310
Note: See TracChangeset for help on using the changeset viewer.