diff --git a/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php b/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php index f824b0c9e2ca..2176d6abbe40 100644 --- a/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php +++ b/wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php @@ -221,6 +221,14 @@ public function create_item( $request ) { update_post_meta( $attachment_id, '_wp_attachment_image_alt', sanitize_text_field( $request['alt_text'] ) ); } + if ( isset( $request['focal_point'] ) ) { + $focal_point = array( + 'x' => floatval( $request['focal_point']['x'] ), + 'y' => floatval( $request['focal_point']['y'] ), + ); + update_post_meta( $attachment_id, '_wp_attachment_focal_point', $focal_point ); + } + if ( ! empty( $schema['properties']['featured_media'] ) && isset( $request['featured_media'] ) ) { $thumbnail_update = $this->handle_featured_media( $request['featured_media'], $attachment_id ); @@ -469,6 +477,14 @@ public function update_item( $request ) { update_post_meta( $data['id'], '_wp_attachment_image_alt', $request['alt_text'] ); } + if ( isset( $request['focal_point'] ) ) { + $focal_point = array( + 'x' => floatval( $request['focal_point']['x'] ), + 'y' => floatval( $request['focal_point']['y'] ), + ); + update_post_meta( $data['id'], '_wp_attachment_focal_point', $focal_point ); + } + $attachment = get_post( $request['id'] ); if ( ! empty( $schema['properties']['featured_media'] ) && isset( $request['featured_media'] ) ) { @@ -929,6 +945,26 @@ public function prepare_item_for_response( $item, $request ) { $data['alt_text'] = get_post_meta( $post->ID, '_wp_attachment_image_alt', true ); } + if ( in_array( 'focal_point', $fields, true ) ) { + $focal_point = get_post_meta( $post->ID, '_wp_attachment_focal_point', true ); + $data['focal_point'] = ! empty( $focal_point ) ? $focal_point : array( 'x' => 0.5, 'y' => 0.5 ); + } + + if ( in_array( 'accessibility_status', $fields, true ) ) { + $alt = get_post_meta( $post->ID, '_wp_attachment_image_alt', true ); + $cap = $post->post_excerpt; + $desc = $post->post_content; + $count = ( ! empty( $alt ) ? 1 : 0 ) + ( ! empty( $cap ) ? 1 : 0 ) + ( ! empty( $desc ) ? 1 : 0 ); + + if ( 3 === $count ) { + $data['accessibility_status'] = 'complete'; + } elseif ( $count > 0 ) { + $data['accessibility_status'] = 'partial'; + } else { + $data['accessibility_status'] = 'missing'; + } + } + if ( in_array( 'media_type', $fields, true ) ) { $data['media_type'] = wp_attachment_is_image( $post->ID ) ? 'image' : 'file'; } @@ -1069,6 +1105,38 @@ public function get_item_schema() { ), ); + $schema['properties']['focal_point'] = array( + 'description' => __( 'Focal point for smart image cropping, as an object with x and y coordinates from 0 to 1.' ), + 'type' => 'object', + 'context' => array( 'view', 'edit', 'embed' ), + 'properties' => array( + 'x' => array( + 'description' => __( 'Horizontal position from 0 (left) to 1 (right).' ), + 'type' => 'number', + 'minimum' => 0, + 'maximum' => 1, + ), + 'y' => array( + 'description' => __( 'Vertical position from 0 (top) to 1 (bottom).' ), + 'type' => 'number', + 'minimum' => 0, + 'maximum' => 1, + ), + ), + 'arg_options' => array( + 'sanitize_callback' => null, + 'validate_callback' => null, + ), + ); + + $schema['properties']['accessibility_status'] = array( + 'description' => __( 'Accessibility completeness based on alt text, caption, and description.' ), + 'type' => 'string', + 'enum' => array( 'complete', 'partial', 'missing' ), + 'context' => array( 'view', 'edit', 'embed' ), + 'readonly' => true, + ); + $schema['properties']['caption'] = array( 'description' => __( 'The attachment caption.' ), 'type' => 'object', diff --git a/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php b/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php index 3f83504f8a3e..4ee7e3e21dd8 100644 --- a/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php +++ b/wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php @@ -820,6 +820,10 @@ public function create_item( $request ) { } } + if ( isset( $request['priority'] ) ) { + update_comment_meta( $comment_id, '_comment_priority', sanitize_text_field( $request['priority'] ) ); + } + $fields_update = $this->update_additional_fields_for_object( $comment, $request ); if ( is_wp_error( $fields_update ) ) { @@ -983,6 +987,10 @@ public function update_item( $request ) { } } + if ( isset( $request['priority'] ) ) { + update_comment_meta( $id, '_comment_priority', sanitize_text_field( $request['priority'] ) ); + } + $fields_update = $this->update_additional_fields_for_object( $comment, $request ); if ( is_wp_error( $fields_update ) ) { @@ -1204,6 +1212,15 @@ public function prepare_item_for_response( $item, $request ) { $data['meta'] = $this->meta->get_value( $comment->comment_ID, $request ); } + if ( in_array( 'priority', $fields, true ) ) { + $priority = get_comment_meta( $comment->comment_ID, '_comment_priority', true ); + $data['priority'] = ! empty( $priority ) ? $priority : 'normal'; + } + + if ( in_array( 'word_count', $fields, true ) ) { + $data['word_count'] = str_word_count( wp_strip_all_tags( $comment->comment_content ) ); + } + $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); @@ -1638,6 +1655,24 @@ public function get_item_schema() { ); } + $schema['properties']['priority'] = array( + 'description' => __( 'Editorial priority level for comment moderation workflow.' ), + 'type' => 'string', + 'default' => 'normal', + 'enum' => array( 'low', 'normal', 'high', 'urgent' ), + 'context' => array( 'view', 'edit' ), + 'arg_options' => array( + 'sanitize_callback' => 'sanitize_text_field', + ), + ); + + $schema['properties']['word_count'] = array( + 'description' => __( 'Word count of the comment content.' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit', 'embed' ), + 'readonly' => true, + ); + $schema['properties']['meta'] = $this->meta->get_field_schema(); $this->schema = $schema; diff --git a/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php b/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php index 14afb8c2eedd..745c5e6fcb2b 100644 --- a/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php +++ b/wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php @@ -2105,6 +2105,24 @@ public function prepare_item_for_response( $item, $request ) { } } + if ( rest_is_field_included( 'reading_time', $fields ) ) { + $content = wp_strip_all_tags( $post->post_content ); + $word_count = str_word_count( $content ); + $reading_speed = isset( $request['reading_speed'] ) ? (int) $request['reading_speed'] : 250; + $reading_speed = max( 1, $reading_speed ); + $data['reading_time'] = (int) ceil( $word_count / $reading_speed ); + } + + if ( rest_is_field_included( 'content_summary', $fields ) ) { + $stripped = wp_strip_all_tags( $post->post_content ); + $stripped = trim( preg_replace( '/\s+/', ' ', $stripped ) ); + if ( strlen( $stripped ) > 160 ) { + $data['content_summary'] = substr( $stripped, 0, 157 ) . '...'; + } else { + $data['content_summary'] = $stripped; + } + } + $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); @@ -2700,6 +2718,20 @@ public function get_item_schema() { ), ); + $schema['properties']['reading_time'] = array( + 'description' => __( 'Estimated reading time for the post content in minutes.' ), + 'type' => 'integer', + 'context' => array( 'view', 'edit', 'embed' ), + 'readonly' => true, + ); + + $schema['properties']['content_summary'] = array( + 'description' => __( 'Auto-generated plain-text summary of the post content.' ), + 'type' => 'string', + 'context' => array( 'view', 'edit', 'embed' ), + 'readonly' => true, + ); + $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); foreach ( $taxonomies as $taxonomy ) { @@ -3102,6 +3134,13 @@ public function get_collection_params() { ); } + $query_params['reading_speed'] = array( + 'description' => __( 'Words per minute rate used to calculate reading time. Default 250.' ), + 'type' => 'integer', + 'default' => 250, + 'minimum' => 1, + ); + /** * Filters collection parameters for the posts controller. * diff --git a/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php b/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php index 94b3e5c264a9..383530acaaca 100644 --- a/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php +++ b/wp-includes/rest-api/endpoints/class-wp-rest-themes-controller.php @@ -211,6 +211,28 @@ public function get_items( $request ) { $themes[] = $this->prepare_response_for_collection( $prepared ); } + if ( ! empty( $request['sort'] ) ) { + $sort_field = $request['sort']; + usort( $themes, function ( $a, $b ) use ( $sort_field ) { + switch ( $sort_field ) { + case 'name': + $a_val = isset( $a['name']['raw'] ) ? $a['name']['raw'] : ''; + $b_val = isset( $b['name']['raw'] ) ? $b['name']['raw'] : ''; + return strnatcasecmp( $a_val, $b_val ); + case 'author': + $a_val = isset( $a['author']['raw'] ) ? $a['author']['raw'] : ''; + $b_val = isset( $b['author']['raw'] ) ? $b['author']['raw'] : ''; + return strnatcasecmp( $a_val, $b_val ); + case 'version': + $a_val = isset( $a['version'] ) ? $a['version'] : '0'; + $b_val = isset( $b['version'] ) ? $b['version'] : '0'; + return version_compare( $a_val, $b_val ); + default: + return 0; + } + }); + } + $response = rest_ensure_response( $themes ); $response->header( 'X-WP-Total', count( $themes ) ); @@ -361,6 +383,28 @@ public function prepare_item_for_response( $item, $request ) { $data['default_template_part_areas'] = get_allowed_block_template_part_areas(); } + if ( rest_is_field_included( 'compatibility_status', $fields ) ) { + $compatibility = array( + 'php' => 'unknown', + 'wp' => 'unknown', + ); + + $requires_php = $theme->get( 'RequiresPHP' ); + if ( $requires_php ) { + $compatibility['php'] = version_compare( phpversion(), $requires_php, '>=' ) + ? 'compatible' : 'incompatible'; + } + + $requires_wp = $theme->get( 'RequiresWP' ); + if ( $requires_wp ) { + global $wp_version; + $compatibility['wp'] = version_compare( $wp_version, $requires_wp, '>=' ) + ? 'compatible' : 'incompatible'; + } + + $data['compatibility_status'] = $compatibility; + } + $data = $this->add_additional_fields_to_object( $data, $request ); // Wrap the data in a response object. @@ -686,6 +730,24 @@ public function get_item_schema() { ), ), ), + 'compatibility_status' => array( + 'description' => __( 'Compatibility status of the theme with the current environment.' ), + 'type' => 'object', + 'readonly' => true, + 'context' => array( 'view', 'edit' ), + 'properties' => array( + 'php' => array( + 'description' => __( 'PHP version compatibility: compatible, incompatible, or unknown.' ), + 'type' => 'string', + 'enum' => array( 'compatible', 'incompatible', 'unknown' ), + ), + 'wp' => array( + 'description' => __( 'WordPress version compatibility: compatible, incompatible, or unknown.' ), + 'type' => 'string', + 'enum' => array( 'compatible', 'incompatible', 'unknown' ), + ), + ), + ), ), ); @@ -723,6 +785,12 @@ public function get_collection_params() { ), ); + $query_params['sort'] = array( + 'description' => __( 'Sort the result set by the given field.' ), + 'type' => 'string', + 'enum' => array( 'name', 'author', 'version' ), + ); + /** * Filters REST API collection parameters for the themes controller. *