Commit bb495884aa for wordpress.org
commit bb495884aac290907a606c381257d74f8056e5d7
Author: westonruter <westonruter@git.wordpress.org>
Date: Tue Nov 10 04:52:57 2026 +0000
REST API: Accept a post object in global styles `prepare_links()`.
The `WP_REST_Global_Styles_Controller::prepare_links()` override took a post ID while the parent method takes a `WP_Post`, which PHPStan reports as an incompatible child parameter type. It now accepts either, and `prepare_item_for_response()` continues to pass the ID so that subclasses overriding the method and expecting an ID are not broken. The method also gains a precise return type, which in turn required narrowing the types it relies on: REST URLs are documented as non-empty, and `count` is declared as a non-negative link attribute.
The `WP_Query::$found_posts` count is now clamped to zero, both from the `found_posts_query` query and after the `found_posts` filter, since a negative post count has no valid meaning and would otherwise surface as a negative revisions count. In r65281 only `$max_num_pages` was clamped, and its test asserted a negative `found_posts` merely as a precondition.
Developed in https://github.com/WordPress/wordpress-develop/pull/15826.
Follow-up to r48328, r52051, r56082, r65281.
Props westonruter, huzaifaalmesbah, dpknauss.
See #65817.
Built from https://develop.svn.wordpress.org/trunk@65369
git-svn-id: http://core.svn.wordpress.org/trunk@64508 1a063a9b-81f0-0310-95a4-ce76da25c4cd
diff --git a/wp-includes/class-wp-query.php b/wp-includes/class-wp-query.php
index 2b593c0add..998fe477d4 100644
--- a/wp-includes/class-wp-query.php
+++ b/wp-includes/class-wp-query.php
@@ -178,6 +178,7 @@ class WP_Query {
*
* @since 2.1.0
* @var int
+ * @phpstan-var int<0, max>
*/
public $found_posts = 0;
@@ -3709,7 +3710,7 @@ class WP_Query {
*/
$found_posts_query = apply_filters_ref_array( 'found_posts_query', array( 'SELECT FOUND_ROWS()', &$this ) );
- $this->found_posts = (int) $wpdb->get_var( $found_posts_query );
+ $this->found_posts = max( 0, (int) $wpdb->get_var( $found_posts_query ) );
} else {
if ( is_array( $this->posts ) ) {
$this->found_posts = count( $this->posts );
@@ -3726,11 +3727,15 @@ class WP_Query {
* Filters the number of found posts for the query.
*
* @since 2.1.0
+ * @since 7.2.0 A negative return value is clamped to zero, as is a negative count from the
+ * {@see 'found_posts_query'} query before it is passed to this filter.
*
* @param int $found_posts The number of posts found.
* @param WP_Query $query The WP_Query instance (passed by reference).
+ *
+ * @phpstan-param int<0, max> $found_posts
*/
- $this->found_posts = (int) apply_filters_ref_array( 'found_posts', array( $this->found_posts, &$this ) );
+ $this->found_posts = max( 0, (int) apply_filters_ref_array( 'found_posts', array( $this->found_posts, &$this ) ) );
if ( ! empty( $limits ) ) {
$this->max_num_pages = max( 0, (int) ceil( $this->found_posts / (int) $query_vars['posts_per_page'] ) );
diff --git a/wp-includes/rest-api.php b/wp-includes/rest-api.php
index f40021f4d0..f8d5e0a383 100644
--- a/wp-includes/rest-api.php
+++ b/wp-includes/rest-api.php
@@ -531,6 +531,8 @@ function rest_get_url_prefix() {
* @param string $path Optional. REST route. Default '/'.
* @param string $scheme Optional. Sanitization scheme. Default 'rest'.
* @return string Full URL to the endpoint.
+ *
+ * @phpstan-return non-falsy-string
*/
function get_rest_url( $blog_id = null, $path = '/', $scheme = 'rest' ) {
if ( empty( $path ) ) {
@@ -589,6 +591,8 @@ function get_rest_url( $blog_id = null, $path = '/', $scheme = 'rest' ) {
* @param string $path REST route.
* @param int|null $blog_id Blog ID.
* @param string $scheme Sanitization scheme.
+ *
+ * @phpstan-param non-falsy-string $url
*/
return apply_filters( 'rest_url', $url, $path, $blog_id, $scheme );
}
@@ -603,6 +607,8 @@ function get_rest_url( $blog_id = null, $path = '/', $scheme = 'rest' ) {
* @param string $path Optional. REST route. Default empty.
* @param string $scheme Optional. Sanitization scheme. Default 'rest'.
* @return string Full URL to the endpoint.
+ *
+ * @phpstan-return non-falsy-string
*/
function rest_url( $path = '', $scheme = 'rest' ) {
return get_rest_url( null, $path, $scheme );
diff --git a/wp-includes/rest-api/class-wp-rest-response.php b/wp-includes/rest-api/class-wp-rest-response.php
index f86976e94d..7ec735a8f7 100644
--- a/wp-includes/rest-api/class-wp-rest-response.php
+++ b/wp-includes/rest-api/class-wp-rest-response.php
@@ -24,6 +24,7 @@
* @phpstan-type Link_Attributes array{
* embeddable?: bool,
* targetHints?: Target_Hints,
+ * count?: int<0, max>,
* ...<string, mixed>
* }
* @phpstan-type Link array{
@@ -34,6 +35,7 @@
* href: non-falsy-string,
* embeddable?: bool,
* targetHints?: array<string, mixed>, // Not narrowed like in Link_Attributes, since PHPStan merges this shape with its list form in add_links(). The merged attributes passed to add_link() there are only accepted while they include `mixed`; with the nested shape, the call would be an argument.type error even when `mixed` is not checked.
+ * count?: int<0, max>, // Declared rather than left to the unsealed keys, since that merge also drops them: an attribute not named here is rejected when a caller passes a precise shape.
* ...<string, mixed>
* }
* @phpstan-type Curie array{
diff --git a/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php b/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php
index e9f3b281ed..daa83ba352 100644
--- a/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php
+++ b/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php
@@ -359,6 +359,7 @@ class WP_REST_Global_Styles_Controller extends WP_REST_Posts_Controller {
$response = rest_ensure_response( $data );
if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
+ // The ID is passed on purpose, since subclasses that override prepare_links() may expect an ID rather than a post object.
$links = $this->prepare_links( $post->ID );
// Only return resolved URIs for get requests to user theme JSON.
@@ -387,11 +388,19 @@ class WP_REST_Global_Styles_Controller extends WP_REST_Posts_Controller {
*
* @since 5.9.0
* @since 6.3.0 Adds revisions count and rest URL href to version-history.
+ * @since 7.2.0 Accepts a post object, matching the parent method.
*
- * @param int $id ID.
- * @return array Links for the given post.
+ * @param WP_Post|int $post Post object or ID.
+ * @return array<string, array<string, string|int>> Links for the given post.
+ *
+ * @phpstan-return array{
+ * self: array{ href: non-falsy-string },
+ * about: array{ href: non-falsy-string },
+ * version-history?: array{ href: non-falsy-string, count: int<0, max> },
+ * }
*/
- protected function prepare_links( $id ) {
+ protected function prepare_links( $post ) {
+ $id = $post instanceof WP_Post ? $post->ID : $post;
$base = sprintf( '%s/%s', $this->namespace, $this->rest_base );
$links = array(
@@ -404,7 +413,7 @@ class WP_REST_Global_Styles_Controller extends WP_REST_Posts_Controller {
);
if ( post_type_supports( $this->post_type, 'revisions' ) ) {
- $revisions = wp_get_latest_revision_id_and_total_count( $id );
+ $revisions = wp_get_latest_revision_id_and_total_count( $post );
$revisions_count = ! is_wp_error( $revisions ) ? $revisions['count'] : 0;
$revisions_base = sprintf( '/%s/%d/revisions', $base, $id );
$links['version-history'] = array(
diff --git a/wp-includes/revision.php b/wp-includes/revision.php
index 179a32c283..77b0b6425e 100644
--- a/wp-includes/revision.php
+++ b/wp-includes/revision.php
@@ -716,6 +716,8 @@ function wp_get_post_revisions( $post = 0, $args = null ) {
* @type int $latest_id The latest revision post ID or 0 if no revisions exist.
* @type int $count The total count of revisions for the given post.
* }
+ *
+ * @phpstan-return array{ latest_id: int, count: int<0, max> }|WP_Error
*/
function wp_get_latest_revision_id_and_total_count( $post = 0 ) {
$post = get_post( $post );
diff --git a/wp-includes/version.php b/wp-includes/version.php
index eb651d0c42..b020f0e145 100644
--- a/wp-includes/version.php
+++ b/wp-includes/version.php
@@ -16,7 +16,7 @@
*
* @global string $wp_version
*/
-$wp_version = '7.2-beta4-65368';
+$wp_version = '7.2-beta4-65369';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.