From 518ece6648105ab4b58cab05fdd38b7481454732 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 23 Mar 2026 12:41:09 +0100 Subject: [PATCH 01/16] Document object shape returned by WP_REST_Posts_Controller::prepare_item_for_database() --- .../class-wp-rest-posts-controller.php | 43 ++++++++++++++++--- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php index 0ab54a3a0d384..52bf14343849f 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php @@ -13,6 +13,26 @@ * @since 4.7.0 * * @see WP_REST_Controller + * + * @phpstan-type PreparedPost object{ + * ID?: int, + * post_title?: string, + * post_content?: string, + * post_excerpt?: string, + * post_type: string, + * post_status?: string, + * post_date?: string|null, + * post_date_gmt?: string|null, + * edit_date?: true, + * post_name?: string, + * post_author?: int, + * post_parent?: int, + * menu_order?: int, + * comment_status?: string, + * ping_status?: string, + * page_template?: null, + * meta_input?: array, + * } */ class WP_REST_Posts_Controller extends WP_REST_Controller { /** @@ -1283,13 +1303,15 @@ protected function prepare_date_response( $date_gmt, $date = null ) { * @since 4.7.0 * * @param WP_REST_Request $request Request object. - * @return stdClass|WP_Error Post object or WP_Error. + * @return object|WP_Error Post object or WP_Error. + * @phpstan-return PreparedPost|WP_Error */ protected function prepare_item_for_database( $request ) { $prepared_post = new stdClass(); $current_status = ''; // Post ID. + $existing_post = null; if ( isset( $request['id'] ) ) { $existing_post = $this->get_post( $request['id'] ); if ( is_wp_error( $existing_post ) ) { @@ -1330,15 +1352,22 @@ protected function prepare_item_for_database( $request ) { } // Post type. - if ( empty( $request['id'] ) ) { + if ( ! $existing_post ) { // Creating new post, use default type for the controller. $prepared_post->post_type = $this->post_type; } else { // Updating a post, use previous type. - $prepared_post->post_type = get_post_type( $request['id'] ); + $prepared_post->post_type = $existing_post->post_type; } $post_type = get_post_type_object( $prepared_post->post_type ); + if ( ! $post_type ) { + return new WP_Error( + 'rest_post_invalid_type', + __( 'Invalid post type.' ), + array( 'status' => 400 ) + ); + } // Post status. if ( @@ -1357,7 +1386,7 @@ protected function prepare_item_for_database( $request ) { // Post date. if ( ! empty( $schema['properties']['date'] ) && ! empty( $request['date'] ) ) { - $current_date = isset( $prepared_post->ID ) ? get_post( $prepared_post->ID )->post_date : false; + $current_date = $existing_post ? $existing_post->post_date : false; $date_data = rest_get_date_with_gmt( $request['date'] ); if ( ! empty( $date_data ) && $current_date !== $date_data[0] ) { @@ -1365,7 +1394,7 @@ protected function prepare_item_for_database( $request ) { $prepared_post->edit_date = true; } } elseif ( ! empty( $schema['properties']['date_gmt'] ) && ! empty( $request['date_gmt'] ) ) { - $current_date = isset( $prepared_post->ID ) ? get_post( $prepared_post->ID )->post_date_gmt : false; + $current_date = $existing_post ? $existing_post->post_date_gmt : false; $date_data = rest_get_date_with_gmt( $request['date_gmt'], true ); if ( ! empty( $date_data ) && $current_date !== $date_data[1] ) { @@ -1423,7 +1452,7 @@ protected function prepare_item_for_database( $request ) { ); } - if ( ! empty( $prepared_post->ID ) && is_sticky( $prepared_post->ID ) ) { + if ( $existing_post && is_sticky( $existing_post->ID ) ) { return new WP_Error( 'rest_invalid_field', __( 'A sticky post can not be password protected.' ), @@ -1434,7 +1463,7 @@ protected function prepare_item_for_database( $request ) { } if ( ! empty( $schema['properties']['sticky'] ) && ! empty( $request['sticky'] ) ) { - if ( ! empty( $prepared_post->ID ) && post_password_required( $prepared_post->ID ) ) { + if ( $existing_post && post_password_required( $prepared_post->ID ) ) { return new WP_Error( 'rest_invalid_field', __( 'A password protected post can not be set to sticky.' ), From 3607c42f51940070005720acc19192062c0e02f5 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 23 Mar 2026 12:51:20 +0100 Subject: [PATCH 02/16] Make PreparedPost object shape writable by intersecting with stdClass PHPStan treats object{} shape properties as read-only by default. Since prepare_item_for_database() returns a stdClass that callers mutate (e.g. setting post_type, post_name), the PreparedPost type needs to be intersected with stdClass to indicate properties are writable. See: https://github.com/phpstan/phpstan/discussions/10079 See: https://phpstan.org/writing-php-code/phpdoc-types Co-Authored-By: Claude Opus 4.6 (1M context) --- .../rest-api/endpoints/class-wp-rest-posts-controller.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php index 52bf14343849f..bbab97b9b83c7 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php @@ -32,7 +32,7 @@ * ping_status?: string, * page_template?: null, * meta_input?: array, - * } + * }&stdClass */ class WP_REST_Posts_Controller extends WP_REST_Controller { /** From 340ad6d9feca48948c09af72d86108869c8406b4 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 23 Mar 2026 13:18:27 +0100 Subject: [PATCH 03/16] Short-circuit with WP_Error when returned by prepare_item_for_database --- .../class-wp-rest-attachments-controller.php | 20 ++++++++++++++----- .../class-wp-rest-autosaves-controller.php | 6 +++++- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php index 7b207938412f8..6d84193665b15 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php @@ -355,6 +355,9 @@ protected function insert_attachment( $request ) { } $attachment = $this->prepare_item_for_database( $request ); + if ( is_wp_error( $attachment ) ) { + return $attachment; + } $attachment->post_mime_type = $type; $attachment->guid = $url; @@ -377,10 +380,6 @@ protected function insert_attachment( $request ) { // $post_parent is inherited from $attachment['post_parent']. $id = wp_insert_attachment( wp_slash( (array) $attachment ), $file, 0, true, false ); - if ( trim( $alt ) ) { - update_post_meta( $id, '_wp_attachment_image_alt', sanitize_text_field( $alt ) ); - } - if ( is_wp_error( $id ) ) { if ( 'db_update_error' === $id->get_error_code() ) { $id->add_data( array( 'status' => 500 ) ); @@ -391,6 +390,10 @@ protected function insert_attachment( $request ) { return $id; } + if ( trim( $alt ) ) { + update_post_meta( $id, '_wp_attachment_image_alt', sanitize_text_field( $alt ) ); + } + $attachment = get_post( $id ); /** @@ -766,7 +769,11 @@ public function edit_media_item( $request ) { $original_attachment_post = get_post( $attachment_id ); // Check request fields and assign default values. - $new_attachment_post = $this->prepare_item_for_database( $request ); + $new_attachment_post = $this->prepare_item_for_database( $request ); + if ( is_wp_error( $new_attachment_post ) ) { + return $new_attachment_post; + } + $new_attachment_post->post_mime_type = $saved['mime-type']; $new_attachment_post->guid = $uploads['url'] . "/$filename"; @@ -869,6 +876,9 @@ public function edit_media_item( $request ) { */ protected function prepare_item_for_database( $request ) { $prepared_attachment = parent::prepare_item_for_database( $request ); + if ( is_wp_error( $prepared_attachment ) ) { + return $prepared_attachment; + } // Attachment caption (post_excerpt internally). if ( isset( $request['caption'] ) ) { diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php index f0cec04f191f8..9b471774bd203 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php @@ -220,7 +220,11 @@ public function create_item( $request ) { return $post; } - $prepared_post = $this->parent_controller->prepare_item_for_database( $request ); + $prepared_post = $this->parent_controller->prepare_item_for_database( $request ); + if ( is_wp_error( $prepared_post ) ) { + return $prepared_post; + } + $prepared_post->ID = $post->ID; $user_id = get_current_user_id(); From e5edb479cc1b256b11c8341647f773ff9bcef862 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 23 Mar 2026 16:40:00 +0100 Subject: [PATCH 04/16] Reuse initial valid post check for the post-before --- .../endpoints/class-wp-rest-posts-controller.php | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php index bbab97b9b83c7..782ef0424b10e 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php @@ -962,14 +962,12 @@ public function update_item_permissions_check( $request ) { * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. */ public function update_item( $request ) { - $valid_check = $this->get_post( $request['id'] ); - if ( is_wp_error( $valid_check ) ) { - return $valid_check; + $post_before = $this->get_post( $request['id'] ); + if ( is_wp_error( $post_before ) ) { + return $post_before; } - $post_before = get_post( $request['id'] ); - $post = $this->prepare_item_for_database( $request ); - + $post = $this->prepare_item_for_database( $request ); if ( is_wp_error( $post ) ) { return $post; } From 54e337644664b784f34327f140158d24f322925c Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 23 Mar 2026 16:43:24 +0100 Subject: [PATCH 05/16] Fix posts controller create_item_permissions_check static analysis issue --- .../rest-api/endpoints/class-wp-rest-posts-controller.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php index 782ef0424b10e..998298933fe0f 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php @@ -714,6 +714,13 @@ public function create_item_permissions_check( $request ) { } $post_type = get_post_type_object( $this->post_type ); + if ( ! $post_type ) { + return new WP_Error( + 'rest_post_invalid_type', + __( 'Invalid post type.' ), + array( 'status' => 400 ) + ); + } if ( ! empty( $request['author'] ) && get_current_user_id() !== $request['author'] && ! current_user_can( $post_type->cap->edit_others_posts ) ) { return new WP_Error( From a6fa997fa586dc242e18cedc6505f192d4f015b1 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 23 Mar 2026 20:03:35 +0100 Subject: [PATCH 06/16] Add conditional return for wp_slash() --- src/wp-includes/formatting.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/wp-includes/formatting.php b/src/wp-includes/formatting.php index 2b32b5aafb05d..fb5ca66f17573 100644 --- a/src/wp-includes/formatting.php +++ b/src/wp-includes/formatting.php @@ -5777,6 +5777,8 @@ function sanitize_trackback_urls( $to_ping ) { * * @param string|array $value String or array of data to slash. * @return string|array Slashed `$value`, in the same type as supplied. + * + * @phpstan-return ( $value is string ? string : array ) */ function wp_slash( $value ) { if ( is_array( $value ) ) { From a6ef31aec2855e078e865b2fffed8e7526dad16d Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 23 Mar 2026 20:03:47 +0100 Subject: [PATCH 07/16] Add conditional return for get_post() --- src/wp-includes/post.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index 88deb1090fc5c..a598adecbcf14 100644 --- a/src/wp-includes/post.php +++ b/src/wp-includes/post.php @@ -1145,6 +1145,14 @@ function get_extended( $post ) { * or 'display'. Default 'raw'. * @return WP_Post|array|null Type corresponding to $output on success or null on failure. * When $output is OBJECT, a `WP_Post` instance is returned. + * + * @phpstan-return ( + * $output is 'ARRAY_A' ? array|null : ( + * $output is 'ARRAY_N' ? array|null : ( + * WP_Post|null + * ) + * ) + * ) */ function get_post( $post = null, $output = OBJECT, $filter = 'raw' ) { if ( empty( $post ) && isset( $GLOBALS['post'] ) ) { From 6cea1b0a70b3a9208ba3254be85f7c9cffeb41e8 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 23 Mar 2026 20:04:24 +0100 Subject: [PATCH 08/16] Add conditional return for rest_ensure_response() --- src/wp-includes/rest-api.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/wp-includes/rest-api.php b/src/wp-includes/rest-api.php index 922f8968c5b21..af5ece695adee 100644 --- a/src/wp-includes/rest-api.php +++ b/src/wp-includes/rest-api.php @@ -684,6 +684,8 @@ function rest_ensure_request( $request ) { * @return WP_REST_Response|WP_Error If response generated an error, WP_Error, if response * is already an instance, WP_REST_Response, otherwise * returns a new WP_REST_Response instance. + * + * @phpstan-return ( $response is WP_Error ? WP_Error : WP_REST_Response ) */ function rest_ensure_response( $response ) { if ( is_wp_error( $response ) ) { From 5c46ca66625804129648fef50977f0356b47d3a8 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 23 Mar 2026 20:05:54 +0100 Subject: [PATCH 09/16] Fix static analysis issues with WP_REST_Posts_Controller::create_item() --- .../endpoints/class-wp-rest-posts-controller.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php index 998298933fe0f..2a4aa4dd6b6d8 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php @@ -814,6 +814,13 @@ public function create_item( $request ) { } $post = get_post( $post_id ); + if ( ! $post ) { + return new WP_Error( + 'rest_post_invalid_id', + __( 'Invalid post ID.' ), + array( 'status' => 404 ) + ); + } /** * Fires after a single post is created or updated via the REST API. @@ -870,7 +877,6 @@ public function create_item( $request ) { } } - $post = get_post( $post_id ); $fields_update = $this->update_additional_fields_for_object( $post, $request ); if ( is_wp_error( $fields_update ) ) { @@ -901,6 +907,10 @@ public function create_item( $request ) { wp_after_insert_post( $post, false, null ); $response = $this->prepare_item_for_response( $post, $request ); + if ( is_wp_error( $response ) ) { + return $response; + } + $response = rest_ensure_response( $response ); $response->set_status( 201 ); From a231298e3b03a4b83c6e44b3643023c2220ad37b Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 23 Mar 2026 20:20:03 +0100 Subject: [PATCH 10/16] Fix passing args to wp_unique_post_slug() in create and update methods --- .../endpoints/class-wp-rest-posts-controller.php | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php index 2a4aa4dd6b6d8..5f0d0cc7f2386 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php @@ -790,13 +790,16 @@ public function create_item( $request ) { * `wp_unique_post_slug()` returns the same slug for 'draft' or 'pending' posts. * * To ensure that a unique slug is generated, pass the post data with the 'publish' status. + * + * Note that neither ID nor post_parent are guaranteed to be set in ::prepare_item_for_database(), so this + * is the reason for the null coalescing operator. */ $prepared_post->post_name = wp_unique_post_slug( $prepared_post->post_name, - $prepared_post->id, + $prepared_post->ID ?? 0, 'publish', $prepared_post->post_type, - $prepared_post->post_parent + $prepared_post->post_parent ?? 0 ); } @@ -999,15 +1002,17 @@ public function update_item( $request ) { * `wp_unique_post_slug()` returns the same slug for 'draft' or 'pending' posts. * * To ensure that a unique slug is generated, pass the post data with the 'publish' status. + * + * Note that neither ID nor post_parent are guaranteed to be set in ::prepare_item_for_database(), so this + * is the reason for the null coalescing operator. */ if ( ! empty( $post->post_name ) && in_array( $post_status, array( 'draft', 'pending' ), true ) ) { - $post_parent = ! empty( $post->post_parent ) ? $post->post_parent : 0; $post->post_name = wp_unique_post_slug( $post->post_name, - $post->ID, + $post->ID ?? 0, 'publish', $post->post_type, - $post_parent + $post->post_parent ?? 0 ); } From eece334aee0bbdf87da77a9d3ce77124c926899e Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 23 Mar 2026 20:20:19 +0100 Subject: [PATCH 11/16] Explain why post_type property is being set redundantly --- .../rest-api/endpoints/class-wp-rest-posts-controller.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php index 5f0d0cc7f2386..891612af6ee4a 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php @@ -780,6 +780,10 @@ public function create_item( $request ) { return $prepared_post; } + /* + * This is also set in the ::prepare_item_for_database() method above, but since the return value is filterable, + * there is no guarantee. + */ $prepared_post->post_type = $this->post_type; if ( ! empty( $prepared_post->post_name ) From aa43838097657e65094d09b45ef80bf9a8d524ad Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 23 Mar 2026 20:21:06 +0100 Subject: [PATCH 12/16] Ensure post onject in update_item() method --- .../rest-api/endpoints/class-wp-rest-posts-controller.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php index 891612af6ee4a..ac1b18d93b625 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php @@ -1033,6 +1033,13 @@ public function update_item( $request ) { } $post = get_post( $post_id ); + if ( ! $post ) { + return new WP_Error( + 'rest_post_invalid_id', + __( 'Invalid post ID.' ), + array( 'status' => 404 ) + ); + } /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */ do_action( "rest_insert_{$this->post_type}", $post, $request, false ); @@ -1073,7 +1080,6 @@ public function update_item( $request ) { } } - $post = get_post( $post_id ); $fields_update = $this->update_additional_fields_for_object( $post, $request ); if ( is_wp_error( $fields_update ) ) { From 3be29db48d92d0010ea31e403312684dab42c2f7 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 23 Mar 2026 20:26:21 +0100 Subject: [PATCH 13/16] Fix static analysis in WP_REST_Menu_Items_Controller::create_item() --- .../endpoints/class-wp-rest-menu-items-controller.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php index dd72bc1c15210..07dc24c50d17b 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php @@ -196,6 +196,13 @@ public function create_item( $request ) { do_action( 'rest_after_insert_nav_menu_item', $nav_menu_item, $request, true ); $post = get_post( $nav_menu_item_id ); + if ( ! $post ) { + return new WP_Error( + 'rest_post_invalid_id', + __( 'Invalid post ID.' ), + array( 'status' => 404 ) + ); + } wp_after_insert_post( $post, false, null ); $response = $this->prepare_item_for_response( $post, $request ); From f5169795ccdaa9e15e21cdc43d174b5d90983a97 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 23 Mar 2026 20:39:59 +0100 Subject: [PATCH 14/16] Fix static analysis with WP_REST_Autosaves_Controller::create_item() --- .../endpoints/class-wp-rest-autosaves-controller.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php index 9b471774bd203..f4564af41b061 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-autosaves-controller.php @@ -14,6 +14,8 @@ * * @see WP_REST_Revisions_Controller * @see WP_REST_Controller + * + * @phpstan-import-type PreparedPost from WP_REST_Posts_Controller */ class WP_REST_Autosaves_Controller extends WP_REST_Revisions_Controller { @@ -225,6 +227,9 @@ public function create_item( $request ) { return $prepared_post; } + /** + * @var PreparedPost $prepared_post + */ $prepared_post->ID = $post->ID; $user_id = get_current_user_id(); @@ -276,6 +281,13 @@ public function create_item( $request ) { } $autosave = get_post( $autosave_id ); + if ( ! $autosave ) { + return new WP_Error( + 'rest_post_invalid_id', + __( 'Invalid post ID.' ), + array( 'status' => 404 ) + ); + } $request->set_param( 'context', 'edit' ); $response = $this->prepare_item_for_response( $autosave, $request ); From 72b426ee3ef9b5f9999688887b028bd1832de5f5 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 23 Mar 2026 20:43:13 +0100 Subject: [PATCH 15/16] Fix static analysis issues with WP_REST_Attachments_Controller::insert_attachment() --- .../class-wp-rest-attachments-controller.php | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php index 6d84193665b15..c96d2080b73c9 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php @@ -327,7 +327,7 @@ protected function insert_attachment( $request ) { $name = wp_basename( $file['file'] ); $name_parts = pathinfo( $name ); - $name = trim( substr( $name, 0, -( 1 + strlen( $name_parts['extension'] ) ) ) ); + $name = trim( substr( $name, 0, -( 1 + strlen( $name_parts['extension'] ?? '' ) ) ) ); $url = $file['url']; $type = $file['type']; @@ -365,10 +365,12 @@ protected function insert_attachment( $request ) { // If the title was not set, use the original filename. if ( empty( $attachment->post_title ) && ! empty( $files['file']['name'] ) ) { // Remove the file extension (after the last `.`) - $tmp_title = substr( $files['file']['name'], 0, strrpos( $files['file']['name'], '.' ) ); - - if ( ! empty( $tmp_title ) ) { - $attachment->post_title = $tmp_title; + $last_dot_location = strrpos( $files['file']['name'], '.' ); + if ( false !== $last_dot_location ) { + $tmp_title = substr( $files['file']['name'], 0, $last_dot_location ); + if ( ! empty( $tmp_title ) ) { + $attachment->post_title = $tmp_title; + } } } From 7c2131d118d950c4118c3aab7ad8a98d237c8cf5 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 23 Mar 2026 20:48:44 +0100 Subject: [PATCH 16/16] Fix static analysis issues with WP_REST_Attachments_Controller::edit_media_item() --- .../class-wp-rest-attachments-controller.php | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php index c96d2080b73c9..b52843d5024b0 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php @@ -664,6 +664,13 @@ public function edit_media_item( $request ) { if ( ! file_exists( $image_file_to_edit ) ) { $image_file_to_edit = _load_image_to_edit_path( $attachment_id ); } + if ( false === $image_file_to_edit ) { + return new WP_Error( + 'rest_cannot_get_image_file_to_edit', + __( 'Unable to get image file.' ), + array( 'status' => 404 ) + ); + } $image_editor = wp_get_image_editor( $image_file_to_edit ); @@ -861,7 +868,15 @@ public function edit_media_item( $request ) { wp_update_attachment_metadata( $new_attachment_id, $new_image_meta ); - $response = $this->prepare_item_for_response( get_post( $new_attachment_id ), $request ); + $new_attachment_post = get_post( $new_attachment_id ); + if ( ! $new_attachment_post ) { + return new WP_Error( + 'rest_post_invalid_id', + __( 'Invalid post ID.' ), + array( 'status' => 404 ) + ); + } + $response = $this->prepare_item_for_response( $new_attachment_post, $request ); $response->set_status( 201 ); $response->header( 'Location', rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $new_attachment_id ) ) );