diff --git a/lib/compat/wordpress-6.6/class-wp-rest-media-search-handler-gutenberg-6-6.php b/lib/compat/wordpress-6.6/class-wp-rest-media-search-handler-gutenberg-6-6.php
new file mode 100644
index 00000000000000..53b4dc02ecbc16
--- /dev/null
+++ b/lib/compat/wordpress-6.6/class-wp-rest-media-search-handler-gutenberg-6-6.php
@@ -0,0 +1,110 @@
+type = 'media';
+ $this->subtypes = array();
+ }
+
+ /**
+ * Searches the object type content for a given search request.
+ *
+ * @since 6.6.0
+ *
+ * @param WP_REST_Request $request Full REST request.
+ * @return array Associative array containing an `WP_REST_Search_Handler::RESULT_IDS` containing
+ * an array of found IDs and `WP_REST_Search_Handler::RESULT_TOTAL` containing the
+ * total count for the matching search results.
+ */
+ public function search_items( WP_REST_Request $request ) {
+
+ $query_args = array(
+ 'post_type' => 'attachment',
+ 'post_status' => 'inherit',
+ 'paged' => (int) $request['page'],
+ 'posts_per_page' => (int) $request['per_page'],
+ );
+
+ if ( ! empty( $request['search'] ) ) {
+ $query_args['s'] = $request['search'];
+
+ // Filter query clauses to include filenames.
+ add_filter( 'wp_allow_query_attachment_by_filename', '__return_true' );
+ }
+
+ if ( ! empty( $request['exclude'] ) ) {
+ $query_args['post__not_in'] = $request['exclude'];
+ }
+
+ if ( ! empty( $request['include'] ) ) {
+ $query_args['post__in'] = $request['include'];
+ }
+
+ /**
+ * Filters the query arguments for a REST API search request.
+ *
+ * Enables adding extra arguments or setting defaults for a media search request.
+ *
+ * @since 6.6.0
+ *
+ * @param array $query_args Key value array of query var to query value.
+ * @param WP_REST_Request $request The request used.
+ */
+ $query_args = apply_filters( 'rest_media_search_query', $query_args, $request );
+
+ $query = new WP_Query();
+ $posts = $query->query( $query_args );
+ // Querying the whole post object will warm the object cache, avoiding an extra query per result.
+ $found_ids = wp_list_pluck( $posts, 'ID' );
+ $total = $query->found_posts;
+
+ return array(
+ self::RESULT_IDS => $found_ids,
+ self::RESULT_TOTAL => $total,
+ );
+ }
+
+ /**
+ * Prepares the search result for a given ID.
+ *
+ * @since 6.6.0
+ *
+ * @param int $id Item ID.
+ * @param array $fields Fields to include for the item.
+ * @return array Associative array containing all fields for the item.
+ */
+ public function prepare_item( $id, array $fields ) {
+ $data = parent::prepare_item( $id, $fields );
+
+ if ( isset( $data[ WP_REST_Search_Controller::PROP_SUBTYPE ] ) ) {
+ unset( $data[ WP_REST_Search_Controller::PROP_SUBTYPE ] );
+ }
+
+ return $data;
+ }
+}
diff --git a/lib/compat/wordpress-6.6/rest-api.php b/lib/compat/wordpress-6.6/rest-api.php
new file mode 100644
index 00000000000000..24040fde2762ed
--- /dev/null
+++ b/lib/compat/wordpress-6.6/rest-api.php
@@ -0,0 +1,118 @@
+ '_gutenberg_get_search_result_thumbnail_field',
+ 'update_callback' => null,
+ 'schema' => array(
+ 'description' => __( 'Object human readable subtype.', 'gutenberg' ),
+ 'type' => 'string',
+ 'readonly' => true,
+ 'context' => array( 'view', 'embed' ),
+ ),
+ )
+ );
+
+ register_rest_field(
+ 'search-result',
+ 'alt_text',
+ array(
+ 'get_callback' => '_gutenberg_get_search_result_alt_text_field',
+ 'update_callback' => null,
+ 'schema' => array(
+ 'description' => __( 'Object human readable subtype.', 'gutenberg' ),
+ 'type' => 'string',
+ 'readonly' => true,
+ 'context' => array( 'view', 'embed' ),
+ ),
+ )
+ );
+}
+
+add_action( 'rest_api_init', '_gutenberg_register_search_result_additional_fields' );
+
+/**
+ * Register custom rest media search handler.
+ *
+ * @param array $handlers
+ *
+ * @return array
+ */
+function _gutenberg_register_media_search_handler( $handlers ) {
+ if ( class_exists( 'WP_REST_Media_Search_Handler_Gutenberg_6_6' ) ) {
+ $handlers[] = new WP_REST_Media_Search_Handler_Gutenberg_6_6();
+ }
+
+ return $handlers;
+}
+
+add_action( 'wp_rest_search_handlers', '_gutenberg_register_media_search_handler' );
diff --git a/lib/load.php b/lib/load.php
index 3d9213ce6e93a9..363e74f60db93f 100644
--- a/lib/load.php
+++ b/lib/load.php
@@ -45,6 +45,10 @@ function gutenberg_is_experiment_enabled( $name ) {
require_once __DIR__ . '/compat/wordpress-6.5/class-gutenberg-rest-global-styles-revisions-controller-6-5.php';
require_once __DIR__ . '/compat/wordpress-6.5/rest-api.php';
+ // WordPress 6.6 compat.
+ require_once __DIR__ . '/compat/wordpress-6.6/class-wp-rest-media-search-handler-gutenberg-6-6.php';
+ require_once __DIR__ . '/compat/wordpress-6.6/rest-api.php';
+
// Plugin specific code.
require_once __DIR__ . '/class-wp-rest-global-styles-controller-gutenberg.php';
require_once __DIR__ . '/rest-api.php';
diff --git a/packages/block-editor/src/components/link-control/is-url-image.js b/packages/block-editor/src/components/link-control/is-url-image.js
new file mode 100644
index 00000000000000..34c05396633121
--- /dev/null
+++ b/packages/block-editor/src/components/link-control/is-url-image.js
@@ -0,0 +1,10 @@
+/**
+ * Checks if the url is an image.
+ * @param {string} url the url we are checking.
+ * @return {boolean} true if the url is an image url.
+ */
+export default function isImageUrl( url ) {
+ const pattern =
+ /^(https?:\/\/)([\w-]+(?:\.[\w-]+)*\/)*[\w-]+\.(?:jpg|JPG|jpeg|gif|png|webp)$/i;
+ return pattern.test( url );
+}
diff --git a/packages/block-editor/src/components/link-control/link-preview.js b/packages/block-editor/src/components/link-control/link-preview.js
index 17cc851c503a14..c75aae8b078442 100644
--- a/packages/block-editor/src/components/link-control/link-preview.js
+++ b/packages/block-editor/src/components/link-control/link-preview.js
@@ -3,6 +3,11 @@
*/
import classnames from 'classnames';
+/**
+ * Internal dependencies
+ */
+import isImageUrl from './is-url-image';
+
/**
* WordPress dependencies
*/
@@ -56,8 +61,22 @@ export default function LinkPreview( {
let icon;
+ const attachmentImg =
+ value.type === 'attachment' && isImageUrl( value?.url )
+ ? value.url
+ : null;
+
if ( richData?.icon ) {
icon =
;
+ } else if ( attachmentImg ) {
+ //TODO we don't have an alt text for this image
+ icon = (
+
+ );
} else if ( isEmptyURL ) {
icon = ;
} else {
diff --git a/packages/block-editor/src/components/link-control/search-item.js b/packages/block-editor/src/components/link-control/search-item.js
index 7fa8ee1803644c..22ab57a5544297 100644
--- a/packages/block-editor/src/components/link-control/search-item.js
+++ b/packages/block-editor/src/components/link-control/search-item.js
@@ -28,6 +28,13 @@ const ICONS_MAP = {
function SearchItemIcon( { isURL, suggestion } ) {
let icon = null;
+ let imageURL = null;
+ let altText = '';
+
+ if ( suggestion.kind === 'media' ) {
+ imageURL = suggestion.thumbnail ? suggestion.thumbnail : null;
+ altText = suggestion.alt_text;
+ }
if ( isURL ) {
icon = globe;
@@ -43,7 +50,17 @@ function SearchItemIcon( { isURL, suggestion } ) {
}
}
- if ( icon ) {
+ if ( imageURL ) {
+ return (
+
+
+
+ );
+ } else if ( icon ) {
return (
{
+ // use .each to test multiple cases
+ it.each( [
+ [ true, 'https://example.com/image.jpg' ],
+ [ true, 'https://example.com/image.gif' ],
+ [ true, 'https://example.com/image.png' ],
+ [ true, 'https://example.com/image.webp' ],
+ [ true, 'https://example.com/image.jpeg' ],
+ [ true, 'https://example.com/image.JPG' ],
+ [ false, 'https://example.com/image.txt' ],
+ [ false, 'https://example.com/image' ],
+ [ false, 'https://example.com/image.jpg?query=123' ],
+ [ false, '' ],
+ [ false, null ],
+ [ false, undefined ],
+ [ false, 123 ],
+ ] )(
+ 'returns %s when testing against URL "%s" for a valid image',
+ ( expected, testString ) => {
+ expect( isImageUrl( testString ) ).toBe( expected );
+ }
+ );
+} );
diff --git a/packages/core-data/src/fetch/__experimental-fetch-link-suggestions.js b/packages/core-data/src/fetch/__experimental-fetch-link-suggestions.js
index 3bba760b20c459..41a14f74531c3a 100644
--- a/packages/core-data/src/fetch/__experimental-fetch-link-suggestions.js
+++ b/packages/core-data/src/fetch/__experimental-fetch-link-suggestions.js
@@ -179,10 +179,11 @@ const fetchLinkSuggestions = async (
if ( ! type || type === 'attachment' ) {
queries.push(
apiFetch( {
- path: addQueryArgs( '/wp/v2/media', {
+ path: addQueryArgs( '/wp/v2/search', {
search,
page,
per_page: perPage,
+ type: 'media',
} ),
} )
.then( ( results ) => {
@@ -229,6 +230,8 @@ const fetchLinkSuggestions = async (
) || __( '(no title)' ),
type: result.subtype || result.type,
kind: result?.meta?.kind,
+ thumbnail: result?.thumbnail,
+ alt_text: result?.alt_text,
};
} );
} );