diff -r 7b1b88e27a20 -r 48c4eec2b7e6 wp/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php --- a/wp/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php Thu Sep 29 08:06:27 2022 +0200 +++ b/wp/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php Fri Sep 05 18:40:08 2025 +0200 @@ -97,6 +97,12 @@ $get_item_args = array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), ); + if ( isset( $schema['properties']['excerpt'] ) ) { + $get_item_args['excerpt_length'] = array( + 'description' => __( 'Override the default excerpt length.' ), + 'type' => 'integer', + ); + } if ( isset( $schema['properties']['password'] ) ) { $get_item_args['password'] = array( 'description' => __( 'The password for the post if it is password protected.' ), @@ -167,7 +173,7 @@ } /** - * Override the result of the post password check for REST requested posts. + * Overrides the result of the post password check for REST requested posts. * * Allow users to read the content of password protected posts if they have * previously passed a permission check or if they have the `edit_post` capability @@ -249,6 +255,7 @@ 'parent' => 'post_parent__in', 'parent_exclude' => 'post_parent__not_in', 'search' => 's', + 'search_columns' => 'search_columns', 'slug' => 'post_name__in', 'status' => 'post_status', ); @@ -369,6 +376,13 @@ $posts = array(); + update_post_author_caches( $query_result ); + update_post_parent_caches( $query_result ); + + if ( post_type_supports( $this->post_type, 'thumbnail' ) ) { + update_post_thumbnail_cache( $posts_query ); + } + foreach ( $query_result as $post ) { if ( ! $this->check_read_permission( $post ) ) { continue; @@ -386,7 +400,7 @@ $page = (int) $query_args['paged']; $total_posts = $posts_query->found_posts; - if ( $total_posts < 1 ) { + if ( $total_posts < 1 && $page > 1 ) { // Out-of-bounds, run the query again without LIMIT for total count. unset( $query_args['paged'] ); @@ -395,7 +409,7 @@ $total_posts = $count_query->found_posts; } - $max_pages = ceil( $total_posts / (int) $posts_query->query_vars['posts_per_page'] ); + $max_pages = (int) ceil( $total_posts / (int) $posts_query->query_vars['posts_per_page'] ); if ( $page > $max_pages && $total_posts > 0 ) { return new WP_Error( @@ -411,7 +425,8 @@ $response->header( 'X-WP-TotalPages', (int) $max_pages ); $request_params = $request->get_query_params(); - $base = add_query_arg( urlencode_deep( $request_params ), rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ) ); + $collection_url = rest_url( rest_get_route_for_post_type_items( $this->post_type ) ); + $base = add_query_arg( urlencode_deep( $request_params ), $collection_url ); if ( $page > 1 ) { $prev_page = $page - 1; @@ -434,7 +449,7 @@ } /** - * Get the post, if the ID is valid. + * Gets the post, if the ID is valid. * * @since 4.7.2 * @@ -466,7 +481,7 @@ * @since 4.7.0 * * @param WP_REST_Request $request Full details about the request. - * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise. + * @return bool|WP_Error True if the request has read access for the item, WP_Error object or false otherwise. */ public function get_item_permissions_check( $request ) { $post = $this->get_post( $request['id'] ); @@ -646,6 +661,24 @@ $prepared_post->post_type = $this->post_type; + if ( ! empty( $prepared_post->post_name ) + && ! empty( $prepared_post->post_status ) + && in_array( $prepared_post->post_status, array( 'draft', 'pending' ), true ) + ) { + /* + * `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. + */ + $prepared_post->post_name = wp_unique_post_slug( + $prepared_post->post_name, + $prepared_post->id, + 'publish', + $prepared_post->post_type, + $prepared_post->post_parent + ); + } + $post_id = wp_insert_post( wp_slash( (array) $prepared_post ), true, false ); if ( is_wp_error( $post_id ) ) { @@ -750,7 +783,7 @@ $response = rest_ensure_response( $response ); $response->set_status( 201 ); - $response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $post_id ) ) ); + $response->header( 'Location', rest_url( rest_get_route_for_post( $post ) ) ); return $response; } @@ -827,6 +860,28 @@ return $post; } + if ( ! empty( $post->post_status ) ) { + $post_status = $post->post_status; + } else { + $post_status = $post_before->post_status; + } + + /* + * `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. + */ + 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, + 'publish', + $post->post_type, + $post_parent + ); + } + // Convert the post object to an array, otherwise wp_update_post() will expect non-escaped input. $post_id = wp_update_post( wp_slash( (array) $post ), true, false ); @@ -1014,8 +1069,10 @@ ); } - // (Note that internally this falls through to `wp_delete_post()` - // if the Trash is disabled.) + /* + * (Note that internally this falls through to `wp_delete_post()` + * if the Trash is disabled.) + */ $result = wp_trash_post( $id ); $post = get_post( $id ); $response = $this->prepare_item_for_response( $post, $request ); @@ -1220,8 +1277,10 @@ } } - // Sending a null date or date_gmt value resets date and date_gmt to their - // default values (`0000-00-00 00:00:00`). + /* + * Sending a null date or date_gmt value resets date and date_gmt to their + * default values (`0000-00-00 00:00:00`). + */ if ( ( ! empty( $schema['properties']['date_gmt'] ) && $request->has_param( 'date_gmt' ) && null === $request['date_gmt'] ) || ( ! empty( $schema['properties']['date'] ) && $request->has_param( 'date' ) && null === $request['date'] ) @@ -1344,7 +1403,6 @@ * @param WP_REST_Request $request Request object. */ return apply_filters( "rest_pre_insert_{$this->post_type}", $prepared_post, $request ); - } /** @@ -1443,17 +1501,16 @@ } else { return delete_post_thumbnail( $post_id ); } - } /** - * Check whether the template is valid for the given post. + * Checks whether the template is valid for the given post. * * @since 4.9.0 * * @param string $template Page template filename. * @param WP_REST_Request $request Request. - * @return bool|WP_Error True if template is still valid or if the same as existing value, or false if template not supported. + * @return true|WP_Error True if template is still valid or if the same as existing value, or a WP_Error if template not supported. */ public function check_template( $template, $request ) { @@ -1691,13 +1748,16 @@ * @since 4.7.0 * @since 5.9.0 Renamed `$post` to `$item` to match parent class for PHP 8 named parameter support. * + * @global WP_Post $post Global post object. + * * @param WP_Post $item Post object. * @param WP_REST_Request $request Request object. * @return WP_REST_Response Response object. */ public function prepare_item_for_response( $item, $request ) { // Restores the more descriptive, specific name for use within this method. - $post = $item; + $post = $item; + $GLOBALS['post'] = $post; setup_postdata( $post ); @@ -1749,7 +1809,7 @@ * with the site's timezone offset applied. */ if ( '0000-00-00 00:00:00' === $post->post_modified_gmt ) { - $post_modified_gmt = gmdate( 'Y-m-d H:i:s', strtotime( $post->post_modified ) - ( get_option( 'gmt_offset' ) * 3600 ) ); + $post_modified_gmt = gmdate( 'Y-m-d H:i:s', strtotime( $post->post_modified ) - ( get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ) ); } else { $post_modified_gmt = $post->post_modified_gmt; } @@ -1818,6 +1878,19 @@ } if ( rest_is_field_included( 'excerpt', $fields ) ) { + if ( isset( $request['excerpt_length'] ) ) { + $excerpt_length = $request['excerpt_length']; + $override_excerpt_length = static function () use ( $excerpt_length ) { + return $excerpt_length; + }; + + add_filter( + 'excerpt_length', + $override_excerpt_length, + 20 + ); + } + /** This filter is documented in wp-includes/post-template.php */ $excerpt = apply_filters( 'get_the_excerpt', $post->post_excerpt, $post ); @@ -1829,6 +1902,14 @@ 'rendered' => post_password_required( $post ) ? '' : $excerpt, 'protected' => (bool) $post->post_password, ); + + if ( isset( $override_excerpt_length ) ) { + remove_filter( + 'excerpt_length', + $override_excerpt_length, + 20 + ); + } } if ( $has_password_filter ) { @@ -1917,6 +1998,10 @@ $data['generated_slug'] = $sample_permalink[1]; } } + + if ( rest_is_field_included( 'class_list', $fields ) ) { + $data['class_list'] = get_post_class( array(), $post->ID ); + } } $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; @@ -1926,16 +2011,18 @@ // Wrap the data in a response object. $response = rest_ensure_response( $data ); - $links = $this->prepare_links( $post ); - $response->add_links( $links ); - - if ( ! empty( $links['self']['href'] ) ) { - $actions = $this->get_available_actions( $post, $request ); - - $self = $links['self']['href']; - - foreach ( $actions as $rel ) { - $response->add_link( $rel, $self ); + if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { + $links = $this->prepare_links( $post ); + $response->add_links( $links ); + + if ( ! empty( $links['self']['href'] ) ) { + $actions = $this->get_available_actions( $post, $request ); + + $self = $links['self']['href']; + + foreach ( $actions as $rel ) { + $response->add_link( $rel, $self ); + } } } @@ -1983,15 +2070,13 @@ * @return array Links for the given post. */ protected function prepare_links( $post ) { - $base = sprintf( '%s/%s', $this->namespace, $this->rest_base ); - // Entity meta. $links = array( 'self' => array( - 'href' => rest_url( trailingslashit( $base ) . $post->ID ), + 'href' => rest_url( rest_get_route_for_post( $post->ID ) ), ), 'collection' => array( - 'href' => rest_url( $base ), + 'href' => rest_url( rest_get_route_for_post_type_items( $this->post_type ) ), ), 'about' => array( 'href' => rest_url( 'wp/v2/types/' . $this->post_type ), @@ -2017,20 +2102,19 @@ } if ( in_array( $post->post_type, array( 'post', 'page' ), true ) || post_type_supports( $post->post_type, 'revisions' ) ) { - $revisions = wp_get_post_revisions( $post->ID, array( 'fields' => 'ids' ) ); - $revisions_count = count( $revisions ); + $revisions = wp_get_latest_revision_id_and_total_count( $post->ID ); + $revisions_count = ! is_wp_error( $revisions ) ? $revisions['count'] : 0; + $revisions_base = sprintf( '/%s/%s/%d/revisions', $this->namespace, $this->rest_base, $post->ID ); $links['version-history'] = array( - 'href' => rest_url( trailingslashit( $base ) . $post->ID . '/revisions' ), + 'href' => rest_url( $revisions_base ), 'count' => $revisions_count, ); if ( $revisions_count > 0 ) { - $last_revision = array_shift( $revisions ); - $links['predecessor-version'] = array( - 'href' => rest_url( trailingslashit( $base ) . $post->ID . '/revisions/' . $last_revision ), - 'id' => $last_revision, + 'href' => rest_url( $revisions_base . '/' . $revisions['latest_id'] ), + 'id' => $revisions['latest_id'], ); } } @@ -2094,7 +2178,7 @@ } /** - * Get the link relations available for the post and current user. + * Gets the link relations available for the post and current user. * * @since 4.9.8 * @@ -2273,6 +2357,16 @@ 'context' => array( 'edit' ), 'readonly' => true, ); + + $schema['properties']['class_list'] = array( + 'description' => __( 'An array of the class names for the post container element.' ), + 'type' => 'array', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + 'items' => array( + 'type' => 'string', + ), + ); } if ( $post_type_obj->hierarchical ) { @@ -2324,6 +2418,7 @@ 'comments', 'revisions', 'custom-fields', + 'thumbnail', ), ); @@ -2582,7 +2677,7 @@ } /** - * Retrieve Link Description Objects that should be added to the Schema for the posts collection. + * Retrieves Link Description Objects that should be added to the Schema for the posts collection. * * @since 4.9.8 * @@ -2844,13 +2939,22 @@ ); } - $query_params['slug'] = array( - 'description' => __( 'Limit result set to posts with one or more specific slugs.' ), - 'type' => 'array', - 'items' => array( + $query_params['search_columns'] = array( + 'default' => array(), + 'description' => __( 'Array of column names to be searched.' ), + 'type' => 'array', + 'items' => array( + 'enum' => array( 'post_title', 'post_content', 'post_excerpt' ), 'type' => 'string', ), - 'sanitize_callback' => 'wp_parse_slug_list', + ); + + $query_params['slug'] = array( + 'description' => __( 'Limit result set to posts with one or more specific slugs.' ), + 'type' => 'array', + 'items' => array( + 'type' => 'string', + ), ); $query_params['status'] = array(