Ticket #38280: 38280.18.diff
File 38280.18.diff, 20.4 KB (added by , 5 years ago) |
---|
-
src/wp-includes/default-filters.php
diff --git a/src/wp-includes/default-filters.php b/src/wp-includes/default-filters.php index 5487c8fbe4..3146bea49f 100644
a b add_action( 'post_updated', 'wp_save_post_revision', 10, 1 ); 343 343 add_action( 'publish_post', '_publish_post_hook', 5, 1 ); 344 344 add_action( 'transition_post_status', '_transition_post_status', 5, 3 ); 345 345 add_action( 'transition_post_status', '_update_term_count_on_transition_post_status', 10, 3 ); 346 add_action( 'wp_no_term_object_count_found', 'wp_update_term_count_for_post_type', 10, 2 ); 346 347 add_action( 'comment_form', 'wp_comment_form_unfiltered_html_nonce' ); 347 348 add_action( 'admin_init', 'send_frame_options_header', 10, 0 ); 348 349 add_action( 'welcome_panel', 'wp_welcome_panel' ); -
src/wp-includes/taxonomy.php
diff --git a/src/wp-includes/taxonomy.php b/src/wp-includes/taxonomy.php index 3f9b960538..40bb988c78 100644
a b function wp_update_term_count_now( $terms, $taxonomy ) { 3124 3124 return true; 3125 3125 } 3126 3126 3127 /** 3128 * Retrieves the number of objects of a specific type for a term. 3129 * 3130 * @since 5.0.0 3131 * 3132 * @param int $term_id Term ID. 3133 * @param string $taxonomy Taxonomy name. 3134 * @param string $object_type Object type. 3135 * 3136 * @return WP_Error|int WP_Error on failure, term's object count for specified type otherwise. 3137 */ 3138 function wp_get_term_object_count( $term_id, $taxonomy, $object_type ) { 3139 if ( ! taxonomy_exists( $taxonomy ) ) { 3140 return new WP_Error( 3141 'invalid_taxonomy', 3142 __( 'Invalid taxonomy.' ), 3143 array( 3144 'taxonomy' => $taxonomy, 3145 ) 3146 ); 3147 } 3148 3149 if ( ! is_object_in_taxonomy( $object_type, $taxonomy ) ) { 3150 return new WP_Error( 3151 'invalid_object_type', 3152 __( 'Object type is not in taxonomy.' ), 3153 array( 3154 'object_type' => $object_type, 3155 'taxonomy' => $taxonomy, 3156 ) 3157 ); 3158 } 3159 3160 $term = get_term( $term_id, $taxonomy ); 3161 3162 if ( is_wp_error( $term ) ) { 3163 return $term; 3164 } 3165 3166 $taxonomy_object = get_taxonomy( $taxonomy ); 3167 3168 if ( 0 === $term->count ) { 3169 // If the global count for a term is 0, no need for an object-specific count. 3170 $count = 0; 3171 } else { 3172 $count = wp_get_term_object_count_from_meta( $term_id, $object_type ); 3173 3174 // No calculated count found for this object type. 3175 if ( false === $count ) { 3176 /** 3177 * Fires when no object count is found for a given object type. 3178 * 3179 * This action provides an opportunity for third parties to run count routines. 3180 * When the object type is a post type, the count routine is run automatically. 3181 * See wp_update_term_count_for_post_type(). 3182 * 3183 * @since 5.0.0 3184 * 3185 * @param WP_Term $term Term object. 3186 * @param string $object_type Object type name. 3187 */ 3188 do_action( 'wp_no_term_object_count_found', $term, $object_type ); 3189 3190 // Try one more fetch after a count has potentially been triggered. 3191 $count = (int) wp_get_term_object_count_from_meta( $term_id, $object_type ); 3192 } 3193 } 3194 3195 /** 3196 * Filters the object count for a given term. 3197 * 3198 * @since 5.0.0 3199 * 3200 * @param int $count Count of objects of the given `$object_type` belonging to the term. 3201 * @param WP_Term $term Term object. 3202 * @param string $object_type Object type. 3203 */ 3204 return apply_filters( 'wp_term_object_count', $count, $term, $object_type ); 3205 } 3206 3207 /** 3208 * Triggers a term count refresh when `$object_type` is a post type. 3209 * 3210 * Term-object counts are generally recalculated at the time that term-relationships are 3211 * updated. See eg wp_set_object_terms(). But for terms that were last counted before the 3212 * introduction of term-object counts, this data will initially be missing. This function 3213 * forces a calculation of those counts when the object is a post type. Object types 3214 * that are not post types must provide their own separate mechanism for triggering 3215 * these counts. 3216 * 3217 * @since 5.0.0 3218 * 3219 * @param WP_Term $term Term object. 3220 * @param string $object_type Object type. 3221 */ 3222 function wp_update_term_count_for_post_type( $term, $object_type ) { 3223 // Non-post-types cannot be handled automatically. 3224 if ( ! post_type_exists( $object_type ) ) { 3225 return; 3226 } 3227 3228 // Taxonomies with their own count callbacks cannot be handled automatically. 3229 $taxonomy = get_taxonomy( $term->taxonomy ); 3230 if ( ! empty( $taxonomy->update_count_callback ) ) { 3231 return; 3232 } 3233 3234 wp_update_term_count_now( array( $term->term_taxonomy_id ), $term->taxonomy ); 3235 } 3236 3237 /** 3238 * Get the cached term-object count from termmeta. 3239 * 3240 * @param int $term_id ID of the term. 3241 * @param string $object_type Object type. 3242 * @return bool|int Returns false when no metadata is found, which indicates that a count has not taken place. 3243 */ 3244 function wp_get_term_object_count_from_meta( $term_id, $object_type ) { 3245 $term_object_count = get_term_meta( $term_id, '_wp_object_count_' . $object_type, true ); 3246 if ( $term_object_count ) { 3247 return (int) $term_object_count; 3248 } 3249 3250 $counted_object_types = (array) get_term_meta( $term_id, '_wp_counted_object_types', true ); 3251 3252 // When the object type is marked counted and no meta key exists, the count is 0. 3253 if ( in_array( $object_type, $counted_object_types, true ) ) { 3254 return 0; 3255 } 3256 3257 return false; 3258 } 3259 3127 3260 // 3128 3261 // Cache 3129 3262 // … … function _prime_term_caches( $term_ids, $update_meta_cache = true ) { 3638 3771 * 3639 3772 * @access private 3640 3773 * @since 2.3.0 3774 * @since 5.0.0 Store term counts on a per object type basis in meta. 3641 3775 * 3642 3776 * @global wpdb $wpdb WordPress database abstraction object. 3643 3777 * … … function _update_post_term_count( $terms, $taxonomy ) { 3661 3795 } 3662 3796 3663 3797 if ( $object_types ) { 3664 $object_types = esc_sql( array_filter( $object_types, 'post_type_exists' ));3798 $object_types = array_filter( $object_types, 'post_type_exists' ); 3665 3799 } 3666 3800 3667 foreach ( (array) $terms as $t erm) {3801 foreach ( (array) $terms as $tt_id ) { 3668 3802 $count = 0; 3669 3803 3804 $term = get_term_by( 'term_taxonomy_id', $tt_id ); 3805 3806 // Remove previous counts to prevent stale data if an object type is removed from a taxonomy. 3807 $counted_object_types = (array) get_term_meta( $term->term_id, '_wp_counted_object_types', true ); 3808 3809 foreach ( $counted_object_types as $o_type ) { 3810 delete_term_meta( $term->term_id, '_wp_object_count_' . $o_type ); 3811 } 3812 3813 delete_term_meta( $term->term_id, '_wp_counted_object_types' ); 3814 3815 $term_count_meta = array(); 3816 3817 if ( $object_types ) { 3818 foreach ( $object_types as $type ) { 3819 $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 ) ); 3820 3821 $count += $current_count; 3822 3823 $term_count_meta[ $type ] = $current_count; 3824 } 3825 } 3826 3670 3827 // Attachments can be 'inherit' status, we need to base count off the parent's status if so. 3671 3828 if ( $check_attachments ) { 3672 $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 ) ); 3829 $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 ) ); 3830 3831 $count += $attachment_count; 3832 3833 $term_count_meta['attachment'] = $attachment_count; 3834 3835 // Re-add attachment so the meta gets saved below. 3836 $object_types[] = 'attachment'; 3673 3837 } 3674 3838 3675 if ( $object_types ) { 3676 $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 ) ); 3839 // Save individual counts for each object type in term meta. 3840 foreach ( $object_types as $type ) { 3841 if ( ! empty( $term_count_meta[ $type ] ) ) { 3842 update_term_meta( $term->term_id, '_wp_object_count_' . $type, (int) $term_count_meta[ $type ] ); 3843 } 3677 3844 } 3678 3845 3846 update_term_meta( $term->term_id, '_wp_counted_object_types', $object_types ); 3847 3679 3848 /** This action is documented in wp-includes/taxonomy.php */ 3680 do_action( 'edit_term_taxonomy', $t erm, $taxonomy->name );3681 $wpdb->update( $wpdb->term_taxonomy, compact( 'count' ), array( 'term_taxonomy_id' => $t erm) );3849 do_action( 'edit_term_taxonomy', $tt_id, $taxonomy->name ); 3850 $wpdb->update( $wpdb->term_taxonomy, compact( 'count' ), array( 'term_taxonomy_id' => $tt_id ) ); 3682 3851 3683 3852 /** This action is documented in wp-includes/taxonomy.php */ 3684 do_action( 'edited_term_taxonomy', $t erm, $taxonomy->name );3853 do_action( 'edited_term_taxonomy', $tt_id, $taxonomy->name ); 3685 3854 } 3686 3855 } 3687 3856 -
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 { 599 599 $p = self::factory()->post->create(); 600 600 wp_set_object_terms( $p, $terms, 'wptests_tax' ); 601 601 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 602 610 $found = wp_get_object_terms( 603 611 $p, 'wptests_tax', array( 604 612 'update_term_meta_cache' => false, -
new file tests/phpunit/tests/term/wpGetTermObjectCount.php
diff --git a/tests/phpunit/tests/term/wpGetTermObjectCount.php b/tests/phpunit/tests/term/wpGetTermObjectCount.php new file mode 100644 index 0000000000..cdc49c67d1
- + 1 <?php 2 3 /** 4 * Class Tests_Term_wpGetTermCountForObjectType 5 * 6 * @group taxonomy 7 * @ticket 38280 8 */ 9 class Tests_Term_wpGetTermObjectCount extends WP_UnitTestCase { 10 11 /** 12 * Test when a taxonomy is invalid. 13 */ 14 public function test_wp_get_term_object_count_invalid_taxonomy() { 15 $actual = wp_get_term_object_count( 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_object_count_object_not_in_taxonomy() { 25 $this->assertWPError( wp_get_term_object_count( 0, 'category', 'page' ) ); 26 } 27 28 /** 29 * Test when an invalid term is passed. 30 */ 31 public function test_wp_get_term_object_count_invalid_term() { 32 $actual = wp_get_term_object_count( 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_object_count_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->assertEquals( 1, wp_get_term_object_count( $term_id, 'category', 'post' ) ); 54 55 $term_object = get_term( $term_id, 'category' ); 56 $this->assertEquals( 1, $term_object->count ); 57 58 wp_remove_object_terms( $post_id, array( $term_id ), 'category' ); 59 60 $this->assertEquals( 0, wp_get_term_object_count( $term_id, 'category', 'post' ) ); 61 62 $term_object = get_term( $term_id, 'category' ); 63 $this->assertEquals( 0, $term_object->count ); 64 } 65 66 /** 67 * Test when a taxonomy belongs to more than one object, and at least one object is not a post type. 68 */ 69 public function test_wp_get_term_object_count_non_post_type() { 70 register_taxonomy( 71 'wptests_tax', 72 array( 73 'user', 74 'foo', 75 ) 76 ); 77 78 $term_id = self::factory()->term->create( 79 array( 80 'taxonomy' => 'wptests_tax', 81 ) 82 ); 83 84 $foo_id = 99999; 85 86 wp_set_object_terms( $foo_id, $term_id, 'wptests_tax' ); 87 88 // 'foo' object doesn't have an update count callback, so the value is always 0. 89 $count = wp_get_term_object_count( $term_id, 'wptests_tax', 'foo' ); 90 $this->assertSame( 0, $count ); 91 } 92 93 /** 94 * Test when a taxonomy belongs to multiple object types. 95 */ 96 public function test_wp_get_term_object_count_multiple_object_types() { 97 register_post_type( 'wptests_cpt' ); 98 register_taxonomy( 99 'wptests_tax', 100 array( 101 'post', 102 'wptests_cpt', 103 ) 104 ); 105 106 $term_id = self::factory()->term->create( array( 'taxonomy' => 'wptests_tax' ) ); 107 $post_id = self::factory()->post->create( array( 'post_type' => 'post' ) ); 108 $custom_post_id = self::factory()->post->create( array( 'post_type' => 'wptests_cpt' ) ); 109 110 // When a term has no relationships, no meta should be stored. 111 $this->assertEquals( 0, wp_get_term_object_count( $term_id, 'wptests_tax', 'post' ) ); 112 $this->assertEquals( 0, wp_get_term_object_count( $term_id, 'wptests_tax', 'wptests_cpt' ) ); 113 $this->assertEmpty( get_term_meta( $term_id, '_wp_counted_object_types', true ) ); 114 $this->assertEmpty( get_term_meta( $term_id, '_wp_object_count_post', true ) ); 115 $this->assertEmpty( get_term_meta( $term_id, '_wp_object_count_wptests_cpt', true ) ); 116 117 wp_set_object_terms( $post_id, array( $term_id ), 'wptests_tax' ); 118 119 // Term has relationships, meta should be stored caching types counted and counts for each type > 0. 120 $this->assertEquals( 1, wp_get_term_object_count( $term_id, 'wptests_tax', 'post' ) ); 121 $this->assertEquals( 0, wp_get_term_object_count( $term_id, 'wptests_tax', 'wptests_cpt' ) ); 122 $this->assertEquals( array( 'post', 'wptests_cpt' ), get_term_meta( $term_id, '_wp_counted_object_types', true ) ); 123 $this->assertEquals( 1, get_term_meta( $term_id, '_wp_object_count_post', true ) ); 124 $this->assertEmpty( get_term_meta( $term_id, '_wp_object_count_wptests_cpt', true ) ); 125 126 wp_set_object_terms( $custom_post_id, array( $term_id ), 'wptests_tax' ); 127 128 $this->assertEquals( 1, wp_get_term_object_count( $term_id, 'wptests_tax', 'post' ) ); 129 $this->assertEquals( 1, wp_get_term_object_count( $term_id, 'wptests_tax', 'wptests_cpt' ) ); 130 $this->assertEquals( array( 'post', 'wptests_cpt' ), get_term_meta( $term_id, '_wp_counted_object_types', true ) ); 131 $this->assertEquals( 1, get_term_meta( $term_id, '_wp_object_count_post', true ) ); 132 $this->assertEquals( 1, get_term_meta( $term_id, '_wp_object_count_wptests_cpt', true ) ); 133 134 // Total count should be stored in the term's count property. 135 $term_object = get_term( $term_id, 'wptests_tax' ); 136 $this->assertEquals( 2, $term_object->count ); 137 138 wp_remove_object_terms( $custom_post_id, array( $term_id ), 'wptests_tax' ); 139 140 // Object count cache should be removed. 141 $this->assertEquals( 1, wp_get_term_object_count( $term_id, 'wptests_tax', 'post' ) ); 142 $this->assertEquals( 0, wp_get_term_object_count( $term_id, 'wptests_tax', 'wptests_cpt' ) ); 143 $this->assertEquals( array( 'post', 'wptests_cpt' ), get_term_meta( $term_id, '_wp_counted_object_types', true ) ); 144 $this->assertEquals( 1, get_term_meta( $term_id, '_wp_object_count_post', true ) ); 145 $this->assertEmpty( get_term_meta( $term_id, '_wp_object_count_wptests_cpt', true ) ); 146 147 wp_remove_object_terms( $post_id, array( $term_id ), 'wptests_tax' ); 148 149 $this->assertEquals( 0, wp_get_term_object_count( $term_id, 'wptests_tax', 'post' ) ); 150 $this->assertEquals( 0, wp_get_term_object_count( $term_id, 'wptests_tax', 'wptests_cpt' ) ); 151 $this->assertEquals( array( 'post', 'wptests_cpt' ), get_term_meta( $term_id, '_wp_counted_object_types', true ) ); 152 $this->assertEmpty( get_term_meta( $term_id, '_wp_object_count_post', true ) ); 153 $this->assertEmpty( get_term_meta( $term_id, '_wp_object_count_wptests_cpt', true ) ); 154 } 155 156 /** 157 * Term count must be generated on the fly (as for "legacy" terms). 158 */ 159 public function test_count_should_be_generated_for_legacy_terms() { 160 register_post_type( 'wptests_cpt' ); 161 register_taxonomy( 162 'wptests_tax', 163 array( 164 'post', 165 'wptests_cpt', 166 ) 167 ); 168 169 $t = self::factory()->term->create( array( 'taxonomy' => 'wptests_tax' ) ); 170 $p = self::factory()->post->create( array( 'post_type' => 'post' ) ); 171 172 wp_set_object_terms( $p, array( $t ), 'wptests_tax' ); 173 174 // Mimic "legacy" terms, which will not have the proper counts. 175 delete_term_meta( $t, '_wp_object_count_post' ); 176 delete_term_meta( $t, '_wp_counted_object_types' ); 177 178 $found = wp_get_term_object_count( $t, 'wptests_tax', 'post' ); 179 $this->assertSame( 1, $found ); 180 } 181 182 /** 183 * Test when a taxonomy belongs to multiple object types, one of which is attachments. 184 */ 185 public function test_wp_get_term_object_count_multiple_object_types_attachment() { 186 register_taxonomy( 187 'wptests_tax', 188 array( 189 'post', 190 'attachment', 191 ) 192 ); 193 194 $term_id = self::factory()->term->create( array( 'taxonomy' => 'wptests_tax' ) ); 195 $post_id = self::factory()->post->create( array( 'post_type' => 'post' ) ); 196 $attachment_id = self::factory()->attachment->create_upload_object( DIR_TESTDATA . '/images/canola.jpg', $post_id ); 197 198 // When a term has no relationships, no meta should be stored. 199 $this->assertEquals( 0, wp_get_term_object_count( $term_id, 'wptests_tax', 'post' ) ); 200 $this->assertEquals( 0, wp_get_term_object_count( $term_id, 'wptests_tax', 'attachment' ) ); 201 $this->assertEmpty( get_term_meta( $term_id, '_wp_counted_object_types', true ) ); 202 $this->assertEmpty( get_term_meta( $term_id, '_wp_object_count_post', true ) ); 203 $this->assertEmpty( get_term_meta( $term_id, '_wp_object_count_attachment', true ) ); 204 205 wp_set_object_terms( $post_id, array( $term_id ), 'wptests_tax' ); 206 207 // Term has relationships, meta should be stored caching types counted and counts for each type > 0. 208 $this->assertEquals( 1, wp_get_term_object_count( $term_id, 'wptests_tax', 'post' ) ); 209 $this->assertEquals( 0, wp_get_term_object_count( $term_id, 'wptests_tax', 'attachment' ) ); 210 $this->assertEquals( array( 'post', 'attachment' ), get_term_meta( $term_id, '_wp_counted_object_types', true ) ); 211 $this->assertEquals( 1, get_term_meta( $term_id, '_wp_object_count_post', true ) ); 212 $this->assertEmpty( get_term_meta( $term_id, '_wp_object_count_attachment', true ) ); 213 214 wp_set_object_terms( $attachment_id, array( $term_id ), 'wptests_tax' ); 215 216 $this->assertEquals( 1, wp_get_term_object_count( $term_id, 'wptests_tax', 'post' ) ); 217 $this->assertEquals( 1, wp_get_term_object_count( $term_id, 'wptests_tax', 'attachment' ) ); 218 $this->assertEquals( array( 'post', 'attachment' ), get_term_meta( $term_id, '_wp_counted_object_types', true ) ); 219 $this->assertEquals( 1, get_term_meta( $term_id, '_wp_object_count_post', true ) ); 220 $this->assertEquals( 1, get_term_meta( $term_id, '_wp_object_count_attachment', true ) ); 221 222 // Total count should be stored in the term's count property. 223 $term_object = get_term( $term_id, 'wptests_tax' ); 224 $this->assertEquals( 2, $term_object->count ); 225 226 wp_remove_object_terms( $post_id, array( $term_id ), 'wptests_tax' ); 227 228 // Object count cache should be removed. 229 $this->assertEquals( 0, wp_get_term_object_count( $term_id, 'wptests_tax', 'post' ) ); 230 $this->assertEquals( 1, wp_get_term_object_count( $term_id, 'wptests_tax', 'attachment' ) ); 231 $this->assertEmpty( get_term_meta( $term_id, '_wp_object_count_post', true ) ); 232 $this->assertEquals( 1, get_term_meta( $term_id, '_wp_object_count_attachment', true ) ); 233 wp_remove_object_terms( $attachment_id, array( $term_id ), 'wptests_tax' ); 234 235 $this->assertEquals( 0, wp_get_term_object_count( $term_id, 'wptests_tax', 'post' ) ); 236 $this->assertEquals( 0, wp_get_term_object_count( $term_id, 'wptests_tax', 'attachment' ) ); 237 $this->assertEquals( array( 'post', 'attachment' ), get_term_meta( $term_id, '_wp_counted_object_types', true ) ); 238 $this->assertEmpty( get_term_meta( $term_id, '_wp_object_count_post', true ) ); 239 $this->assertEmpty( get_term_meta( $term_id, '_wp_object_count_attachment', true ) ); 240 } 241 }