Make WordPress Core

Changeset 65281


Ignore:
Timestamp:
11/05/2026 05:27:20 PM (37 hours ago)
Author:
westonruter
Message:

Code Quality: Fix the nullCoalesce.property PHPStan error.

Since WP_Query::$max_num_pages is never null, the ?? 1 fallback in paginate_links() never applied, so the property is now read directly.

The property is also documented as a non-negative integer. This exposed that WP_Query::set_found_posts() could compute a negative page count when a found_posts filter returns a negative number, or when a filter injects a LIMIT into a query with a posts_per_page of -1, so that result is now clamped to zero. For the same reason the unreachable isset() fallback in WP_Sitemaps_Posts::get_max_num_pages() is removed, retiring its baseline entry.

Developed in ​https://github.com/WordPress/wordpress-develop/pull/15475.
Follow-up to r48476, r57648, r61454, r65192.

Props bgturner, westonruter.
See #65817.

Location:
trunk
Files:
1 deleted
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/phpstan.neon.dist

    r65265 r65281  
    4545        - tests/phpstan/baselines/method.unused.neon
    4646        - tests/phpstan/baselines/notIdentical.alwaysTrue.neon
    47         - tests/phpstan/baselines/nullCoalesce.property.neon
    4847        - tests/phpstan/baselines/offsetAccess.nonOffsetAccessible.neon
    4948        - tests/phpstan/baselines/offsetAccess.notFound.neon
  • trunk/src/wp-includes/class-wp-query.php

    r64794 r65281  
    187187         * @since 2.1.0
    188188         * @var int
     189         * @phpstan-var int<0, max>
    189190         */
    190191        public $max_num_pages = 0;
    … …  
    37333734
    37343735                if ( ! empty( $limits ) ) {
    3735                         $this->max_num_pages = (int) ceil( $this->found_posts / $query_vars['posts_per_page'] );
     3736                        $this->max_num_pages = max( 0, (int) ceil( $this->found_posts / (int) $query_vars['posts_per_page'] ) );
    37363737                }
    37373738        }
  • trunk/src/wp-includes/general-template.php

    r65219 r65281  
    49844984
    49854985        // Get max pages and current page out of the current query, if available.
    4986         $total   = $wp_query->max_num_pages ?? 1;
     4986        $total   = $wp_query->max_num_pages;
    49874987        $current = get_query_var( 'paged' ) ? (int) get_query_var( 'paged' ) : 1;
    49884988
  • trunk/src/wp-includes/sitemaps/providers/class-wp-sitemaps-posts.php

    r65043 r65281  
    210210
    211211                $min_num_pages = ( 'page' === $post_type && 'posts' === get_option( 'show_on_front' ) ) ? 1 : 0;
    212                 return isset( $query->max_num_pages ) ? max( $min_num_pages, $query->max_num_pages ) : 1;
     212                return max( $min_num_pages, $query->max_num_pages );
    213213        }
    214214
  • trunk/tests/phpstan/baselines/isset.property.neon

    r64868 r65281  
    180180                        path: ../../../src/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php
    181181                -
    182                         message: '#^Property WP_Query\:\:\$max_num_pages \(int\) in isset\(\) is not nullable\.$#'
    183                         identifier: isset.property
    184                         count: 1
    185                         path: ../../../src/wp-includes/sitemaps/providers/class-wp-sitemaps-posts.php
    186                 -
    187182                        message: '#^Property WP_Site\:\:\$domain \(string\) in isset\(\) is not nullable\.$#'
    188183                        identifier: isset.property
  • trunk/tests/phpunit/tests/query/noFoundRows.php

    r54090 r65281  
    103103                $this->assertSame( 1, $q->found_posts );
    104104        }
     105
     106        /**
     107         * Ensures {@see WP_Query::$max_num_pages} is not negative when the
     108         * {@see 'found_posts'} filter returns a negative count.
     109         *
     110         * @ticket 65817
     111         */
     112        public function test_max_num_pages_is_not_negative_when_found_posts_is_filtered_negative() {
     113                self::factory()->post->create_many( 3 );
     114
     115                add_filter(
     116                        'found_posts',
     117                        static function (): int {
     118                                return -5;
     119                        }
     120                );
     121
     122                $q = new WP_Query(
     123                        array(
     124                                'post_type'      => 'post',
     125                                'posts_per_page' => 2,
     126                        )
     127                );
     128
     129                $this->assertSame( -5, $q->found_posts, 'Expected the filtered found_posts value to be kept.' );
     130                $this->assertSame( 0, $q->max_num_pages, 'Expected max_num_pages to be clamped to zero.' );
     131        }
     132
     133        /**
     134         * Ensures {@see WP_Query::$max_num_pages} is not negative when a
     135         * {@see 'post_limits'} filter injects a non-empty `LIMIT` clause on a
     136         * `posts_per_page = -1` query, which otherwise never runs the found-rows
     137         * calculation and skips the `max_num_pages` division entirely.
     138         *
     139         * @ticket 65817
     140         */
     141        public function test_max_num_pages_is_not_negative_when_limits_are_filtered_with_postsperpage_minus1() {
     142                self::factory()->post->create_many( 3 );
     143
     144                add_filter(
     145                        'post_limits',
     146                        static function (): string {
     147                                return 'LIMIT 0, 2';
     148                        }
     149                );
     150
     151                $q = new WP_Query(
     152                        array(
     153                                'post_type'      => 'post',
     154                                'posts_per_page' => -1,
     155                        )
     156                );
     157
     158                $this->assertIsString( $q->request );
     159                $this->assertStringContainsString( 'SQL_CALC_FOUND_ROWS', $q->request, 'Expected the filtered LIMIT to trigger the found-rows query.' );
     160                $this->assertSame( 3, $q->found_posts, 'Expected found_posts to reflect all matching rows.' );
     161                $this->assertSame( 0, $q->max_num_pages, 'Expected max_num_pages to be clamped to zero.' );
     162        }
    105163}
Note: See TracChangeset for help on using the changeset viewer.