Skip to content

Commit

Permalink
Adds suggestions for categories and formats to LinkControl (#22600)
Browse files Browse the repository at this point in the history
* adds suggestions for categories and formats

adds a format search handler
adds a category search handler
hooks the new handlers in the search endpoint
updates LinkControl's fetchLinkSuggestions to handle formats and categories as well

* adds concurent search for suggestions

also searches for category matches via term query and limits formats with no items

* generalizes the category search handler to all terms

* lint

* adds links to terms search results

* adds default listing for empty searches and a stup test class

* shows empty terms in results for search and tests pass

* REST API: Add pagination to term search handler

* Navigation: Remove unnecessary async

* REST API: Fix doc comments

* REST API: Use null instead of empty string

* REST API: Add pagination to page format search handler

* REST API: Fix term search handler tests

* REST API: Add post format search handler tests

* REST API: Only show term subtypes which are REST API enabled

* REST API: Use WP_Term_Query's 'search' param

* REST API: Use wp_count_terms() instead of WP_Term_Query

* REST API: Set fields=all when fetching terms so that they're cached

* REST API: Use rest_get_route_for_term() instead of custom method

* REST API: Pass forwards-compatible array to rest_post_format_search_query

* REST API: Improve stability of WP_REST_Post_Format_Search_Handler tests

* REST API: Add test for when no term matches search query

* Navigation: Don't call /v2/search?type=post-format if theme does not support formats

* REST API: Fix post format handler tests

Co-authored-by: Robert Anderson <robert@noisysocks.com>
  • Loading branch information
draganescu and noisysocks authored Aug 25, 2020
1 parent d20270f commit a0f36df
Show file tree
Hide file tree
Showing 8 changed files with 809 additions and 25 deletions.
120 changes: 120 additions & 0 deletions lib/class-wp-rest-post-format-search-handler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<?php
/**
* REST API: WP_REST_Post_Format_Search_Handler class
*
* @package Gutenberg
*/

/**
* Core class representing a search handler for post formats in the REST API.
*
* @see WP_REST_Search_Handler
*/
class WP_REST_Post_Format_Search_Handler extends WP_REST_Search_Handler {

/**
* Constructor.
*/
public function __construct() {
$this->type = 'post-format';
}

/**
* Searches the object type content for a given search request.
*
* @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 ) {
$format_strings = get_post_format_strings();
$format_slugs = array_keys( $format_strings );

$query_args = array();

if ( ! empty( $request['search'] ) ) {
$query_args['search'] = $request['search'];
}

/**
* Filters the query arguments for a search request.
*
* Enables adding extra arguments or setting defaults for a post format search request.
*
* @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_post_format_search_query', $query_args, $request );

$found_ids = array();
foreach ( $format_slugs as $index => $format_slug ) {
if ( ! empty( $query_args['search'] ) ) {
$format_string = get_post_format_string( $format_slug );
$format_slug_match = stripos( $format_slug, $query_args['search'] ) !== false;
$format_string_match = stripos( $format_string, $query_args['search'] ) !== false;
if ( ! $format_slug_match && ! $format_string_match ) {
continue;
}
}

$format_link = get_post_format_link( $format_slug );
if ( $format_link ) {
// Formats don't have an ID, so fake one using the array index.
$found_ids[] = $index + 1;
}
}

$page = (int) $request['page'];
$per_page = (int) $request['per_page'];

return array(
self::RESULT_IDS => array_slice( $found_ids, ( $page - 1 ) * $per_page, $per_page ),
self::RESULT_TOTAL => count( $found_ids ),
);
}

/**
* Prepares the search result for a given ID.
*
* @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 ) {
$format_strings = get_post_format_strings();
$format_slugs = array_keys( $format_strings );
$format_slug = $format_slugs[ $id - 1 ];

$data = array();

if ( in_array( WP_REST_Search_Controller::PROP_ID, $fields, true ) ) {
$data[ WP_REST_Search_Controller::PROP_ID ] = $id;
}

if ( in_array( WP_REST_Search_Controller::PROP_TITLE, $fields, true ) ) {
$data[ WP_REST_Search_Controller::PROP_TITLE ] = get_post_format_string( $format_slug );
}

if ( in_array( WP_REST_Search_Controller::PROP_URL, $fields, true ) ) {
$data[ WP_REST_Search_Controller::PROP_URL ] = get_post_format_link( $format_slug );
}

if ( in_array( WP_REST_Search_Controller::PROP_TYPE, $fields, true ) ) {
$data[ WP_REST_Search_Controller::PROP_TYPE ] = $this->type;
}

return $data;
}

/**
* Prepares links for the search result.
*
* @param string $id Item ID.
* @return array Links for the given item.
*/
public function prepare_item_links( $id ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
return array();
}

}
142 changes: 142 additions & 0 deletions lib/class-wp-rest-term-search-handler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
<?php
/**
* REST API: WP_REST_Terms_Search_Handler class
*
* @package Gutenberg
*/

/**
* Core class representing a search handler for term in the REST API.
*
* @see WP_REST_Search_Handler
*/
class WP_REST_Term_Search_Handler extends WP_REST_Search_Handler {

/**
* Constructor.
*/
public function __construct() {
$this->type = 'term';

$this->subtypes = array_values(
get_taxonomies(
array(
'public' => true,
'show_in_rest' => true,
),
'names'
)
);
}

/**
* Searches the object type content for a given search request.
*
* @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 ) {
$taxonomies = $request[ WP_REST_Search_Controller::PROP_SUBTYPE ];
if ( in_array( WP_REST_Search_Controller::TYPE_ANY, $taxonomies, true ) ) {
$taxonomies = $this->subtypes;
}

$page = (int) $request['page'];
$per_page = (int) $request['per_page'];

$query_args = array(
'taxonomy' => $taxonomies,
'hide_empty' => false,
'offset' => ( $page - 1 ) * $per_page,
'number' => $per_page,
);

if ( ! empty( $request['search'] ) ) {
$query_args['search'] = $request['search'];
}

/**
* Filters the query arguments for a search request.
*
* Enables adding extra arguments or setting defaults for a term search request.
*
* @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_term_search_query', $query_args, $request );

$query = new WP_Term_Query();
$found_terms = $query->query( $query_args );
$found_ids = wp_list_pluck( $found_terms, 'term_id' );

unset( $query_args['offset'], $query_args['number'] );

$total = wp_count_terms( $query_args );

// wp_count_terms() can return a falsey value when the term has no children.
if ( ! $total ) {
$total = 0;
}

return array(
self::RESULT_IDS => $found_ids,
self::RESULT_TOTAL => $total,
);
}

/**
* Prepares the search result for a given ID.
*
* @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 ) {
$term = get_term( $id );

$data = array();

if ( in_array( WP_REST_Search_Controller::PROP_ID, $fields, true ) ) {
$data[ WP_REST_Search_Controller::PROP_ID ] = (int) $id;
}
if ( in_array( WP_REST_Search_Controller::PROP_TITLE, $fields, true ) ) {
$data[ WP_REST_Search_Controller::PROP_TITLE ] = $term->name;
}
if ( in_array( WP_REST_Search_Controller::PROP_URL, $fields, true ) ) {
$data[ WP_REST_Search_Controller::PROP_URL ] = get_term_link( $id );
}
if ( in_array( WP_REST_Search_Controller::PROP_TYPE, $fields, true ) ) {
$data[ WP_REST_Search_Controller::PROP_TYPE ] = $term->taxonomy;
}

return $data;
}

/**
* Prepares links for the search result of a given ID.
*
* @param int $id Item ID.
* @return array Links for the given item.
*/
public function prepare_item_links( $id ) {
$term = get_term( $id );

$links = array();

$item_route = rest_get_route_for_term( $term );
if ( $item_route ) {
$links['self'] = array(
'href' => rest_url( $item_route ),
'embeddable' => true,
);
}

$links['about'] = array(
'href' => rest_url( sprintf( 'wp/v2/taxonomies/%s', $term->taxonomy ) ),
);

return $links;
}
}
6 changes: 6 additions & 0 deletions lib/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ function gutenberg_is_experiment_enabled( $name ) {
if ( ! class_exists( 'WP_REST_Plugins_Controller' ) ) {
require_once dirname( __FILE__ ) . '/class-wp-rest-plugins-controller.php';
}
if ( ! class_exists( 'WP_REST_Post_Format_Search_Handler' ) ) {
require_once dirname( __FILE__ ) . '/class-wp-rest-post-format-search-handler.php';
}
if ( ! class_exists( 'WP_REST_Term_Search_Handler' ) ) {
require_once dirname( __FILE__ ) . '/class-wp-rest-term-search-handler.php';
}
/**
* End: Include for phase 2
*/
Expand Down
29 changes: 29 additions & 0 deletions lib/rest-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -330,3 +330,32 @@ function gutenberg_register_image_editor() {
}
}
add_filter( 'rest_api_init', 'gutenberg_register_image_editor' );

/**
* Registers the post format search handler.
*
* @param string $search_handlers Title list of current handlers.
*
* @return array Title updated list of handlers.
*/
function gutenberg_post_format_search_handler( $search_handlers ) {
if ( current_theme_supports( 'post-formats' ) ) {
$search_handlers[] = new WP_REST_Post_Format_Search_Handler();
}

return $search_handlers;
}
add_filter( 'wp_rest_search_handlers', 'gutenberg_post_format_search_handler', 10, 5 );

/**
* Registers the terms search handler.
*
* @param string $search_handlers Title list of current handlers.
*
* @return array Title updated list of handlers.
*/
function gutenberg_term_search_handler( $search_handlers ) {
$search_handlers[] = new WP_REST_Term_Search_Handler();
return $search_handlers;
}
add_filter( 'wp_rest_search_handlers', 'gutenberg_term_search_handler', 10, 5 );
59 changes: 46 additions & 13 deletions packages/edit-navigation/src/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* External dependencies
*/
import { map, set } from 'lodash';
import { map, set, flatten, partialRight } from 'lodash';

/**
* WordPress dependencies
Expand Down Expand Up @@ -39,26 +39,56 @@ function disableInsertingNonNavigationBlocks( settings, name ) {
* It seems like there is no suitable package to import this from. Ideally it would be either part of core-data.
* Until we refactor it, just copying the code is the simplest solution.
*
* @param {Object} search
* @param {number} perPage
* @param {string} search
* @param {Object} [searchArguments]
* @param {number} [searchArguments.perPage=20]
* @param {Object} [editorSettings]
* @param {boolean} [editorSettings.disablePostFormats=false]
* @return {Promise<Object[]>} List of suggestions
*/
async function fetchLinkSuggestions( search, { perPage = 20 } = {} ) {
const posts = await apiFetch( {
const fetchLinkSuggestions = (
search,
{ perPage = 20 } = {},
{ disablePostFormats = false } = {}
) => {
const posts = apiFetch( {
path: addQueryArgs( '/wp/v2/search', {
search,
per_page: perPage,
type: 'post',
} ),
} );

return map( posts, ( post ) => ( {
id: post.id,
url: post.url,
title: decodeEntities( post.title ) || __( '(no title)' ),
type: post.subtype || post.type,
} ) );
}
const terms = apiFetch( {
path: addQueryArgs( '/wp/v2/search', {
search,
per_page: perPage,
type: 'term',
} ),
} );

let formats;
if ( disablePostFormats ) {
formats = Promise.resolve( [] );
} else {
formats = apiFetch( {
path: addQueryArgs( '/wp/v2/search', {
search,
per_page: perPage,
type: 'post-format',
} ),
} );
}

return Promise.all( [ posts, terms, formats ] ).then( ( results ) => {
return map( flatten( results ).slice( 0, perPage ), ( result ) => ( {
id: result.id,
url: result.url,
title: decodeEntities( result.title ) || __( '(no title)' ),
type: result.subtype || result.type,
} ) );
} );
};

export function initialize( id, settings ) {
if ( ! settings.blockNavMenus ) {
Expand All @@ -75,7 +105,10 @@ export function initialize( id, settings ) {
__experimentalRegisterExperimentalCoreBlocks( settings );
}

settings.__experimentalFetchLinkSuggestions = fetchLinkSuggestions;
settings.__experimentalFetchLinkSuggestions = partialRight(
fetchLinkSuggestions,
settings
);

render(
<Layout blockEditorSettings={ settings } />,
Expand Down
Loading

0 comments on commit a0f36df

Please sign in to comment.