Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement forward-compatible REST API search controller #7894

Merged
merged 11 commits into from
Jul 24, 2018
Merged
6 changes: 3 additions & 3 deletions editor/components/url-input/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,10 @@ class URLInput extends Component {
} );

const request = apiFetch( {
path: `/wp/v2/posts?${ stringify( {
path: `/gutenberg/v1/search?${ stringify( {
search: value,
per_page: 20,
orderby: 'relevance',
type: 'post',
} ) }`,
} );

Expand Down Expand Up @@ -230,7 +230,7 @@ class URLInput extends Component {
onClick={ () => this.selectLink( post.link ) }
aria-selected={ index === selectedSuggestion }
>
{ decodeEntities( post.title.rendered ) || __( '(no title)' ) }
{ decodeEntities( post.title ) || __( '(no title)' ) }
</button>
) ) }
</div>
Expand Down
182 changes: 182 additions & 0 deletions lib/class-wp-rest-post-search-handler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
<?php
/**
* REST API: WP_REST_Post_Search_Handler class
*
* @package gutenberg
* @since 3.3.0
*/

/**
* Core class representing a search handler for posts in the REST API.
*
* @since 3.3.0
*/
class WP_REST_Post_Search_Handler extends WP_REST_Search_Handler {

/**
* Constructor.
*
* @since 3.3.0
*/
public function __construct() {
$this->type = 'post';

// Support all public post types except attachments.
$this->subtypes = array_diff( array_values( get_post_types( array(
'public' => true,
'show_in_rest' => true,
), 'names' ) ), array( 'attachment' ) );
}

/**
* Searches the object type content for a given search request.
*
* @since 3.3.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 ) {

// Get the post types to search for the current request.
$post_types = $request[ WP_REST_Search_Controller::PROP_SUBTYPE ];
if ( in_array( WP_REST_Search_Controller::TYPE_ANY, $post_types, true ) ) {
$post_types = $this->subtypes;
}

$query_args = array(
'post_type' => $post_types,
'post_status' => 'publish',
'paged' => (int) $request['page'],
'posts_per_page' => (int) $request['per_page'],
'ignore_sticky_posts' => true,
'fields' => 'ids',
);

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

$query = new WP_Query();
$found_ids = $query->query( $query_args );
$total = $query->found_posts;

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

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

$data = array();

if ( in_array( WP_REST_Search_Controller::PROP_ID, $fields, true ) ) {
$data[ WP_REST_Search_Controller::PROP_ID ] = (int) $post->ID;
}

if ( in_array( WP_REST_Search_Controller::PROP_TITLE, $fields, true ) ) {
if ( post_type_supports( $post->post_type, 'title' ) ) {
add_filter( 'protected_title_format', array( $this, 'protected_title_format' ) );
$data[ WP_REST_Search_Controller::PROP_TITLE ] = get_the_title( $post->ID );
remove_filter( 'protected_title_format', array( $this, 'protected_title_format' ) );
} else {
$data[ WP_REST_Search_Controller::PROP_TITLE ] = '';
}
}

if ( in_array( WP_REST_Search_Controller::PROP_URL, $fields, true ) ) {
$data[ WP_REST_Search_Controller::PROP_URL ] = get_permalink( $post->ID );
}

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

if ( in_array( WP_REST_Search_Controller::PROP_SUBTYPE, $fields, true ) ) {
$data[ WP_REST_Search_Controller::PROP_SUBTYPE ] = $post->post_type;
}

return $data;
}

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

$links = array();

$item_route = $this->detect_rest_item_route( $post );
if ( ! empty( $item_route ) ) {
$links['self'] = array(
'href' => rest_url( $item_route ),
'embeddable' => true,
);
}

$links['about'] = array(
'href' => rest_url( 'wp/v2/types/' . $post->post_type ),
);

return $links;
}

/**
* Overwrites the default protected title format.
*
* By default, WordPress will show password protected posts with a title of
* "Protected: %s". As the REST API communicates the protected status of a post
* in a machine readable format, we remove the "Protected: " prefix.
*
* @since 3.3.0
*
* @return string Protected title format.
*/
public function protected_title_format() {
return '%s';
}

/**
* Attempts to detect the route to access a single item.
*
* @since 3.3.0
*
* @param WP_Post $post Post object.
* @return string REST route relative to the REST base URI, or empty string if unknown.
*/
protected function detect_rest_item_route( $post ) {
$post_type = get_post_type_object( $post->post_type );
if ( ! $post_type ) {
return '';
}

// It's currently impossible to detect the REST URL from a custom controller.
if ( ! empty( $post_type->rest_controller_class ) && 'WP_REST_Posts_Controller' !== $post_type->rest_controller_class ) {
return '';
}

$namespace = 'wp/v2';
$rest_base = ! empty( $post_type->rest_base ) ? $post_type->rest_base : $post_type->name;

return sprintf( '%s/%s/%d', $namespace, $rest_base, $post->ID );
}
}
Loading