Make WordPress Core

Ticket #24386: 24386.diff

File 24386.diff, 13.1 KB (added by david.binda, 4 years ago)
  • src/wp-includes/class-walker-pad-term-counts.php

     
     1<?php
     2/**
     3 * Taxonomy API: Walker_Pad_Term_Counts class
     4 *
     5 * @package WordPress
     6 * @subpackage Template
     7 * @since 5.7.0
     8 */
     9
     10/**
     11 * Core class used to pad term counts.
     12 *
     13 * @since 5.7.0
     14 *
     15 * @see Walker
     16 */
     17class Walker_Pad_Term_Counts {
     18
     19        /**
     20         * Holds all the ancestors of the current term.
     21         *
     22         * @since 5.7.0
     23         * @var array
     24         */
     25        public $ancestors = array();
     26
     27        /**
     28         * Adds new term to the ancestors list.
     29         *
     30         * @since 5.7.0
     31         *
     32         * @param object $term The data object.
     33         */
     34        public function start_lvl( &$term ) {
     35                array_push( $this->ancestors, $term );
     36        }
     37
     38        /**
     39         * Removes the last term from the ancestors list.
     40         *
     41         * @since 5.7.0
     42         *
     43         * @param object $term The data object.
     44         */
     45        public function end_lvl( $term ) {
     46                array_pop( $this->ancestors );
     47        }
     48
     49        /**
     50         * Add terms count to all it's ancestors.
     51         *
     52         * @since 5.7.0
     53         *
     54         * @param object $term The data object.
     55         */
     56        public function count_term( $term ) {
     57                foreach ( $this->ancestors as &$ancestor ) {
     58                        $ancestor->count += $term->count;
     59                }
     60        }
     61
     62        /**
     63         * Traverse terms to add descendants counts to their ancestors.
     64         *
     65         * This method should not be called directly, use the walk() method instead.
     66         *
     67         * @since 5.7.0
     68         *
     69         * @param object $term           Data object.
     70         * @param array  $children_terms List of terms to continue traversing (passed by reference).
     71         */
     72        public function process_term( $term, &$children_terms ) {
     73                if ( ! $term ) {
     74                        return;
     75                }
     76
     77                $id = $term->term_id;
     78
     79                $this->count_term( $term );
     80
     81                // Descend only when there are children for this term.
     82                if ( isset( $children_terms[ $id ] ) ) {
     83
     84                        foreach ( $children_terms[ $id ] as $child ) {
     85
     86                                if ( ! isset( $newlevel ) ) {
     87                                        $newlevel = true;
     88                                        // Start the new level.
     89                                        $this->start_lvl( $term );
     90                                }
     91                                $this->process_term( $child, $children_terms );
     92                        }
     93                        unset( $children_terms[ $id ] );
     94                }
     95
     96                if ( isset( $newlevel ) && $newlevel ) {
     97                        // End the child level.
     98                        $this->end_lvl( $term );
     99                }
     100        }
     101
     102        /**
     103         * Process the array of terms hierarchically.
     104         *
     105         * Does not assume any existing order of terms.
     106         *
     107         * @since 5.7.0
     108         *
     109         * @param array $terms An array of terms.
     110         */
     111        public function walk( $terms, $debug = false ) {
     112                // Invalid parameter or nothing to walk.
     113                if ( empty( $terms ) ) {
     114                        return;
     115                }
     116
     117                /*
     118                 * Separate terms into two buckets: top level and children terms.
     119                 * Children_terms is two dimensional array, eg.
     120                 * Children_terms[10][] contains all sub-terms whose parent is 10.
     121                 */
     122                $top_level_terms = array();
     123                $children_terms  = array();
     124                foreach ( $terms as $t ) {
     125                        if ( empty( $t->parent ) ) {
     126                                $top_level_terms[] = $t;
     127                        } else {
     128                                $children_terms[ $t->parent ][] = $t;
     129                        }
     130                }
     131
     132                /**
     133                 * In case there are no children, there is nothing to do here.
     134                 */
     135                if ( empty( $children_terms ) ) {
     136                        return;
     137                }
     138
     139                /*
     140                 * When none of the terms is top level.
     141                 * Assume the terms with no parent in among terms passed to the method are top level terms.
     142                 */
     143                if ( empty( $top_level_terms ) ) {
     144                        $all_term_ids = wp_list_pluck( $terms, 'term_id' );
     145                        foreach ( $children_terms as $parent => $ts ) {
     146                                foreach ( $ts as $key => $t ) {
     147                                        if ( ! in_array( $parent, $all_term_ids, true ) ) {
     148                                                $top_level_terms[] = $t;
     149                                                unset( $children_terms[ $parent ][ $key ] );
     150                                        }
     151                                }
     152                        }
     153                }
     154
     155                // Process the terms.
     156                foreach ( $top_level_terms as $t ) {
     157                        $this->process_term( $t, $children_terms );
     158                }
     159        }
     160}
  • src/wp-includes/taxonomy.php

    Property changes on: src/wp-includes/class-walker-pad-term-counts.php
    ___________________________________________________________________
    Added: svn:eol-style
    ## -0,0 +1 ##
    +native
    \ No newline at end of property
    Added: svn:executable
    ## -0,0 +1 ##
    +*
    \ No newline at end of property
     
    37383738 * @access private
    37393739 * @since 2.3.0
    37403740 *
    3741  * @global wpdb $wpdb WordPress database abstraction object.
    3742  *
    37433741 * @param array  $terms    List of term objects (passed by reference).
    37443742 * @param string $taxonomy Term context.
    37453743 */
    37463744function _pad_term_counts( &$terms, $taxonomy ) {
    3747         global $wpdb;
    3748 
    37493745        // This function only works for hierarchical taxonomies like post categories.
    37503746        if ( ! is_taxonomy_hierarchical( $taxonomy ) ) {
    37513747                return;
     
    37573753                return;
    37583754        }
    37593755
    3760         $term_items  = array();
    3761         $terms_by_id = array();
    3762         $term_ids    = array();
    3763 
    3764         foreach ( (array) $terms as $key => $term ) {
    3765                 $terms_by_id[ $term->term_id ]       = & $terms[ $key ];
    3766                 $term_ids[ $term->term_taxonomy_id ] = $term->term_id;
    3767         }
    3768 
    3769         // Get the object and term IDs and stick them in a lookup table.
    3770         $tax_obj      = get_taxonomy( $taxonomy );
    3771         $object_types = esc_sql( $tax_obj->object_type );
    3772         $results      = $wpdb->get_results( "SELECT object_id, term_taxonomy_id FROM $wpdb->term_relationships INNER JOIN $wpdb->posts ON object_id = ID WHERE term_taxonomy_id IN (" . implode( ',', array_keys( $term_ids ) ) . ") AND post_type IN ('" . implode( "', '", $object_types ) . "') AND post_status = 'publish'" );
    3773 
    3774         foreach ( $results as $row ) {
    3775                 $id = $term_ids[ $row->term_taxonomy_id ];
    3776 
    3777                 $term_items[ $id ][ $row->object_id ] = isset( $term_items[ $id ][ $row->object_id ] ) ? ++$term_items[ $id ][ $row->object_id ] : 1;
    3778         }
    3779 
    3780         // Touch every ancestor's lookup row for each post in each term.
    3781         foreach ( $term_ids as $term_id ) {
    3782                 $child     = $term_id;
    3783                 $ancestors = array();
    3784                 while ( ! empty( $terms_by_id[ $child ] ) && $parent = $terms_by_id[ $child ]->parent ) {
    3785                         $ancestors[] = $child;
    3786 
    3787                         if ( ! empty( $term_items[ $term_id ] ) ) {
    3788                                 foreach ( $term_items[ $term_id ] as $item_id => $touches ) {
    3789                                         $term_items[ $parent ][ $item_id ] = isset( $term_items[ $parent ][ $item_id ] ) ? ++$term_items[ $parent ][ $item_id ] : 1;
    3790                                 }
    3791                         }
    3792 
    3793                         $child = $parent;
    3794 
    3795                         if ( in_array( $parent, $ancestors, true ) ) {
    3796                                 break;
    3797                         }
    3798                 }
    3799         }
    3800 
    3801         // Transfer the touched cells.
    3802         foreach ( (array) $term_items as $id => $items ) {
    3803                 if ( isset( $terms_by_id[ $id ] ) ) {
    3804                         $terms_by_id[ $id ]->count = count( $items );
    3805                 }
    3806         }
     3756        $walker = new Walker_Pad_Term_Counts();
     3757        $walker->walk( $terms );
    38073758}
    38083759
    38093760/**
  • src/wp-settings.php

     
    213213require ABSPATH . WPINC . '/class-wp-term.php';
    214214require ABSPATH . WPINC . '/class-wp-term-query.php';
    215215require ABSPATH . WPINC . '/class-wp-tax-query.php';
     216require ABSPATH . WPINC . '/class-walker-pad-term-counts.php';
    216217require ABSPATH . WPINC . '/update.php';
    217218require ABSPATH . WPINC . '/canonical.php';
    218219require ABSPATH . WPINC . '/shortcodes.php';
  • tests/phpunit/tests/taxonomy/padTermCounts.php

     
     1<?php
     2
     3/**
     4 * @group taxonomy
     5 */
     6class Tests_Taxonomy_Pad_Term_Counts extends WP_UnitTestCase {
     7
     8        public function test_no_child_terms() {
     9                $terms = self::factory()->term->create_many( 2, array( 'taxonomy' => 'category' ) );
     10
     11                $terms = array_map( 'get_term', $terms );
     12                _pad_term_counts( $terms, 'category' );
     13
     14                foreach( $terms as $term ) {
     15                        $this->assertEquals( 0, $term->count );
     16                }
     17        }
     18
     19        public function test_single_post_in_a_category() {
     20                $terms           = array();
     21                $terms['parent'] = self::create_term_with_that_many_posts( 1, array( 'taxonomy' => 'category' ) );
     22
     23                $terms = array_map( 'get_term', $terms );
     24                _pad_term_counts( $terms, 'category' );
     25
     26                $this->assertEquals( 1, $terms['parent']->count );
     27        }
     28
     29        public function test_single_child() {
     30                $terms = array();
     31                $terms['parent'] = self::factory()->term->create( array( 'taxonomy' => 'category' ) );
     32                $terms['child']  = self::create_term_with_that_many_posts( 1, array( 'taxonomy' => 'category', 'parent' => $terms['parent'] ) );
     33
     34                $terms = array_map( 'get_term', $terms );
     35                _pad_term_counts( $terms, 'category' );
     36
     37                foreach( $terms as $term ) {
     38                        $this->assertEquals( 1, $term->count );
     39                }
     40        }
     41
     42        public function test_single_grandchild() {
     43                $terms               = array();
     44                $terms['parent']     = self::factory()->term->create( array( 'taxonomy' => 'category' ) );
     45                $terms['child']      = self::factory()->term->create( array( 'taxonomy' => 'category', 'parent' => $terms['parent'] ) );
     46                $terms['grandchild'] = self::create_term_with_that_many_posts( 1, array( 'taxonomy' => 'category', 'parent' => $terms['child'] ) );
     47
     48                $terms = array_map( 'get_term', $terms );
     49                _pad_term_counts( $terms, 'category' );
     50
     51                foreach( $terms as $term ) {
     52                        $this->assertEquals( 1, $term->count );
     53                }
     54        }
     55
     56        public function test_both_parent_and_grandchild() {
     57                $terms = array();
     58                $terms['parent']      = self::create_term_with_that_many_posts( 1, array( 'taxonomy' => 'category', 'parent' => 0 ) );
     59                $terms['child']       = self::factory()->term->create( array( 'taxonomy' => 'category', 'parent' => $terms['parent'] ) );
     60                $terms['grandchild']  = self::create_term_with_that_many_posts( 1, array( 'taxonomy' => 'category', 'parent' => $terms['child'] ) );
     61
     62                $terms = array_map( 'get_term', $terms );
     63                _pad_term_counts( $terms, 'category' );
     64
     65                $this->assertEquals( 2, $terms['parent']->count );
     66                $this->assertEquals( 1, $terms['child']->count );
     67                $this->assertEquals( 1, $terms['grandchild']->count );
     68        }
     69
     70        public function test_many_children_and_grandchildren() {
     71                $terms = array();
     72                $terms['parent']        = self::create_term_with_that_many_posts( 1, array( 'taxonomy' => 'category', 'parent' => 0 ) );
     73
     74                $terms['child_0_posts'] = self::factory()->term->create( array( 'taxonomy' => 'category', 'parent' => $terms['parent'] ) );
     75                $terms['grandchild_3_posts'] = self::create_term_with_that_many_posts( 3, array( 'taxonomy' => 'category', 'parent' => $terms['child_0_posts'] ) );
     76
     77                $terms['child_2_posts'] = self::create_term_with_that_many_posts( 2, array( 'taxonomy' => 'category', 'parent' => $terms['parent'] ) );
     78                $terms['grandchild_2_posts'] = self::create_term_with_that_many_posts( 2, array( 'taxonomy' => 'category', 'parent' => $terms['child_2_posts'] ) );
     79                $terms['grandchild_1_posts'] = self::create_term_with_that_many_posts( 1, array( 'taxonomy' => 'category', 'parent' => $terms['child_2_posts'] ) );
     80
     81                $terms['grandgrandchild_1_posts'] = self::create_term_with_that_many_posts( 1, array( 'taxonomy' => 'category', 'parent' => $terms['grandchild_1_posts'] ) );
     82
     83                $terms = array_map( 'get_term', $terms );
     84                _pad_term_counts( $terms, 'category' );
     85
     86                $this->assertEquals( 10, $terms['parent']->count );
     87                $this->assertEquals( 3, $terms['child_0_posts']->count );
     88                $this->assertEquals( 3, $terms['grandchild_3_posts']->count );
     89                $this->assertEquals( 6, $terms['child_2_posts']->count );
     90                $this->assertEquals( 2, $terms['grandchild_2_posts']->count );
     91                $this->assertEquals( 2, $terms['grandchild_1_posts']->count );
     92                $this->assertEquals( 1, $terms['grandgrandchild_1_posts']->count );
     93        }
     94
     95        public function test_no_top_level_terms() {
     96                $terms = array();
     97                $parent = self::factory()->term->create( array( 'taxonomy' => 'category', 'parent' => 0 ) );
     98                $terms['child_0_posts'] = self::factory()->term->create( array( 'taxonomy' => 'category', 'parent' => $parent ) );
     99                $terms['grandchild_3_posts'] = self::create_term_with_that_many_posts( 3, array( 'taxonomy' => 'category', 'parent' => $terms['child_0_posts'] ) );
     100
     101                $terms['child_2_posts'] = self::create_term_with_that_many_posts( 2, array( 'taxonomy' => 'category', 'parent' => $parent ) );
     102                $terms['grandchild_2_posts'] = self::create_term_with_that_many_posts( 2, array( 'taxonomy' => 'category', 'parent' => $terms['child_2_posts'] ) );
     103
     104                $terms = array_map( 'get_term', $terms );
     105                _pad_term_counts( $terms, 'category' );
     106
     107                $parent = get_term( $parent );
     108                $this->assertEquals( 0, $parent->count );
     109                $this->assertEquals( 3, $terms['child_0_posts']->count );
     110                $this->assertEquals( 3, $terms['grandchild_3_posts']->count );
     111                $this->assertEquals( 4, $terms['child_2_posts']->count );
     112                $this->assertEquals( 2, $terms['grandchild_2_posts']->count );
     113        }
     114
     115        private static function create_term_with_that_many_posts( $posts_no, $args ) {
     116                $term  = self::factory()->term->create( $args );
     117                if ( 0 < $posts_no ) {
     118                        $posts = self::factory()->post->create_many( $posts_no, array( 'post_category' => (array) $term ) );
     119                }
     120                return $term;
     121        }
     122}