diff --git a/lib/rest-api.php b/lib/rest-api.php
index 424927acf1f4a0..f79724492132cd 100644
--- a/lib/rest-api.php
+++ b/lib/rest-api.php
@@ -36,3 +36,23 @@ function gutenberg_register_edit_site_export_controller_endpoints() {
$edit_site_export_controller->register_routes();
}
add_action( 'rest_api_init', 'gutenberg_register_edit_site_export_controller_endpoints' );
+
+
+function gutenberg_register_archive_link_field() {
+ register_rest_field(
+ 'type',
+ 'archive_link',
+ array(
+ 'get_callback' => static function ( $post_object ) {
+ return (string) get_post_type_archive_link( $post_object['slug'] );
+ },
+ 'update_callback' => null,
+ 'schema' => array(
+ 'description' => __( 'Link to the post type archive page', 'default' ),
+ 'type' => 'string',
+ 'context' => array( 'view', 'edit' ),
+ ),
+ )
+ );
+}
+add_action( 'rest_api_init', 'gutenberg_register_archive_link_field' );
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 27084e5e77b96a..e4fd82fe02782a 100644
--- a/packages/block-editor/src/components/link-control/search-item.js
+++ b/packages/block-editor/src/components/link-control/search-item.js
@@ -13,6 +13,7 @@ import {
file,
home,
verse,
+ customPostType,
} from '@wordpress/icons';
import { __unstableStripHTML as stripHTML } from '@wordpress/dom';
import { safeDecodeURI, filterURLForDisplay, getPath } from '@wordpress/url';
@@ -42,18 +43,16 @@ function SearchItemIcon( { isURL, suggestion } ) {
icon = verse;
}
}
+ } else {
+ icon = customPostType;
}
- if ( icon ) {
- return (
-
- );
- }
-
- return null;
+ return (
+
+ );
}
/**
@@ -156,6 +155,10 @@ function getVisualTypeName( suggestion ) {
return 'blog home';
}
+ if ( suggestion.kind === 'post-type-archive' ) {
+ return 'archive';
+ }
+
// Rename 'post_tag' to 'tag'. Ideally, the API would return the localised CPT or taxonomy label.
return suggestion.type === 'post_tag' ? 'tag' : suggestion.type;
}
diff --git a/packages/block-library/src/navigation-link/index.php b/packages/block-library/src/navigation-link/index.php
index 81df2099dfc188..92f5115c1300de 100644
--- a/packages/block-library/src/navigation-link/index.php
+++ b/packages/block-library/src/navigation-link/index.php
@@ -386,6 +386,7 @@ function block_core_navigation_link_filter_variations( $variations, $block_type
* @return array
*/
function block_core_navigation_link_build_variations() {
+
$post_types = get_post_types( array( 'show_in_nav_menus' => true ), 'objects' );
$taxonomies = get_taxonomies( array( 'show_in_nav_menus' => true ), 'objects' );
@@ -407,6 +408,28 @@ function block_core_navigation_link_build_variations() {
$variations[] = $variation;
}
}
+
+ // If any of the post types have `has_archive` set to true then add a post-type-archive variation.
+ $has_archive = array();
+
+ foreach ( $post_types as $post_type ) {
+ if ( $post_type->has_archive ) {
+ $has_archive[] = $post_type;
+ }
+ }
+
+ if ( $has_archive ) {
+ $variation = array(
+ 'name' => 'post-type-archive',
+ 'title' => __( 'Post Type Archive Link' ),
+ 'description' => __( 'A link to a post type archive' ),
+ 'attributes' => array(
+ 'type' => 'all',
+ 'kind' => 'post-type-archive',
+ ),
+ );
+ $variations[] = $variation;
+ }
}
if ( $taxonomies ) {
foreach ( $taxonomies as $taxonomy ) {
diff --git a/packages/block-library/src/navigation-link/link-ui.js b/packages/block-library/src/navigation-link/link-ui.js
index 52db034c6f980c..3637438c7a8b0e 100644
--- a/packages/block-library/src/navigation-link/link-ui.js
+++ b/packages/block-library/src/navigation-link/link-ui.js
@@ -63,6 +63,9 @@ export function getSuggestionsQuery( type, kind ) {
if ( kind === 'taxonomy' ) {
return { type: 'term', subtype: type };
}
+ if ( kind === 'post-type-archive' ) {
+ return { type: 'post-type-archive' };
+ }
if ( kind === 'post-type' ) {
return { type: 'post', subtype: type };
}
diff --git a/packages/core-data/src/fetch/__experimental-fetch-link-suggestions.ts b/packages/core-data/src/fetch/__experimental-fetch-link-suggestions.ts
index 29012197589035..198bb71884ae06 100644
--- a/packages/core-data/src/fetch/__experimental-fetch-link-suggestions.ts
+++ b/packages/core-data/src/fetch/__experimental-fetch-link-suggestions.ts
@@ -21,7 +21,7 @@ export type SearchOptions = {
/**
* Filters by search type.
*/
- type?: 'attachment' | 'post' | 'term' | 'post-format';
+ type?: 'attachment' | 'post' | 'term' | 'post-format' | 'post-type-archive';
/**
* Slug of the post-type or taxonomy.
*/
@@ -58,6 +58,12 @@ type MediaAPIResult = {
type: string;
};
+type PostTypesAPIResult = {
+ slug: string;
+ name: string;
+ archive_link: string;
+};
+
export type SearchResult = {
/**
* Post or term id.
@@ -240,6 +246,31 @@ export default async function fetchLinkSuggestions(
);
}
+ if ( ! type || type === 'post-type-archive' ) {
+ queries.push(
+ apiFetch< PostTypesAPIResult[] >( {
+ path: addQueryArgs( '/wp/v2/types' ),
+ } )
+ .then( ( results ) => {
+ const resultValues = Object.values( results );
+ return resultValues
+ .filter( ( result ) => !! result.archive_link ) // Filter out results with falsy archive_link, including empty strings
+ .map( ( result, index ) => {
+ return {
+ id: index + 1, // avoid results being filtered due to falsy id
+ url: result.archive_link,
+ title:
+ decodeEntities( result.name || '' ) ||
+ __( '(no title)' ),
+ type: result.slug,
+ kind: 'post-type-archive',
+ };
+ } );
+ } )
+ .catch( () => [] ) // Fail by returning no results.
+ );
+ }
+
const responses = await Promise.all( queries );
let results = responses.flat();
diff --git a/packages/core-data/src/fetch/test/__experimental-fetch-link-suggestions.js b/packages/core-data/src/fetch/test/__experimental-fetch-link-suggestions.js
index ad0014ff86ecb8..182a97fa306ff5 100644
--- a/packages/core-data/src/fetch/test/__experimental-fetch-link-suggestions.js
+++ b/packages/core-data/src/fetch/test/__experimental-fetch-link-suggestions.js
@@ -86,6 +86,16 @@ jest.mock( '@wordpress/api-fetch', () =>
'http://localhost:8888/wp-content/uploads/2022/03/test-pdf.pdf',
},
] );
+ case '/wp/v2/search?search=&per_page=20&type=post-type-archive':
+ return Promise.resolve( [
+ {
+ id: 'books',
+ title: 'All Books',
+ url: 'http://wordpress.local/books/',
+ type: 'books-archive',
+ kind: 'post-type-archive',
+ },
+ ] );
default:
return Promise.resolve( [
{
@@ -187,7 +197,7 @@ describe( 'fetchLinkSuggestions', () => {
);
} );
- it( 'returns suggestions from post, term, post-format and media', () => {
+ it( 'returns suggestions from post, term, post-format, media and post-type-archive', () => {
return fetchLinkSuggestions( '', {} ).then( ( suggestions ) =>
expect( suggestions ).toEqual( [
{
@@ -232,6 +242,13 @@ describe( 'fetchLinkSuggestions', () => {
type: 'attachment',
kind: 'media',
},
+ {
+ id: 'books',
+ title: 'All Books',
+ url: 'http://wordpress.local/books/',
+ type: 'books-archive',
+ kind: 'post-type-archive',
+ },
] )
);
} );