WordPress.org

Make WordPress Core

Ticket #38280: 38280.14.diff

File 38280.14.diff, 17.5 KB (added by boonebgorges, 2 years ago)
  • src/wp-includes/taxonomy.php

    diff --git a/src/wp-includes/taxonomy.php b/src/wp-includes/taxonomy.php
    index 102345fd3c..ff72ec9986 100644
    a b function wp_update_term_count_now( $terms, $taxonomy ) { 
    30913091        return true;
    30923092}
    30933093
     3094/**
     3095 * Retrieves the term count for a specific object type.
     3096 *
     3097 * @since 5.0.0
     3098 *
     3099 * @param int    $term_id     Term ID.
     3100 * @param string $taxonomy    Taxonomy name.
     3101 * @param string $object_type Object type.
     3102 *
     3103 * @return WP_Error|int WP_Error on failure, object term count otherwise.
     3104 */
     3105function wp_get_term_count_for_object_type( $term_id, $taxonomy, $object_type ) {
     3106        if ( ! taxonomy_exists( $taxonomy ) ) {
     3107                return new WP_Error(
     3108                        'invalid_taxonomy',
     3109                        __( 'Invalid taxonomy.' ),
     3110                        array(
     3111                                'taxonomy' => $taxonomy,
     3112                        )
     3113                );
     3114        }
     3115
     3116        if ( ! is_object_in_taxonomy( $object_type, $taxonomy ) ) {
     3117                return new WP_Error(
     3118                        'invalid_object_type',
     3119                        __( 'Object type is not in taxonomy.' ),
     3120                        array(
     3121                                'object_type' => $object_type,
     3122                                'taxonomy'    => $taxonomy,
     3123                        )
     3124                );
     3125        }
     3126
     3127        $term = get_term( $term_id, $taxonomy );
     3128
     3129        if ( is_wp_error( $term ) ) {
     3130                return $term;
     3131        }
     3132
     3133        if ( 0 === $term->count ) {
     3134                return 0;
     3135        }
     3136
     3137        $taxonomy_object = get_taxonomy( $taxonomy );
     3138
     3139        if ( 1 >= count( $taxonomy_object->object_type ) ) {
     3140                return $term->count;
     3141        }
     3142
     3143        $term_object_count = wp_get_term_count_for_object_type_from_meta( $term_id, $object_type );
     3144
     3145        // Term has not been counted for the object type. Calculate and refetch.
     3146        if ( false === $term_object_count ) {
     3147                wp_update_term_count_now( array( $term->term_taxonomy_id ), $taxonomy );
     3148
     3149                // If it's still false, the count cannot be calculated, so we return 0.
     3150                $term_object_count = (int) wp_get_term_count_for_object_type_from_meta( $term_id, $object_type );
     3151        }
     3152
     3153        return $term_object_count;
     3154}
     3155
     3156/**
     3157 * Get the cached term-object count from termmeta.
     3158 *
     3159 * @param int    $term_id     ID of the term.
     3160 * @param string $object_type Object type.
     3161 * @return bool|int Returns false when no metadata is found, which indicates that a count has not taken place.
     3162 */
     3163function wp_get_term_count_for_object_type_from_meta( $term_id, $object_type ) {
     3164        $term_object_count = get_term_meta( $term_id, '_wp_object_count_' . $object_type, true );
     3165        if ( $term_object_count ) {
     3166                return (int) $term_object_count;
     3167        }
     3168
     3169        $counted_object_types = (array) get_term_meta( $term_id, '_wp_counted_object_types', true );
     3170
     3171        // When the object type is marked counted and no meta key exists, the count is 0.
     3172        if ( in_array( $object_type, $counted_object_types, true ) ) {
     3173                return 0;
     3174        }
     3175
     3176        return false;
     3177}
     3178
    30943179//
    30953180// Cache
    30963181//
    function _prime_term_caches( $term_ids, $update_meta_cache = true ) { 
    36053690 *
    36063691 * @access private
    36073692 * @since 2.3.0
     3693 * @since 5.0.0 Store term counts on a per object type basis in meta.
    36083694 *
    36093695 * @global wpdb $wpdb WordPress database abstraction object.
    36103696 *
    function _update_post_term_count( $terms, $taxonomy ) { 
    36283714        }
    36293715
    36303716        if ( $object_types ) {
    3631                 $object_types = esc_sql( array_filter( $object_types, 'post_type_exists' ) );
     3717                $object_types = array_filter( $object_types, 'post_type_exists' );
    36323718        }
    36333719
    3634         foreach ( (array) $terms as $term ) {
     3720        foreach ( (array) $terms as $tt_id ) {
    36353721                $count = 0;
    36363722
     3723                $term = get_term_by( 'term_taxonomy_id', $tt_id );
     3724
     3725                // Remove previous counts to prevent stale data if an object type is removed from a taxonomy.
     3726                $counted_object_types = (array) get_term_meta( $term->term_id, '_wp_counted_object_types', true );
     3727
     3728                foreach ( $counted_object_types as $o_type ) {
     3729                        delete_term_meta( $term->term_id, '_wp_object_count_' . $o_type );
     3730                }
     3731
     3732                delete_term_meta( $term->term_id, '_wp_counted_object_types' );
     3733
     3734                $term_count_meta = array();
     3735
     3736                if ( $object_types ) {
     3737                        foreach ( $object_types as $type ) {
     3738                                $current_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 = 'publish' AND post_type = %s AND term_taxonomy_id = %d", $type, $tt_id ) );
     3739
     3740                                $count += $current_count;
     3741
     3742                                $term_count_meta[ $type ] = $current_count;
     3743                        }
     3744                }
     3745
    36373746                // Attachments can be 'inherit' status, we need to base count off the parent's status if so.
    36383747                if ( $check_attachments ) {
    3639                         $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 = 'publish' OR ( post_status = 'inherit' AND post_parent > 0 AND ( SELECT post_status FROM $wpdb->posts WHERE ID = p1.post_parent ) = 'publish' ) ) AND post_type = 'attachment' AND term_taxonomy_id = %d", $term ) );
     3748                        $attachment_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 = 'publish' OR ( post_status = 'inherit' AND post_parent > 0 AND ( SELECT post_status FROM $wpdb->posts WHERE ID = p1.post_parent ) = 'publish' ) ) AND post_type = 'attachment' AND term_taxonomy_id = %d", $tt_id ) );
     3749
     3750                        $count += $attachment_count;
     3751
     3752                        $term_count_meta['attachment'] = $attachment_count;
     3753
     3754                        // Re-add attachment so the meta gets saved below.
     3755                        $object_types[] = 'attachment';
    36403756                }
    36413757
    3642                 if ( $object_types ) {
    3643                         $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 = 'publish' AND post_type IN ('" . implode( "', '", $object_types ) . "') AND term_taxonomy_id = %d", $term ) );
     3758                // Save individual counts for each object type in term meta.
     3759                if ( 1 < count( $term_count_meta ) ) {
     3760                        foreach ( $object_types as $type ) {
     3761                                if ( ! empty( $term_count_meta[ $type ] ) ) {
     3762                                        update_term_meta( $term->term_id, '_wp_object_count_' . $type, (int) $term_count_meta[ $type ] );
     3763                                }
     3764                        }
     3765
     3766                        update_term_meta( $term->term_id, '_wp_counted_object_types', $object_types );
    36443767                }
    36453768
    36463769                /** This action is documented in wp-includes/taxonomy.php */
    3647                 do_action( 'edit_term_taxonomy', $term, $taxonomy->name );
    3648                 $wpdb->update( $wpdb->term_taxonomy, compact( 'count' ), array( 'term_taxonomy_id' => $term ) );
     3770                do_action( 'edit_term_taxonomy', $tt_id, $taxonomy->name );
     3771                $wpdb->update( $wpdb->term_taxonomy, compact( 'count' ), array( 'term_taxonomy_id' => $tt_id ) );
    36493772
    36503773                /** This action is documented in wp-includes/taxonomy.php */
    3651                 do_action( 'edited_term_taxonomy', $term, $taxonomy->name );
     3774                do_action( 'edited_term_taxonomy', $tt_id, $taxonomy->name );
    36523775        }
    36533776}
    36543777
  • tests/phpunit/tests/term/wpGetObjectTerms.php

    diff --git a/tests/phpunit/tests/term/wpGetObjectTerms.php b/tests/phpunit/tests/term/wpGetObjectTerms.php
    index e7851dcaee..4bf496ffe0 100644
    a b class Tests_Term_WpGetObjectTerms extends WP_UnitTestCase { 
    599599                $p = self::factory()->post->create();
    600600                wp_set_object_terms( $p, $terms, 'wptests_tax' );
    601601
     602                /**
     603                 * `wp_set_object_terms()` populates the cache, but we need it empty to verify
     604                 * behavior of 'update_term_meta_cache'.
     605                 */
     606                foreach ( $terms as $t ) {
     607                        wp_cache_delete( $t, 'term_meta' );
     608                }
     609
    602610                $found = wp_get_object_terms(
    603611                        $p, 'wptests_tax', array(
    604612                                'update_term_meta_cache' => false,
  • new file tests/phpunit/tests/term/wpGetTermCountForObjectType.php

    diff --git a/tests/phpunit/tests/term/wpGetTermCountForObjectType.php b/tests/phpunit/tests/term/wpGetTermCountForObjectType.php
    new file mode 100644
    index 0000000000..674ffae4d0
    - +  
     1<?php
     2
     3/**
     4 * Class Tests_Term_wpGetTermCountForObjectType
     5 *
     6 * @group taxonomy
     7 * @ticket 38280
     8 */
     9class Tests_Term_wpGetTermCountForObjectType extends WP_UnitTestCase {
     10
     11        /**
     12         * Test when a taxonomy is invalid.
     13         */
     14        public function test_wp_get_term_count_for_object_type_invalid_taxonomy() {
     15                $actual = wp_get_term_count_for_object_type( 0, 'does-not-exist', 'post' );
     16
     17                $this->assertWPError( $actual );
     18                $this->assertSame( 'invalid_taxonomy', $actual->get_error_code() );
     19        }
     20
     21        /**
     22         * Test when an object type is not in a taxonomy.
     23         */
     24        public function test_wp_get_term_count_for_object_type_object_not_in_taxonomy() {
     25                $this->assertWPError( wp_get_term_count_for_object_type( 0, 'category', 'page' ) );
     26        }
     27
     28        /**
     29         * Test when an invalid term is passed.
     30         */
     31        public function test_wp_get_term_count_for_object_type_invalid_term() {
     32                $actual = wp_get_term_count_for_object_type( 0, 'category', 'post' );
     33
     34                $this->assertWPError( $actual );
     35                $this->assertSame( 'invalid_term', $actual->get_error_code() );
     36        }
     37
     38        /**
     39         * Test when a taxonomy belongs to a single object type. No term meta should be
     40         * stored and the count property should be used.
     41         */
     42        public function test_wp_get_term_count_for_object_type_single_object_type() {
     43                $term_id = self::factory()->term->create( array( 'taxonomy' => 'category' ) );
     44                $post_id = self::factory()->post->create(
     45                        array(
     46                                'post_type'     => 'post',
     47                                'post_category' => array(
     48                                        $term_id,
     49                                ),
     50                        )
     51                );
     52
     53                $this->assertFalse( (bool) get_term_meta( $term_id, '_wp_counted_object_types', true ) );
     54                $this->assertEquals( 1, wp_get_term_count_for_object_type( $term_id, 'category', 'post' ) );
     55
     56                $term_object = get_term( $term_id, 'category' );
     57                $this->assertEquals( 1, $term_object->count );
     58
     59                wp_remove_object_terms( $post_id, array( $term_id ), 'category' );
     60
     61                $this->assertEquals( 0, wp_get_term_count_for_object_type( $term_id, 'category', 'post' ) );
     62
     63                $term_object = get_term( $term_id, 'category' );
     64                $this->assertEquals( 0, $term_object->count );
     65        }
     66
     67        /**
     68         * Test when a taxonomy belongs to more than one object, and at least one object is not a post type.
     69         */
     70        public function test_wp_get_term_count_for_object_type_non_post_type() {
     71                register_taxonomy(
     72                        'wptests_tax',
     73                        array(
     74                                'user',
     75                                'foo'
     76                        )
     77                );
     78
     79                $term_id = self::factory()->term->create(
     80                        array(
     81                                'taxonomy' => 'wptests_tax',
     82                        )
     83                );
     84
     85                $foo_id = 99999;
     86
     87                wp_set_object_terms( $foo_id, $term_id, 'wptests_tax' );
     88
     89                // 'foo' object doesn't have an update count callback, so the value is always 0.
     90                $count = wp_get_term_count_for_object_type( $term_id, 'wptests_tax', 'foo' );
     91                $this->assertSame( 0, $count );
     92        }
     93
     94        /**
     95         * Test when a taxonomy belongs to multiple object types.
     96         */
     97        public function test_wp_get_term_count_for_object_type_multiple_object_types() {
     98                register_post_type( 'wptests_cpt' );
     99                register_taxonomy(
     100                        'wptests_tax',
     101                        array(
     102                                'post',
     103                                'wptests_cpt',
     104                        )
     105                );
     106
     107                $term_id        = self::factory()->term->create( array( 'taxonomy' => 'wptests_tax' ) );
     108                $post_id        = self::factory()->post->create( array( 'post_type' => 'post' ) );
     109                $custom_post_id = self::factory()->post->create( array( 'post_type' => 'wptests_cpt' ) );
     110
     111                // When a term has no relationships, no meta should be stored.
     112                $this->assertEquals( 0, wp_get_term_count_for_object_type( $term_id, 'wptests_tax', 'post' ) );
     113                $this->assertEquals( 0, wp_get_term_count_for_object_type( $term_id, 'wptests_tax', 'wptests_cpt' ) );
     114                $this->assertEmpty( get_term_meta( $term_id, '_wp_counted_object_types', true ) );
     115                $this->assertEmpty( get_term_meta( $term_id, '_wp_object_count_post', true ) );
     116                $this->assertEmpty( get_term_meta( $term_id, '_wp_object_count_wptests_cpt', true ) );
     117
     118                wp_set_object_terms( $post_id, array( $term_id ), 'wptests_tax' );
     119
     120                // Term has relationships, meta should be stored caching types counted and counts for each type > 0.
     121                $this->assertEquals( 1, wp_get_term_count_for_object_type( $term_id, 'wptests_tax', 'post' ) );
     122                $this->assertEquals( 0, wp_get_term_count_for_object_type( $term_id, 'wptests_tax', 'wptests_cpt' ) );
     123                $this->assertEquals( array( 'post', 'wptests_cpt' ), get_term_meta( $term_id, '_wp_counted_object_types', true ) );
     124                $this->assertEquals( 1, get_term_meta( $term_id, '_wp_object_count_post', true ) );
     125                $this->assertEmpty( get_term_meta( $term_id, '_wp_object_count_wptests_cpt', true ) );
     126
     127                wp_set_object_terms( $custom_post_id, array( $term_id ), 'wptests_tax' );
     128
     129                $this->assertEquals( 1, wp_get_term_count_for_object_type( $term_id, 'wptests_tax', 'post' ) );
     130                $this->assertEquals( 1, wp_get_term_count_for_object_type( $term_id, 'wptests_tax', 'wptests_cpt' ) );
     131                $this->assertEquals( array( 'post', 'wptests_cpt' ), get_term_meta( $term_id, '_wp_counted_object_types', true ) );
     132                $this->assertEquals( 1, get_term_meta( $term_id, '_wp_object_count_post', true ) );
     133                $this->assertEquals( 1, get_term_meta( $term_id, '_wp_object_count_wptests_cpt', true ) );
     134
     135                // Total count should be stored in the term's count property.
     136                $term_object = get_term( $term_id, 'wptests_tax' );
     137                $this->assertEquals( 2, $term_object->count );
     138
     139                wp_remove_object_terms( $custom_post_id, array( $term_id ), 'wptests_tax' );
     140
     141                // Object count cache should be removed.
     142                $this->assertEquals( 1, wp_get_term_count_for_object_type( $term_id, 'wptests_tax', 'post' ) );
     143                $this->assertEquals( 0, wp_get_term_count_for_object_type( $term_id, 'wptests_tax', 'wptests_cpt' ) );
     144                $this->assertEquals( array( 'post', 'wptests_cpt' ), get_term_meta( $term_id, '_wp_counted_object_types', true ) );
     145                $this->assertEquals( 1, get_term_meta( $term_id, '_wp_object_count_post', true ) );
     146                $this->assertEmpty( get_term_meta( $term_id, '_wp_object_count_wptests_cpt', true ) );
     147
     148                wp_remove_object_terms( $post_id, array( $term_id ), 'wptests_tax' );
     149
     150                $this->assertEquals( 0, wp_get_term_count_for_object_type( $term_id, 'wptests_tax', 'post' ) );
     151                $this->assertEquals( 0, wp_get_term_count_for_object_type( $term_id, 'wptests_tax', 'wptests_cpt' ) );
     152                $this->assertEquals( array( 'post', 'wptests_cpt' ), get_term_meta( $term_id, '_wp_counted_object_types', true ) );
     153                $this->assertEmpty( get_term_meta( $term_id, '_wp_object_count_post', true ) );
     154                $this->assertEmpty( get_term_meta( $term_id, '_wp_object_count_wptests_cpt', true ) );
     155        }
     156
     157        /**
     158         * Test when a taxonomy belongs to multiple object types, one of which is attachments.
     159         */
     160        public function test_wp_get_term_count_for_object_type_multiple_object_types_attachment() {
     161                register_taxonomy(
     162                        'wptests_tax',
     163                        array(
     164                                'post',
     165                                'attachment',
     166                        )
     167                );
     168
     169                $term_id       = self::factory()->term->create( array( 'taxonomy' => 'wptests_tax' ) );
     170                $post_id       = self::factory()->post->create( array( 'post_type' => 'post' ) );
     171                $attachment_id = self::factory()->attachment->create_upload_object( DIR_TESTDATA . '/images/canola.jpg', $post_id );
     172
     173                // When a term has no relationships, no meta should be stored.
     174                $this->assertEquals( 0, wp_get_term_count_for_object_type( $term_id, 'wptests_tax', 'post' ) );
     175                $this->assertEquals( 0, wp_get_term_count_for_object_type( $term_id, 'wptests_tax', 'attachment' ) );
     176                $this->assertEmpty( get_term_meta( $term_id, '_wp_counted_object_types', true ) );
     177                $this->assertEmpty( get_term_meta( $term_id, '_wp_object_count_post', true ) );
     178                $this->assertEmpty( get_term_meta( $term_id, '_wp_object_count_attachment', true ) );
     179
     180                wp_set_object_terms( $post_id, array( $term_id ), 'wptests_tax' );
     181
     182                // Term has relationships, meta should be stored caching types counted and counts for each type > 0.
     183                $this->assertEquals( 1, wp_get_term_count_for_object_type( $term_id, 'wptests_tax', 'post' ) );
     184                $this->assertEquals( 0, wp_get_term_count_for_object_type( $term_id, 'wptests_tax', 'attachment' ) );
     185                $this->assertEquals( array( 'post', 'attachment' ), get_term_meta( $term_id, '_wp_counted_object_types', true ) );
     186                $this->assertEquals( 1, get_term_meta( $term_id, '_wp_object_count_post', true ) );
     187                $this->assertEmpty( get_term_meta( $term_id, '_wp_object_count_attachment', true ) );
     188
     189                wp_set_object_terms( $attachment_id, array( $term_id ), 'wptests_tax' );
     190
     191                $this->assertEquals( 1, wp_get_term_count_for_object_type( $term_id, 'wptests_tax', 'post' ) );
     192                $this->assertEquals( 1, wp_get_term_count_for_object_type( $term_id, 'wptests_tax', 'attachment' ) );
     193                $this->assertEquals( array( 'post', 'attachment' ), get_term_meta( $term_id, '_wp_counted_object_types', true ) );
     194                $this->assertEquals( 1, get_term_meta( $term_id, '_wp_object_count_post', true ) );
     195                $this->assertEquals( 1, get_term_meta( $term_id, '_wp_object_count_attachment', true ) );
     196
     197                // Total count should be stored in the term's count property.
     198                $term_object = get_term( $term_id, 'wptests_tax' );
     199                $this->assertEquals( 2, $term_object->count );
     200
     201                wp_remove_object_terms( $post_id, array( $term_id ), 'wptests_tax' );
     202
     203                // Object count cache should be removed.
     204                $this->assertEquals( 0, wp_get_term_count_for_object_type( $term_id, 'wptests_tax', 'post' ) );
     205                $this->assertEquals( 1, wp_get_term_count_for_object_type( $term_id, 'wptests_tax', 'attachment' ) );
     206                $this->assertEmpty( get_term_meta( $term_id, '_wp_object_count_post', true ) );
     207                $this->assertEquals( 1, get_term_meta( $term_id, '_wp_object_count_attachment', true ) );
     208                wp_remove_object_terms( $attachment_id, array( $term_id ), 'wptests_tax' );
     209
     210                $this->assertEquals( 0, wp_get_term_count_for_object_type( $term_id, 'wptests_tax', 'post' ) );
     211                $this->assertEquals( 0, wp_get_term_count_for_object_type( $term_id, 'wptests_tax', 'attachment' ) );
     212                $this->assertEquals( array( 'post', 'attachment' ), get_term_meta( $term_id, '_wp_counted_object_types', true ) );
     213                $this->assertEmpty( get_term_meta( $term_id, '_wp_object_count_post', true ) );
     214                $this->assertEmpty( get_term_meta( $term_id, '_wp_object_count_attachment', true ) );
     215        }
     216}