Make WordPress Core

Changeset 60711

Timestamp:
09/05/2025 01:28:20 PM (3 months ago)
Author:
SergeyBiryukov
Message:

Taxonomy: Add update_term_count action that fires when term counts are updated.

This allows plugins to run custom queries only when a term count is actually updated and not on every update of terms or posts.

Follow-up to [60365], [60510].

Props leonidasmilossis, peterwilsoncc, mukesh27, rollybueno, SergeyBiryukov.
Fixes #63904.

Location:
trunk
Files:
2 edited

Legend:

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

    r60697 r60711  
    41764176    $post_statuses = esc_sql( apply_filters( 'update_post_term_count_statuses', $post_statuses, $taxonomy ) );
    41774177
    4178     foreach ( (array) $terms as $term ) {
     4178    foreach ( (array) $terms as $tt_id ) {
    41794179        $count = 0;
    41804180
     
    41824182        if ( $check_attachments ) {
    41834183            // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.QuotedDynamicPlaceholderGeneration
    4184             $count += (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_relationships, $wpdb->posts p1 WHERE p1.ID = $wpdb->term_relationships.object_id AND ( post_status IN ('" . implode( "', '", $post_statuses ) . "') OR ( post_status = 'inherit' AND post_parent > 0 AND ( SELECT post_status FROM $wpdb->posts WHERE ID = p1.post_parent ) IN ('" . implode( "', '", $post_statuses ) . "') ) ) AND post_type = 'attachment' AND term_taxonomy_id = %d", $term ) );
     4184            $count += (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_relationships, $wpdb->posts p1 WHERE p1.ID = $wpdb->term_relationships.object_id AND ( post_status IN ('" . implode( "', '", $post_statuses ) . "') OR ( post_status = 'inherit' AND post_parent > 0 AND ( SELECT post_status FROM $wpdb->posts WHERE ID = p1.post_parent ) IN ('" . implode( "', '", $post_statuses ) . "') ) ) AND post_type = 'attachment' AND term_taxonomy_id = %d", $tt_id ) );
    41854185        }
    41864186
    41874187        if ( $object_types ) {
    41884188            // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.QuotedDynamicPlaceholderGeneration
    4189             $count += (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_relationships, $wpdb->posts WHERE $wpdb->posts.ID = $wpdb->term_relationships.object_id AND post_status IN ('" . implode( "', '", $post_statuses ) . "') AND post_type IN ('" . implode( "', '", $object_types ) . "') AND term_taxonomy_id = %d", $term ) );
    4190         }
     4189            $count += (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_relationships, $wpdb->posts WHERE $wpdb->posts.ID = $wpdb->term_relationships.object_id AND post_status IN ('" . implode( "', '", $post_statuses ) . "') AND post_type IN ('" . implode( "', '", $object_types ) . "') AND term_taxonomy_id = %d", $tt_id ) );
     4190        }
     4191
     4192        /**
     4193         * Fires when a term count is calculated, before it is updated in the database.
     4194         *
     4195         * @since 6.9.0
     4196         *
     4197         * @param int    $tt_id         Term taxonomy ID.
     4198         * @param string $taxonomy_name Taxonomy slug.
     4199         * @param int    $count         Term count.
     4200         */
     4201        do_action( 'update_term_count', $tt_id, $taxonomy->name, $count );
    41914202
    41924203        /** This action is documented in wp-includes/taxonomy.php */
    4193         do_action( 'edit_term_taxonomy', $term, $taxonomy->name );
    4194         $wpdb->update( $wpdb->term_taxonomy, compact( 'count' ), array( 'term_taxonomy_id' => $term ) );
     4204        do_action( 'edit_term_taxonomy', $tt_id, $taxonomy->name );
     4205        $wpdb->update( $wpdb->term_taxonomy, compact( 'count' ), array( 'term_taxonomy_id' => $tt_id ) );
    41954206
    41964207        /** This action is documented in wp-includes/taxonomy.php */
    4197         do_action( 'edited_term_taxonomy', $term, $taxonomy->name );
     4208        do_action( 'edited_term_taxonomy', $tt_id, $taxonomy->name );
    41984209    }
    41994210}
     
    42164227    foreach ( (array) $terms as $term ) {
    42174228        $count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_relationships WHERE term_taxonomy_id = %d", $term ) );
     4229
     4230        /** This action is documented in wp-includes/taxonomy.php */
     4231        do_action( 'update_term_count', $term, $taxonomy->name, $count );
    42184232
    42194233        /** This action is documented in wp-includes/taxonomy.php */
  • trunk/tests/phpunit/tests/post/updateTermCountOnTransitionPostStatus.php

    r60510 r60711  
    22
    33/**
    4  * Tests for the _update_term_count_on_transition_post_status function.
     4 * Tests for the `_update_term_count_on_transition_post_status()` function.
    55 *
    6  * See Tests_Term_WpSetObjectTerms for tests that cover changing terms on a post when saving it.
     6 * See `Tests_Term_WpSetObjectTerms` for tests that cover changing terms on a post when saving it.
    77 *
    88 * @group taxonomy
     
    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' ) );
     142        // Create a mock action for checking the `edited_term_taxonomy` hook call count.
     143        $edited_term_taxonomy_action = new MockAction();
     144        add_action( 'edited_term_taxonomy', array( $edited_term_taxonomy_action, 'action' ) );
     145
     146        // Create a mock action for checking the `update_term_count` hook call count.
     147        $update_term_count_action = new MockAction();
     148        add_action( 'update_term_count', array( $update_term_count_action, 'action' ) );
    145149
    146150        $post_id = self::factory()->post->create(
     
    156160            self::$taxonomy
    157161        );
    158         $edited_term_taxonomy_count = $action->get_call_count();
     162        $edited_term_taxonomy_count     = $edited_term_taxonomy_action->get_call_count();
     163        $update_term_count_action_count = $update_term_count_action->get_call_count();
    159164
    160165        // Change something about the post but not its status.
     
    166171        );
    167172
    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.' );
     173        $this->assertSame( 0, $edited_term_taxonomy_action->get_call_count() - $edited_term_taxonomy_count, 'Term taxonomy count should not be recalculated when post status does not change.' );
     174        $this->assertSame( 0, $update_term_count_action->get_call_count() - $update_term_count_action_count, 'The `update_term_count` action should not run when term taxonomy count is not recalculated.' );
    169175        $this->assertTermCount( 2, self::$term_id );
    170176    }
     
    178184     */
    179185    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' ) );
     186        // Create a mock action for checking the `edited_term_taxonomy` hook call count.
     187        $edited_term_taxonomy_action = new MockAction();
     188        add_action( 'edited_term_taxonomy', array( $edited_term_taxonomy_action, 'action' ) );
     189
     190        // Create a mock action for checking the `update_term_count` hook call count.
     191        $update_term_count_action = new MockAction();
     192        add_action( 'update_term_count', array( $update_term_count_action, 'action' ) );
    183193
    184194        // Register a custom status that is included in term counts.
     
    207217        );
    208218
    209         $this->assertSame( 0, $action->get_call_count(), 'Term taxonomy count should not be recalculated both statuses are included in term counts.' );
     219        $this->assertSame( 0, $edited_term_taxonomy_action->get_call_count(), 'Term taxonomy count should not be recalculated when both statuses are included in term counts.' );
     220        $this->assertSame( 0, $update_term_count_action->get_call_count(), 'The `update_term_count` action should not run when term taxonomy count is not recalculated.' );
    210221        $this->assertTermCount( 1, self::$term_id, 'Term count should remain unchanged when transitioning between post statuses that are counted.' );
    211222    }
     
    219230     */
    220231    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' ) );
     232        // Create a mock action for checking the `edited_term_taxonomy` hook call count.
     233        $edited_term_taxonomy_action = new MockAction();
     234        add_action( 'edited_term_taxonomy', array( $edited_term_taxonomy_action, 'action' ) );
     235
     236        // Create a mock action for checking the `update_term_count` hook call count.
     237        $update_term_count_action = new MockAction();
     238        add_action( 'update_term_count', array( $update_term_count_action, 'action' ) );
    224239
    225240        // Change post status to draft.
     
    231246        );
    232247
    233         $edited_term_taxonomy_count = $action->get_call_count();
     248        $edited_term_taxonomy_count     = $edited_term_taxonomy_action->get_call_count();
     249        $update_term_count_action_count = $update_term_count_action->get_call_count();
    234250
    235251        // Change the post to another status that is not included in term counts.
     
    241257        );
    242258
    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.' );
     259        $this->assertSame( 0, $edited_term_taxonomy_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.' );
     260        $this->assertSame( 0, $update_term_count_action->get_call_count() - $update_term_count_action_count, 'The `update_term_count` action should not run when term taxonomy count is not recalculated.' );
    244261        $this->assertTermCount( 0, self::$term_id, 'Term count should remain unchanged when transitioning between post statuses that are not counted.' );
    245262    }
     
    251268     */
    252269    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' ) );
     270        // Create a mock action for checking the `edited_term_taxonomy` hook call count.
     271        $edited_term_taxonomy_action = new MockAction();
     272        add_action( 'edited_term_taxonomy', array( $edited_term_taxonomy_action, 'action' ) );
     273
     274        // Create a mock action for checking the `update_term_count` hook call count.
     275        $update_term_count_action = new MockAction();
     276        add_action( 'update_term_count', array( $update_term_count_action, 'action' ) );
    256277
    257278        $custom_taxonomy = 'category_with_pending';
     
    294315        );
    295316
    296         $edited_term_taxonomy_count = $action->get_call_count();
     317        $edited_term_taxonomy_count     = $edited_term_taxonomy_action->get_call_count();
     318        $update_term_count_action_count = $update_term_count_action->get_call_count();
    297319
    298320        // Change the post to another status that is included in term counts for one of its two taxonomies.
     
    304326        );
    305327
    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.' );
     328        $this->assertSame( 1, $edited_term_taxonomy_action->get_call_count() - $edited_term_taxonomy_count, 'Term taxonomy count should respect the statuses returned by the update_post_term_count_statuses filter.' );
     329        $this->assertSame( 1, $update_term_count_action->get_call_count() - $update_term_count_action_count, 'The `update_term_count` action should run when term taxonomy count is recalculated.' );
    307330        $this->assertTermCount( 0, self::$term_id, 'Term count for the default taxonomy should remain zero since "pending" is not included in its countable statuses.' );
    308331        $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.' );
Note: See TracChangeset for help on using the changeset viewer.