WordPress.org

Make WordPress Core

Ticket #40351: 40351.3.diff

File 40351.3.diff, 46.6 KB (added by peterwilsoncc, 13 months ago)

Wrapping up the docs.

  • src/wp-includes/class-wp-taxonomy.php

    diff --git a/src/wp-includes/class-wp-taxonomy.php b/src/wp-includes/class-wp-taxonomy.php
    index c34a03c38e..21f3d4aec9 100644
    a b final class WP_Taxonomy { 
    180180         */
    181181        public $update_count_callback;
    182182
     183        /**
     184         * Function that will be called when the count is modified by an amount.
     185         *
     186         * @since 5.6.0
     187         * @var callable
     188         */
     189        public $update_count_by_callback;
     190
    183191        /**
    184192         * Whether this taxonomy should appear in the REST API.
    185193         *
    final class WP_Taxonomy { 
    277285                $args = apply_filters( 'register_taxonomy_args', $args, $this->name, (array) $object_type );
    278286
    279287                $defaults = array(
    280                         'labels'                => array(),
    281                         'description'           => '',
    282                         'public'                => true,
    283                         'publicly_queryable'    => null,
    284                         'hierarchical'          => false,
    285                         'show_ui'               => null,
    286                         'show_in_menu'          => null,
    287                         'show_in_nav_menus'     => null,
    288                         'show_tagcloud'         => null,
    289                         'show_in_quick_edit'    => null,
    290                         'show_admin_column'     => false,
    291                         'meta_box_cb'           => null,
    292                         'meta_box_sanitize_cb'  => null,
    293                         'capabilities'          => array(),
    294                         'rewrite'               => true,
    295                         'query_var'             => $this->name,
    296                         'update_count_callback' => '',
    297                         'show_in_rest'          => false,
    298                         'rest_base'             => false,
    299                         'rest_controller_class' => false,
    300                         'default_term'          => null,
    301                         '_builtin'              => false,
     288                        'labels'                   => array(),
     289                        'description'              => '',
     290                        'public'                   => true,
     291                        'publicly_queryable'       => null,
     292                        'hierarchical'             => false,
     293                        'show_ui'                  => null,
     294                        'show_in_menu'             => null,
     295                        'show_in_nav_menus'        => null,
     296                        'show_tagcloud'            => null,
     297                        'show_in_quick_edit'       => null,
     298                        'show_admin_column'        => false,
     299                        'meta_box_cb'              => null,
     300                        'meta_box_sanitize_cb'     => null,
     301                        'capabilities'             => array(),
     302                        'rewrite'                  => true,
     303                        'query_var'                => $this->name,
     304                        'update_count_callback'    => '',
     305                        'update_count_by_callback' => '',
     306                        'show_in_rest'             => false,
     307                        'rest_base'                => false,
     308                        'rest_controller_class'    => false,
     309                        'default_term'             => null,
     310                        '_builtin'                 => false,
    302311                );
    303312
    304313                $args = array_merge( $defaults, $args );
    final class WP_Taxonomy { 
    411420                        );
    412421                }
    413422
     423                // If generic update callback is defined but increment/decrement callback is not.
     424                if (
     425                        ! empty( $args['update_count_callback'] ) &&
     426                        is_callable( $args['update_count_callback'] ) &&
     427                        empty( $args['update_count_by_callback'] )
     428                ) {
     429                        $args['update_count_by_callback'] = function( $tt_ids, $taxonomy, $modify_by ) {
     430                                return call_user_func( $args['update_count_callback'], $tt_ids, $taxonomy );
     431                        };
     432                }
     433
    414434                foreach ( $args as $property_name => $property_value ) {
    415435                        $this->$property_name = $property_value;
    416436                }
  • src/wp-includes/post.php

    diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php
    index 71161f4cab..ee1c9caea9 100644
    a b function wp_insert_post( $postarr, $wp_error = false ) { 
    40714071                clean_post_cache( $post_ID );
    40724072        }
    40734073
     4074        // Allow term counts to be handled by transitioning post type.
     4075        _wp_prevent_term_counting( true );
    40744076        if ( is_object_in_taxonomy( $post_type, 'category' ) ) {
    40754077                wp_set_post_categories( $post_ID, $post_category );
    40764078        }
    function wp_insert_post( $postarr, $wp_error = false ) { 
    41274129                        }
    41284130                }
    41294131        }
     4132        // Restore term counting.
     4133        _wp_prevent_term_counting( false );
    41304134
    41314135        if ( ! empty( $postarr['meta_input'] ) ) {
    41324136                foreach ( $postarr['meta_input'] as $field => $value ) {
    function wp_publish_post( $post ) { 
    44364440                if ( ! $default_term_id ) {
    44374441                        continue;
    44384442                }
     4443                _wp_prevent_term_counting( true );
    44394444                wp_set_post_terms( $post->ID, array( $default_term_id ), $taxonomy );
     4445                _wp_prevent_term_counting( false );
    44404446        }
    44414447
    44424448        $wpdb->update( $wpdb->posts, array( 'post_status' => 'publish' ), array( 'ID' => $post->ID ) );
    function wp_queue_posts_for_term_meta_lazyload( $posts ) { 
    73127318 * @param WP_Post $post       Post object.
    73137319 */
    73147320function _update_term_count_on_transition_post_status( $new_status, $old_status, $post ) {
    7315         // Update counts for the post's terms.
     7321        if ( 'inherit' === $new_status ) {
     7322                $new_status = get_post_status( $post->post_parent );
     7323        }
     7324
     7325        if ( 'inherit' === $old_status ) {
     7326                $old_status = get_post_status( $post->post_parent );
     7327        }
     7328
     7329        $count_new = 'publish' === $new_status;
     7330        $count_old = 'publish' === $old_status;
     7331
     7332        if ( $count_new === $count_old ) {
     7333                // Nothing to do.
     7334                return;
     7335        }
     7336
     7337        /*
     7338         * Update counts for the post's terms.
     7339         *
     7340         * Term counting is deferred while incrementing/decrementing the counts to
     7341         * reduce the number of database queries required. Once the counts are
     7342         * complete the updates are performed if term counting wasn't previously
     7343         * deferred.
     7344         */
     7345        $previous_deferred_setting = wp_defer_term_counting();
     7346        wp_defer_term_counting( true );
    73167347        foreach ( (array) get_object_taxonomies( $post->post_type ) as $taxonomy ) {
    73177348                $tt_ids = wp_get_object_terms( $post->ID, $taxonomy, array( 'fields' => 'tt_ids' ) );
    7318                 wp_update_term_count( $tt_ids, $taxonomy );
     7349
     7350                if ( empty( $tt_ids ) ) {
     7351                        // No terms for this taxonomy on object.
     7352                        continue;
     7353                }
     7354
     7355                $object_types = (array) get_taxonomy( $taxonomy )->object_type;
     7356
     7357                foreach ( $object_types as &$object_type ) {
     7358                        list( $object_type ) = explode( ':', $object_type );
     7359                }
     7360
     7361                $object_types = array_unique( $object_types );
     7362
     7363                if ( ! in_array( $post->post_type, $object_types, true ) ) {
     7364                        $modify_by = 0;
     7365                } elseif ( $count_new && ! $count_old ) {
     7366                        $modify_by = 1;
     7367                } elseif ( $count_old && ! $count_new ) {
     7368                        $modify_by = -1;
     7369                }
     7370
     7371                if ( 'attachment' === $post->post_type ) {
     7372                        wp_modify_term_count_by( $tt_ids, $taxonomy, $modify_by );
     7373                        continue;
     7374                }
     7375
     7376                $check_attachments = array_search( 'attachment', $object_types, true );
     7377                if ( false !== $check_attachments ) {
     7378                        unset( $object_types[ $check_attachments ] );
     7379                        $check_attachments = true;
     7380                }
     7381
     7382                wp_modify_term_count_by( $tt_ids, $taxonomy, $modify_by );
     7383                if ( ! $check_attachments ) {
     7384                        continue;
     7385                }
     7386
     7387                /*
     7388                 * For non-attachments, check if there are any attachment children
     7389                 * with 'inherited' post status -- if so those will need to be counted.
     7390                 */
     7391                $attachments = get_children(
     7392                        array(
     7393                                'post_parent'            => $post->ID,
     7394                                'post_status'            => 'inherit',
     7395                                'post_type'              => 'attachment',
     7396                                'update_post_meta_cache' => false,
     7397                                'update_post_term_cache' => true,
     7398                        )
     7399                );
     7400
     7401                foreach ( $attachments as $attachment ) {
     7402                        _update_term_count_on_transition_post_status( $new_status, $old_status, $attachment );
     7403                }
    73197404        }
     7405        wp_defer_term_counting( $previous_deferred_setting );
    73207406}
    73217407
    73227408/**
  • src/wp-includes/taxonomy.php

    diff --git a/src/wp-includes/taxonomy.php b/src/wp-includes/taxonomy.php
    index 08ac3c9a0c..e891af68d8 100644
    a b function is_taxonomy_hierarchical( $taxonomy ) { 
    336336 * @since 5.1.0 Introduced `meta_box_sanitize_cb` argument.
    337337 * @since 5.4.0 Added the registered taxonomy object as a return value.
    338338 * @since 5.5.0 Introduced `default_term` argument.
     339 * @since 5.6.0 Introduced `update_count_by_callback` argument.
    339340 *
    340341 * @global array $wp_taxonomies Registered taxonomies.
    341342 *
    function is_taxonomy_hierarchical( $taxonomy ) { 
    344345 * @param array|string $args        {
    345346 *     Optional. Array or query string of arguments for registering a taxonomy.
    346347 *
    347  *     @type array         $labels                An array of labels for this taxonomy. By default, Tag labels are
    348  *                                                used for non-hierarchical taxonomies, and Category labels are used
    349  *                                                for hierarchical taxonomies. See accepted values in
    350  *                                                get_taxonomy_labels(). Default empty array.
    351  *     @type string        $description           A short descriptive summary of what the taxonomy is for. Default empty.
    352  *     @type bool          $public                Whether a taxonomy is intended for use publicly either via
    353  *                                                the admin interface or by front-end users. The default settings
    354  *                                                of `$publicly_queryable`, `$show_ui`, and `$show_in_nav_menus`
    355  *                                                are inherited from `$public`.
    356  *     @type bool          $publicly_queryable    Whether the taxonomy is publicly queryable.
    357  *                                                If not set, the default is inherited from `$public`
    358  *     @type bool          $hierarchical          Whether the taxonomy is hierarchical. Default false.
    359  *     @type bool          $show_ui               Whether to generate and allow a UI for managing terms in this taxonomy in
    360  *                                                the admin. If not set, the default is inherited from `$public`
    361  *                                                (default true).
    362  *     @type bool          $show_in_menu          Whether to show the taxonomy in the admin menu. If true, the taxonomy is
    363  *                                                shown as a submenu of the object type menu. If false, no menu is shown.
    364  *                                                `$show_ui` must be true. If not set, default is inherited from `$show_ui`
    365  *                                                (default true).
    366  *     @type bool          $show_in_nav_menus     Makes this taxonomy available for selection in navigation menus. If not
    367  *                                                set, the default is inherited from `$public` (default true).
    368  *     @type bool          $show_in_rest          Whether to include the taxonomy in the REST API. Set this to true
    369  *                                                for the taxonomy to be available in the block editor.
    370  *     @type string        $rest_base             To change the base url of REST API route. Default is $taxonomy.
    371  *     @type string        $rest_controller_class REST API Controller class name. Default is 'WP_REST_Terms_Controller'.
    372  *     @type bool          $show_tagcloud         Whether to list the taxonomy in the Tag Cloud Widget controls. If not set,
    373  *                                                the default is inherited from `$show_ui` (default true).
    374  *     @type bool          $show_in_quick_edit    Whether to show the taxonomy in the quick/bulk edit panel. It not set,
    375  *                                                the default is inherited from `$show_ui` (default true).
    376  *     @type bool          $show_admin_column     Whether to display a column for the taxonomy on its post type listing
    377  *                                                screens. Default false.
    378  *     @type bool|callable $meta_box_cb           Provide a callback function for the meta box display. If not set,
    379  *                                                post_categories_meta_box() is used for hierarchical taxonomies, and
    380  *                                                post_tags_meta_box() is used for non-hierarchical. If false, no meta
    381  *                                                box is shown.
    382  *     @type callable      $meta_box_sanitize_cb  Callback function for sanitizing taxonomy data saved from a meta
    383  *                                                box. If no callback is defined, an appropriate one is determined
    384  *                                                based on the value of `$meta_box_cb`.
     348 *     @type array         $labels                   An array of labels for this taxonomy. By default, Tag labels are
     349 *                                                   used for non-hierarchical taxonomies, and Category labels are used
     350 *                                                   for hierarchical taxonomies. See accepted values in
     351 *                                                   get_taxonomy_labels(). Default empty array.
     352 *     @type string        $description              A short descriptive summary of what the taxonomy is for. Default empty.
     353 *     @type bool          $public                   Whether a taxonomy is intended for use publicly either via
     354 *                                                   the admin interface or by front-end users. The default settings
     355 *                                                   of `$publicly_queryable`, `$show_ui`, and `$show_in_nav_menus`
     356 *                                                   are inherited from `$public`.
     357 *     @type bool          $publicly_queryable       Whether the taxonomy is publicly queryable.
     358 *                                                   If not set, the default is inherited from `$public`
     359 *     @type bool          $hierarchical             Whether the taxonomy is hierarchical. Default false.
     360 *     @type bool          $show_ui                  Whether to generate and allow a UI for managing terms in this taxonomy in
     361 *                                                   the admin. If not set, the default is inherited from `$public`
     362 *                                                   (default true).
     363 *     @type bool          $show_in_menu             Whether to show the taxonomy in the admin menu. If true, the taxonomy is
     364 *                                                   shown as a submenu of the object type menu. If false, no menu is shown.
     365 *                                                   `$show_ui` must be true. If not set, default is inherited from `$show_ui`
     366 *                                                   (default true).
     367 *     @type bool          $show_in_nav_menus        Makes this taxonomy available for selection in navigation menus. If not
     368 *                                                   set, the default is inherited from `$public` (default true).
     369 *     @type bool          $show_in_rest             Whether to include the taxonomy in the REST API. Set this to true
     370 *                                                   for the taxonomy to be available in the block editor.
     371 *     @type string        $rest_base                To change the base url of REST API route. Default is $taxonomy.
     372 *     @type string        $rest_controller_class    REST API Controller class name. Default is 'WP_REST_Terms_Controller'.
     373 *     @type bool          $show_tagcloud            Whether to list the taxonomy in the Tag Cloud Widget controls. If not set,
     374 *                                                   the default is inherited from `$show_ui` (default true).
     375 *     @type bool          $show_in_quick_edit       Whether to show the taxonomy in the quick/bulk edit panel. It not set,
     376 *                                                   the default is inherited from `$show_ui` (default true).
     377 *     @type bool          $show_admin_column        Whether to display a column for the taxonomy on its post type listing
     378 *                                                   screens. Default false.
     379 *     @type bool|callable $meta_box_cb              Provide a callback function for the meta box display. If not set,
     380 *                                                   post_categories_meta_box() is used for hierarchical taxonomies, and
     381 *                                                   post_tags_meta_box() is used for non-hierarchical. If false, no meta
     382 *                                                   box is shown.
     383 *     @type callable      $meta_box_sanitize_cb     Callback function for sanitizing taxonomy data saved from a meta
     384 *                                                   box. If no callback is defined, an appropriate one is determined
     385 *                                                   based on the value of `$meta_box_cb`.
    385386 *     @type array         $capabilities {
    386387 *         Array of capabilities for this taxonomy.
    387388 *
    function is_taxonomy_hierarchical( $taxonomy ) { 
    399400 *         @type bool   $hierarchical Either hierarchical rewrite tag or not. Default false.
    400401 *         @type int    $ep_mask      Assign an endpoint mask. Default `EP_NONE`.
    401402 *     }
    402  *     @type string|bool   $query_var             Sets the query var key for this taxonomy. Default `$taxonomy` key. If
    403  *                                                false, a taxonomy cannot be loaded at `?{query_var}={term_slug}`. If a
    404  *                                                string, the query `?{query_var}={term_slug}` will be valid.
    405  *     @type callable      $update_count_callback Works much like a hook, in that it will be called when the count is
    406  *                                                updated. Default _update_post_term_count() for taxonomies attached
    407  *                                                to post types, which confirms that the objects are published before
    408  *                                                counting them. Default _update_generic_term_count() for taxonomies
    409  *                                                attached to other object types, such as users.
     403 *     @type string|bool   $query_var                Sets the query var key for this taxonomy. Default `$taxonomy` key. If
     404 *                                                   false, a taxonomy cannot be loaded at `?{query_var}={term_slug}`. If a
     405 *                                                   string, the query `?{query_var}={term_slug}` will be valid.
     406 *     @type callable      $update_count_callback    Works much like a hook, in that it will be called when the count is
     407 *                                                   updated. Default _update_post_term_count() for taxonomies attached
     408 *                                                   to post types, which confirms that the objects are published before
     409 *                                                   counting them. Default _update_generic_term_count() for taxonomies
     410 *                                                   attached to other object types, such as users.
     411 *     @type callable      $update_count_by_callback Works much like a hook, in that it will be called when the count is
     412 *                                                   incremented or decremented. Defaults to the value of `$update_count_callback` if
     413 *                                                   a custom callack is defined, otherwise uses wp_modify_term_count_by().
    410414 *     @type string|array  $default_term {
    411415 *         Default term to be used for the taxonomy.
    412416 *
    function is_taxonomy_hierarchical( $taxonomy ) { 
    414418 *         @type string $slug         Slug for default term. Default empty.
    415419 *         @type string $description  Description for default term. Default empty.
    416420 *     }
    417  *     @type bool          $_builtin              This taxonomy is a "built-in" taxonomy. INTERNAL USE ONLY!
    418  *                                                Default false.
     421 *     @type bool          $_builtin                 This taxonomy is a "built-in" taxonomy. INTERNAL USE ONLY!
     422 *                                                   Default false.
    419423 * }
    420424 * @return WP_Taxonomy|WP_Error The registered taxonomy object on success, WP_Error object on failure.
    421425 */
    function wp_set_object_terms( $object_id, $terms, $taxonomy, $append = false ) { 
    25612565                return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
    25622566        }
    25632567
     2568        $taxonomy_object = get_taxonomy( $taxonomy );
     2569
     2570        $object_types = (array) $taxonomy_object->object_type;
     2571        foreach ( $object_types as &$object_type ) {
     2572                if ( 0 === strpos( $object_type, 'attachment:' ) ) {
     2573                        list( $object_type ) = explode( ':', $object_type );
     2574                }
     2575        }
     2576
     2577        if ( array_filter( $object_types, 'post_type_exists' ) !== $object_types ) {
     2578                // This taxonomy applies to non-posts, count changes now.
     2579                $do_recount = ! _wp_prevent_term_counting();
     2580        } elseif ( 'publish' === get_post_status( $object_id ) ) {
     2581                // Published post, count changes now.
     2582                $do_recount = ! _wp_prevent_term_counting();
     2583        } else {
     2584                $do_recount = false;
     2585        }
     2586
    25642587        if ( ! is_array( $terms ) ) {
    25652588                $terms = array( $terms );
    25662589        }
    function wp_set_object_terms( $object_id, $terms, $taxonomy, $append = false ) { 
    26462669                $new_tt_ids[] = $tt_id;
    26472670        }
    26482671
    2649         if ( $new_tt_ids ) {
    2650                 wp_update_term_count( $new_tt_ids, $taxonomy );
     2672        if ( $new_tt_ids && $do_recount ) {
     2673                wp_increment_term_count( $new_tt_ids, $taxonomy );
    26512674        }
    26522675
    26532676        if ( ! $append ) {
    function wp_set_object_terms( $object_id, $terms, $taxonomy, $append = false ) { 
    26652688                }
    26662689        }
    26672690
    2668         $t = get_taxonomy( $taxonomy );
    2669 
    2670         if ( ! $append && isset( $t->sort ) && $t->sort ) {
     2691        if ( ! $append && isset( $taxonomy_object->sort ) && $taxonomy_object->sort ) {
    26712692                $values     = array();
    26722693                $term_order = 0;
    26732694
    function wp_remove_object_terms( $object_id, $terms, $taxonomy ) { 
    27482769                return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
    27492770        }
    27502771
     2772        $taxonomy_object = get_taxonomy( $taxonomy );
     2773
     2774        $object_types = (array) $taxonomy_object->object_type;
     2775        foreach ( $object_types as &$object_type ) {
     2776                if ( 0 === strpos( $object_type, 'attachment:' ) ) {
     2777                        list( $object_type ) = explode( ':', $object_type );
     2778                }
     2779        }
     2780
     2781        if ( array_filter( $object_types, 'post_type_exists' ) !== $object_types ) {
     2782                // This taxonomy applies to non-posts, count changes now.
     2783                $do_recount = ! _wp_prevent_term_counting();
     2784        } elseif (
     2785                'publish' === get_post_status( $object_id ) ||
     2786                (
     2787                        'inherit' === get_post_status( $object_id ) &&
     2788                        'publish' === get_post_status( wp_get_post_parent_id( $object_id ) )
     2789                )
     2790        ) {
     2791                // Published post, count changes now.
     2792                $do_recount = ! _wp_prevent_term_counting();
     2793        } else {
     2794                $do_recount = false;
     2795        }
     2796
    27512797        if ( ! is_array( $terms ) ) {
    27522798                $terms = array( $terms );
    27532799        }
    function wp_remove_object_terms( $object_id, $terms, $taxonomy ) { 
    28062852                 */
    28072853                do_action( 'deleted_term_relationships', $object_id, $tt_ids, $taxonomy );
    28082854
    2809                 wp_update_term_count( $tt_ids, $taxonomy );
     2855                if ( $do_recount ) {
     2856                        wp_decrement_term_count( $tt_ids, $taxonomy );
     2857                }
    28102858
    28112859                return (bool) $deleted;
    28122860        }
    function wp_defer_term_counting( $defer = null ) { 
    32263274                $_defer = $defer;
    32273275                // Flush any deferred counts.
    32283276                if ( ! $defer ) {
     3277                        wp_modify_term_count_by( null, null, null, true );
    32293278                        wp_update_term_count( null, null, true );
    32303279                }
    32313280        }
    function wp_defer_term_counting( $defer = null ) { 
    32333282        return $_defer;
    32343283}
    32353284
     3285/**
     3286 * Prevents add/removing a term from modifying a term count.
     3287 *
     3288 * This is used by functions calling wp_transition_post_status() to indicate the
     3289 * term count will be handled during the post's transition.
     3290 *
     3291 * @private
     3292 * @since 5.6.0
     3293 *
     3294 * @param bool $new_setting The new setting for preventing term counts.
     3295 * @return bool Whether term count prevention is enabled or disabled.
     3296 */
     3297function _wp_prevent_term_counting( $new_setting = null ) {
     3298        static $prevent = false;
     3299
     3300        if ( is_bool( $new_setting ) ) {
     3301                $prevent = $new_setting;
     3302        }
     3303
     3304        return $prevent;
     3305}
     3306
     3307/**
     3308 * Increments the amount of terms in taxonomy.
     3309 *
     3310 * If there is a taxonomy callback applied, then it will be called for updating
     3311 * the count.
     3312 *
     3313 * The default action is to increment the count by one and update the database.
     3314 *
     3315 * @since 5.6.0
     3316 *
     3317 * @param int|array $tt_ids       The term_taxonomy_id of the terms.
     3318 * @param string    $taxonomy     The context of the term.
     3319 * @param int       $increment_by By how many the term count is to be incremented. Default 1.
     3320 * @param bool      $do_deferred  Whether to flush the deferred term counts too. Default false.
     3321 * @return bool If no terms will return false, and if successful will return true.
     3322 */
     3323function wp_increment_term_count( $tt_ids, $taxonomy, $increment_by = 1, $do_deferred = false ) {
     3324        return wp_modify_term_count_by( $tt_ids, $taxonomy, $increment_by, $do_deferred );
     3325}
     3326
     3327/**
     3328 * Decrements the amount of terms in taxonomy.
     3329 *
     3330 * If there is a taxonomy callback applied, then it will be called for updating
     3331 * the count.
     3332 *
     3333 * The default action is to decrement the count by one and update the database.
     3334 *
     3335 * @since 5.6.0
     3336 *
     3337 * @param int|array $tt_ids       The term_taxonomy_id of the terms.
     3338 * @param string    $taxonomy     The context of the term.
     3339 * @param int       $decrement_by By how many the term count is to be decremented. Default 1.
     3340 * @param bool      $do_deferred  Whether to flush the deferred term counts too. Default false.
     3341 * @return bool If no terms will return false, and if successful will return true.
     3342 */
     3343function wp_decrement_term_count( $tt_ids, $taxonomy, $decrement_by = 1, $do_deferred = false ) {
     3344        return wp_modify_term_count_by( $tt_ids, $taxonomy, $decrement_by * -1, $do_deferred );
     3345}
     3346
     3347/**
     3348 * Modifies the amount of terms in taxonomy.
     3349 *
     3350 * If there is a taxonomy callback applied, then it will be called for updating
     3351 * the count.
     3352 *
     3353 * The default action is to decrement the count by one and update the database.
     3354 *
     3355 * @since 5.6.0
     3356 *
     3357 * @param int|array $tt_ids      The term_taxonomy_id of the terms.
     3358 * @param string    $taxonomy    The context of the term.
     3359 * @param int       $modify_by   By how many the term count is to be modified.
     3360 * @param bool      $do_deferred Whether to flush the deferred term counts too. Default false.
     3361 * @return bool If no terms will return false, and if successful will return true.
     3362 */
     3363function wp_modify_term_count_by( $tt_ids, $taxonomy, $modify_by, $do_deferred = false ) {
     3364        static $_deferred = array();
     3365
     3366        if ( $do_deferred ) {
     3367                foreach ( (array) $_deferred as $taxonomy_name => $modifications ) {
     3368                        $tax_by_count = array_reduce(
     3369                                array_keys( $modifications ),
     3370                                function( $by_count, $tt_id ) use ( $modifications ) {
     3371                                        if ( ! isset( $by_count[ $modifications[ $tt_id ] ] ) ) {
     3372                                                $by_count[ $modifications[ $tt_id ] ] = array();
     3373                                        }
     3374                                        $by_count[ $modifications[ $tt_id ] ][] = $tt_id;
     3375                                        return $by_count;
     3376                                },
     3377                                array()
     3378                        );
     3379
     3380                        foreach ( $tax_by_count as $_modify_by => $_tt_ids ) {
     3381                                wp_modify_term_count_by_now( $_tt_ids, $taxonomy_name, $_modify_by );
     3382                        }
     3383                        unset( $_deferred[ $taxonomy_name ] );
     3384                }
     3385        }
     3386
     3387        if ( empty( $tt_ids ) ) {
     3388                return false;
     3389        }
     3390
     3391        if ( ! is_array( $tt_ids ) ) {
     3392                $tt_ids = array( $tt_ids );
     3393        }
     3394
     3395        if ( wp_defer_term_counting() ) {
     3396                foreach ( $tt_ids as $tt_id ) {
     3397                        if ( ! isset( $_deferred[ $taxonomy ][ $tt_id ] ) ) {
     3398                                $_deferred[ $taxonomy ][ $tt_id ] = 0;
     3399                        }
     3400                        $_deferred[ $taxonomy ][ $tt_id ] += $modify_by;
     3401                }
     3402                return true;
     3403        }
     3404
     3405        return wp_modify_term_count_by_now( $tt_ids, $taxonomy, $modify_by );
     3406}
     3407
     3408/**
     3409 * Modifies the amount of terms in taxonomy immediately
     3410 *
     3411 * If there is a taxonomy callback applied, then it will be called for updating
     3412 * the count.
     3413 *
     3414 * The default action is to decrement the count by one and update the database.
     3415 *
     3416 * @since 5.6.0
     3417 *
     3418 * @param int|array $tt_ids      The term_taxonomy_id of the terms.
     3419 * @param string    $taxonomy    The context of the term.
     3420 * @param int       $modify_by   By how many the term count is to be modified.
     3421 * @return bool If no terms will return false, and if successful will return true.
     3422 */
     3423function wp_modify_term_count_by_now( $tt_ids, $taxonomy, $modify_by ) {
     3424        global $wpdb;
     3425
     3426        if ( 0 === $modify_by ) {
     3427                return false;
     3428        }
     3429
     3430        $tt_ids = array_filter( array_map( 'intval', (array) $tt_ids ) );
     3431
     3432        if ( empty( $tt_ids ) ) {
     3433                return false;
     3434        }
     3435
     3436        $taxonomy = get_taxonomy( $taxonomy );
     3437        if ( ! empty( $taxonomy->update_count_by_callback ) ) {
     3438                call_user_func( $taxonomy->update_count_by_callback, $tt_ids, $taxonomy, $modify_by );
     3439                clean_term_cache( $tt_ids, '', false );
     3440                return true;
     3441        }
     3442
     3443        $tt_ids_string = '(' . implode( ',', $tt_ids ) . ')';
     3444
     3445        foreach ( $tt_ids as $tt_id ) {
     3446                /** This action is documented in wp-includes/taxonomy.php */
     3447                do_action( 'edit_term_taxonomy', $tt_id, $taxonomy );
     3448        }
     3449
     3450        $result = $wpdb->query(
     3451                $wpdb->prepare(
     3452                        // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
     3453                        "UPDATE {$wpdb->term_taxonomy} AS tt SET tt.count = GREATEST( 0, tt.count + %d ) WHERE tt.term_taxonomy_id IN $tt_ids_string",
     3454                        $modify_by
     3455                )
     3456        );
     3457
     3458        if ( ! $result ) {
     3459                return false;
     3460        }
     3461
     3462        foreach ( $tt_ids as $tt_id ) {
     3463                /** This action is documented in wp-includes/taxonomy.php */
     3464                do_action( 'edited_term_taxonomy', $tt_id, $taxonomy );
     3465        }
     3466
     3467        clean_term_cache( $tt_ids, '', false );
     3468
     3469        return true;
     3470}
     3471
    32363472/**
    32373473 * Updates the amount of terms in taxonomy.
    32383474 *
  • tests/phpunit/tests/term/termCounts.php

    diff --git a/tests/phpunit/tests/term/termCounts.php b/tests/phpunit/tests/term/termCounts.php
    index 284d5f90ea..6e197c5d07 100644
    a b class Tests_Term_termCount extends WP_UnitTestCase { 
    1919         */
    2020        public static $post_ids;
    2121
     22        /**
     23         * Array of tag IDs.
     24         *
     25         * @var int[]
     26         */
     27        public static $tag_ids;
     28
     29        /**
     30         * Term ID for testing user counts.
     31         *
     32         * @var int
     33         */
     34        public static $user_term;
     35
     36        /**
     37         * User ID for testing user counts.
     38         *
     39         * @var int
     40         */
     41        public static $user_id;
     42
    2243        /**
    2344         * Create shared fixtures.
    2445         *
    class Tests_Term_termCount extends WP_UnitTestCase { 
    3051                        self::$post_ids[ $status ] = $factory->post->create( array( 'post_status' => $status ) );
    3152                }
    3253
    33                 register_taxonomy( 'wp_test_tax_counts', array( 'post', 'attachment' ) );
     54                // Extra published post.
     55                self::$post_ids['publish_two'] = $factory->post->create( array( 'post_status' => 'publish' ) );
     56
     57                self::$user_id = $factory->user->create( array( 'role' => 'author' ) );
     58
     59                self::register_taxonomies();
    3460                self::$attachment_term = $factory->term->create( array( 'taxonomy' => 'wp_test_tax_counts' ) );
     61                self::$user_term       = $factory->term->create( array( 'taxonomy' => 'wp_test_user_tax_counts' ) );
     62                self::$tag_ids         = $factory->term->create_many( 5 );
    3563        }
    3664
    3765        public function setUp() {
    3866                parent::setUp();
     67                self::register_taxonomies();
     68        }
    3969
     70        /**
     71         * Register taxonomies used by tests.
     72         *
     73         * This is called both before class and before each test as the global is
     74         * reset in each test's tearDown.
     75         */
     76        public static function register_taxonomies() {
    4077                register_taxonomy( 'wp_test_tax_counts', array( 'post', 'attachment' ) );
     78                register_taxonomy( 'wp_test_user_tax_counts', 'user' );
     79        }
     80
     81        /**
     82         * Term counts are not double incremented when post created.
     83         *
     84         * @covers wp_modify_term_count_by
     85         * @dataProvider data_term_count_changes_for_post_statuses
     86         * @ticket 40351
     87         *
     88         * @param string $post_status New post status.
     89         * @param int    $change      Expected change.
     90         */
     91        public function test_term_count_changes_for_post_statuses( $post_status, $change ) {
     92                $term_count = get_term( get_option( 'default_category' ) )->count;
     93                // Do not use shared fixture for this test as it relies on a new post.
     94                $post_id = $this->factory()->post->create( array( 'post_status' => $post_status ) );
     95
     96                $expected = $term_count + $change;
     97                $this->assertSame( $expected, get_term( get_option( 'default_category' ) )->count );
     98        }
     99
     100        /**
     101         * Data provider for test_term_count_changes_for_post_statuses.
     102         *
     103         * @return array[] {
     104         *     @type string $post_status New post status.
     105         *     @type int    $change      Expected change.
     106         * }
     107         */
     108        function data_term_count_changes_for_post_statuses() {
     109                return array(
     110                        // 0. Published post
     111                        array( 'publish', 1 ),
     112                        // 1. Auto draft
     113                        array( 'auto-draft', 0 ),
     114                        // 2. Draft
     115                        array( 'draft', 0 ),
     116                        // 3. Private post
     117                        array( 'private', 0 ),
     118                );
    41119        }
    42120
    43121        /**
    44122         * Term counts increments correctly when post status becomes published.
    45123         *
    46124         * @covers wp_publish_post
    47          * @covers wp_count_terms
     125         * @covers wp_modify_term_count_by
    48126         * @dataProvider data_term_counts_incremented_on_publish
    49127         * @ticket 40351
    50128         * @ticket 51292
    class Tests_Term_termCount extends WP_UnitTestCase { 
    83161                );
    84162        }
    85163
     164        /**
     165         * Test post status transition update term counts correctly.
     166         *
     167         * @covers wp_modify_term_count_by
     168         * @dataProvider data_term_count_transitions_update_term_counts
     169         * @ticket 40351
     170         *
     171         * @param string $original_post_status Post status upon create.
     172         * @param string $new_post_status      Post status after update.
     173         * @param int    $change               Expected change upon publish.
     174         */
     175        function test_term_count_transitions_update_term_counts( $original_post_status, $new_post_status, $change ) {
     176                $post_id    = self::$post_ids[ $original_post_status ];
     177                $term_count = get_term( get_option( 'default_category' ) )->count;
     178
     179                wp_update_post(
     180                        array(
     181                                'ID'          => $post_id,
     182                                'post_status' => $new_post_status,
     183                        )
     184                );
     185
     186                $expected = $term_count + $change;
     187                $this->assertSame( $expected, get_term( get_option( 'default_category' ) )->count );
     188        }
     189
     190        /**
     191         * Data provider for test_term_count_transitions_update_term_counts.
     192         *
     193         * @return array[] {
     194         *     @type string $original_post_status Post status upon create.
     195         *     @type string $new_post_status      Post status after update.
     196         *     @type int    $change               Expected change upon publish.
     197         * }
     198         */
     199        function data_term_count_transitions_update_term_counts() {
     200                return array(
     201                        // 0. Draft -> published post
     202                        array( 'draft', 'publish', 1 ),
     203                        // 1. Auto draft -> published post
     204                        array( 'auto-draft', 'publish', 1 ),
     205                        // 2. Private -> published post
     206                        array( 'private', 'publish', 1 ),
     207                        // 3. Published -> published post
     208                        array( 'publish', 'publish', 0 ),
     209
     210                        // 4. Draft -> private post
     211                        array( 'draft', 'private', 0 ),
     212                        // 5. Auto draft -> private post
     213                        array( 'auto-draft', 'private', 0 ),
     214                        // 6. Private -> private post
     215                        array( 'private', 'private', 0 ),
     216                        // 7. Published -> private post
     217                        array( 'publish', 'private', -1 ),
     218
     219                        // 8. Draft -> draft post
     220                        array( 'draft', 'draft', 0 ),
     221                        // 9. Auto draft -> draft post
     222                        array( 'auto-draft', 'draft', 0 ),
     223                        // 10. Private -> draft post
     224                        array( 'private', 'draft', 0 ),
     225                        // 11. Published -> draft post
     226                        array( 'publish', 'draft', -1 ),
     227                );
     228        }
     229
     230        /**
     231         * Term counts are not double incremented when post created.
     232         *
     233         * @covers wp_modify_term_count_by
     234         * @dataProvider data_term_count_changes_for_post_statuses_with_attachments
     235         * @ticket 40351
     236         *
     237         * @param string $post_status New post status.
     238         * @param int    $change      Expected change.
     239         */
     240        public function test_term_count_changes_for_post_statuses_with_attachments( $post_status, $change ) {
     241                $term_count = get_term( self::$attachment_term )->count;
     242                // Do not use shared fixture for this test as it relies on a new post.
     243                $post_id = $this->factory()->post->create( array( 'post_status' => $post_status ) );
     244                wp_add_object_terms( $post_id, self::$attachment_term, 'wp_test_tax_counts' );
     245                $attachment_id = self::factory()->attachment->create_object(
     246                        array(
     247                                'file'        => 'image.jpg',
     248                                'post_parent' => $post_id,
     249                                'post_status' => 'inherit',
     250                        )
     251                );
     252                wp_add_object_terms( $attachment_id, self::$attachment_term, 'wp_test_tax_counts' );
     253
     254                $expected = $term_count + $change;
     255                $this->assertSame( $expected, get_term( self::$attachment_term )->count );
     256        }
     257
     258        /**
     259         * Data provider for test_term_count_changes_for_post_statuses_with_attachments.
     260         *
     261         * @return array[] {
     262         *     @type string $post_status New post status.
     263         *     @type int    $change      Expected change.
     264         * }
     265         */
     266        function data_term_count_changes_for_post_statuses_with_attachments() {
     267                return array(
     268                        // 0. Published post
     269                        array( 'publish', 2 ),
     270                        // 1. Auto draft
     271                        array( 'auto-draft', 0 ),
     272                        // 2. Draft
     273                        array( 'draft', 0 ),
     274                        // 3. Private post
     275                        array( 'private', 0 ),
     276                );
     277        }
     278
    86279        /**
    87280         * Term counts increments correctly when post status becomes published.
    88281         *
    89282         * @covers wp_publish_post
     283         * @covers wp_modify_term_count_by
    90284         * @dataProvider data_term_counts_incremented_on_publish_with_attachments
    91285         * @ticket 40351
    92286         * @ticket 51292
    class Tests_Term_termCount extends WP_UnitTestCase { 
    134328                );
    135329        }
    136330
     331        /**
     332         * Test post status transition update term counts correctly.
     333         *
     334         * @covers wp_modify_term_count_by
     335         * @dataProvider data_term_count_transitions_update_term_counts_with_attachments
     336         * @ticket 40351
     337         *
     338         * @param string $original_post_status Post status upon create.
     339         * @param string $new_post_status      Post status after update.
     340         * @param int    $change               Expected change upon publish.
     341         */
     342        function test_term_count_transitions_update_term_counts_with_attachments( $original_post_status, $new_post_status, $change ) {
     343                $post_id = self::$post_ids[ $original_post_status ];
     344                wp_add_object_terms( $post_id, self::$attachment_term, 'wp_test_tax_counts' );
     345                $attachment_id = self::factory()->attachment->create_object(
     346                        array(
     347                                'file'        => 'image.jpg',
     348                                'post_parent' => $post_id,
     349                                'post_status' => 'inherit',
     350                        )
     351                );
     352                wp_add_object_terms( $attachment_id, self::$attachment_term, 'wp_test_tax_counts' );
     353                $term_count = get_term( self::$attachment_term )->count;
     354
     355                wp_update_post(
     356                        array(
     357                                'ID'          => $post_id,
     358                                'post_status' => $new_post_status,
     359                        )
     360                );
     361
     362                $expected = $term_count + $change;
     363                $this->assertSame( $expected, get_term( self::$attachment_term )->count );
     364        }
     365
     366        /**
     367         * Data provider for test_term_count_transitions_update_term_counts_with_attachments.
     368         *
     369         * @return array[] {
     370         *     @type string $original_post_status Post status upon create.
     371         *     @type string $new_post_status      Post status after update.
     372         *     @type int    $change               Expected change upon publish.
     373         * }
     374         */
     375        function data_term_count_transitions_update_term_counts_with_attachments() {
     376                return array(
     377                        // 0. Draft -> published post
     378                        array( 'draft', 'publish', 2 ),
     379                        // 1. Auto draft -> published post
     380                        array( 'auto-draft', 'publish', 2 ),
     381                        // 2. Private -> published post
     382                        array( 'private', 'publish', 2 ),
     383                        // 3. Published -> published post
     384                        array( 'publish', 'publish', 0 ),
     385
     386                        // 4. Draft -> private post
     387                        array( 'draft', 'private', 0 ),
     388                        // 5. Auto draft -> private post
     389                        array( 'auto-draft', 'private', 0 ),
     390                        // 6. Private -> private post
     391                        array( 'private', 'private', 0 ),
     392                        // 7. Published -> private post
     393                        array( 'publish', 'private', -2 ),
     394
     395                        // 8. Draft -> draft post
     396                        array( 'draft', 'draft', 0 ),
     397                        // 9. Auto draft -> draft post
     398                        array( 'auto-draft', 'draft', 0 ),
     399                        // 10. Private -> draft post
     400                        array( 'private', 'draft', 0 ),
     401                        // 11. Published -> draft post
     402                        array( 'publish', 'draft', -2 ),
     403                );
     404        }
     405
     406        /**
     407         * Term counts are not double incremented when post created.
     408         *
     409         * @covers wp_modify_term_count_by
     410         * @dataProvider data_term_count_changes_for_post_statuses_with_untermed_attachments
     411         * @ticket 40351
     412         *
     413         * @param string $post_status New post status.
     414         * @param int    $change      Expected change.
     415         */
     416        public function test_term_count_changes_for_post_statuses_with_untermed_attachments( $post_status, $change ) {
     417                $term_count = get_term( self::$attachment_term )->count;
     418                // Do not use shared fixture for this test as it relies on a new post.
     419                $post_id = $this->factory()->post->create( array( 'post_status' => $post_status ) );
     420                wp_add_object_terms( $post_id, self::$attachment_term, 'wp_test_tax_counts' );
     421                $attachment_id = self::factory()->attachment->create_object(
     422                        array(
     423                                'file'        => 'image.jpg',
     424                                'post_parent' => $post_id,
     425                                'post_status' => 'inherit',
     426                        )
     427                );
     428
     429                $expected = $term_count + $change;
     430                $this->assertSame( $expected, get_term( self::$attachment_term )->count );
     431        }
     432
     433        /**
     434         * Data provider for test_term_count_changes_for_post_statuses_with_untermed_attachments.
     435         *
     436         * @return array[] {
     437         *     @type string $post_status New post status.
     438         *     @type int    $change      Expected change.
     439         * }
     440         */
     441        function data_term_count_changes_for_post_statuses_with_untermed_attachments() {
     442                return array(
     443                        // 0. Published post
     444                        array( 'publish', 1 ),
     445                        // 1. Auto draft
     446                        array( 'auto-draft', 0 ),
     447                        // 2. Draft
     448                        array( 'draft', 0 ),
     449                        // 3. Private post
     450                        array( 'private', 0 ),
     451                );
     452        }
     453
    137454        /**
    138455         * Term counts increments correctly when post status becomes published.
    139456         *
     457         * @covers wp_modify_term_count_by
    140458         * @covers wp_publish_post
    141459         * @dataProvider data_term_counts_incremented_on_publish_with_untermed_attachments
    142460         * @ticket 40351
    class Tests_Term_termCount extends WP_UnitTestCase { 
    183501                        array( 'private', 1 ),
    184502                );
    185503        }
     504
     505        /**
     506         * Test post status transition update term counts correctly.
     507         *
     508         * @covers wp_modify_term_count_by
     509         * @dataProvider data_term_count_transitions_update_term_counts_with_untermed_attachments
     510         * @ticket 40351
     511         *
     512         * @param string $original_post_status Post status upon create.
     513         * @param string $new_post_status      Post status after update.
     514         * @param int    $change               Expected change upon publish.
     515         */
     516        function test_term_count_transitions_update_term_counts_with_untermed_attachments( $original_post_status, $new_post_status, $change ) {
     517                $post_id = self::$post_ids[ $original_post_status ];
     518                wp_add_object_terms( $post_id, self::$attachment_term, 'wp_test_tax_counts' );
     519                $attachment_id = self::factory()->attachment->create_object(
     520                        array(
     521                                'file'        => 'image.jpg',
     522                                'post_parent' => $post_id,
     523                                'post_status' => 'inherit',
     524                        )
     525                );
     526                $term_count    = get_term( self::$attachment_term )->count;
     527
     528                wp_update_post(
     529                        array(
     530                                'ID'          => $post_id,
     531                                'post_status' => $new_post_status,
     532                        )
     533                );
     534
     535                $expected = $term_count + $change;
     536                $this->assertSame( $expected, get_term( self::$attachment_term )->count );
     537        }
     538
     539        /**
     540         * Data provider for test_term_count_transitions_update_term_counts_with_untermed_attachments.
     541         *
     542         * @return array[] {
     543         *     @type string $original_post_status Post status upon create.
     544         *     @type string $new_post_status      Post status after update.
     545         *     @type int    $change               Expected change upon publish.
     546         * }
     547         */
     548        function data_term_count_transitions_update_term_counts_with_untermed_attachments() {
     549                return array(
     550                        // 0. Draft -> published post
     551                        array( 'draft', 'publish', 1 ),
     552                        // 1. Auto draft -> published post
     553                        array( 'auto-draft', 'publish', 1 ),
     554                        // 2. Private -> published post
     555                        array( 'private', 'publish', 1 ),
     556                        // 3. Published -> published post
     557                        array( 'publish', 'publish', 0 ),
     558
     559                        // 4. Draft -> private post
     560                        array( 'draft', 'private', 0 ),
     561                        // 5. Auto draft -> private post
     562                        array( 'auto-draft', 'private', 0 ),
     563                        // 6. Private -> private post
     564                        array( 'private', 'private', 0 ),
     565                        // 7. Published -> private post
     566                        array( 'publish', 'private', -1 ),
     567
     568                        // 8. Draft -> draft post
     569                        array( 'draft', 'draft', 0 ),
     570                        // 9. Auto draft -> draft post
     571                        array( 'auto-draft', 'draft', 0 ),
     572                        // 10. Private -> draft post
     573                        array( 'private', 'draft', 0 ),
     574                        // 11. Published -> draft post
     575                        array( 'publish', 'draft', -1 ),
     576                );
     577        }
     578
     579        /**
     580         * User taxonomy term counts increments when added to an account.
     581         *
     582         * @covers wp_modify_term_count_by
     583         * @ticket 51292
     584         */
     585        public function test_term_counts_user_adding_term() {
     586                $term_count = get_term( self::$user_term )->count;
     587                wp_add_object_terms( self::$user_id, self::$user_term, 'wp_test_user_tax_counts' );
     588
     589                $expected = $term_count + 1;
     590                $this->assertSame( $expected, get_term( self::$user_term )->count );
     591        }
     592
     593        /**
     594         * User taxonomy term counts decrement when term deleted from user.
     595         *
     596         * @covers wp_modify_term_count_by
     597         * @ticket 51292
     598         */
     599        public function test_term_counts_user_removing_term() {
     600                wp_add_object_terms( self::$user_id, self::$user_term, 'wp_test_user_tax_counts' );
     601                $term_count = get_term( self::$user_term )->count;
     602
     603                wp_remove_object_terms( self::$user_id, self::$user_term, 'wp_test_user_tax_counts' );
     604                $expected = $term_count - 1;
     605                $this->assertSame( $expected, get_term( self::$user_term )->count );
     606        }
     607
     608        /**
     609         * Ensure DB queries for deferred counts are nullified for net zero gain.
     610         *
     611         * @covers wp_modify_term_count_by
     612         * @covers wp_defer_term_counting
     613         * @ticket 51292
     614         */
     615        public function test_counts_after_deferral_net_zero() {
     616                $post_one = self::$post_ids['publish'];
     617                $post_two = self::$post_ids['publish_two'];
     618                $terms    = self::$tag_ids;
     619
     620                wp_set_object_terms( $post_one, $terms[0], 'post_tag', true );
     621
     622                // Net gain 0;
     623                wp_defer_term_counting( true );
     624                wp_remove_object_terms( $post_one, $terms[0], 'post_tag' );
     625                wp_set_object_terms( $post_two, $terms[0], 'post_tag', true );
     626                $num_queries = get_num_queries();
     627                wp_defer_term_counting( false );
     628                // Ensure number of queries unchanged.
     629                $this->assertSame( $num_queries, get_num_queries() );
     630        }
     631
     632        /**
     633         * Ensure full recounts follow modify by X recounts to avoid miscounts.
     634         *
     635         * @covers wp_modify_term_count_by
     636         * @covers wp_update_term_count
     637         * @covers wp_defer_term_counting
     638         * @ticket 51292
     639         */
     640        public function test_counts_after_deferral_full_before_partial() {
     641                $post_one   = self::$post_ids['publish'];
     642                $terms      = self::$tag_ids;
     643                $term_count = get_term( $terms[0] )->count;
     644
     645                // Net gain 1;
     646                wp_defer_term_counting( true );
     647                wp_set_object_terms( $post_one, $terms[0], 'post_tag', true );
     648                wp_update_term_count( get_term( $terms[0] )->term_taxonomy_id, 'post_tag' );
     649                wp_defer_term_counting( false );
     650
     651                // Ensure term count is correct.
     652                $expected = $term_count + 1;
     653                $this->assertSame( $expected, get_term( $terms[0] )->count );
     654        }
     655
     656        /**
     657         * Ensure DB queries for deferred counts are combined.
     658         *
     659         * @covers wp_modify_term_count_by
     660         * @covers wp_defer_term_counting
     661         * @ticket 51292
     662         */
     663        public function test_counts_after_deferral_matching_changes() {
     664                $post_one = self::$post_ids['publish'];
     665                $post_two = self::$post_ids['publish_two'];
     666                $terms    = self::$tag_ids;
     667
     668                wp_set_object_terms( $post_one, $terms[0], 'post_tag', true );
     669
     670                // Net gain 0:
     671                wp_defer_term_counting( true );
     672                wp_remove_object_terms( $post_one, $terms[0], 'post_tag' );
     673                wp_set_object_terms( $post_two, $terms[0], 'post_tag', true );
     674
     675                // Net gain 1:
     676                wp_set_object_terms( $post_one, $terms[1], 'post_tag', true );
     677                wp_set_object_terms( $post_two, $terms[2], 'post_tag', true );
     678
     679                // Net gain 2:
     680                wp_set_object_terms( $post_one, array( $terms[3], $terms[4] ), 'post_tag', true );
     681                wp_set_object_terms( $post_two, array( $terms[3], $terms[4] ), 'post_tag', true );
     682
     683                $num_queries = get_num_queries();
     684                wp_defer_term_counting( false );
     685
     686                /*
     687                 * Each count is expected to produce two queries:
     688                 * 1) The count update
     689                 * 2) The SELECT in `clean_term_cache()`.
     690                 */
     691                $expected = $num_queries + ( 2 * 2 );
     692                // Ensure number of queries correct.
     693                $this->assertSame( $expected, get_num_queries() );
     694        }
    186695}