[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/rest-api/endpoints/ -> class-wp-rest-search-controller.php (source)

   1  <?php
   2  /**
   3   * REST API: WP_REST_Search_Controller class
   4   *
   5   * @package WordPress
   6   * @subpackage REST_API
   7   * @since 5.0.0
   8   */
   9  
  10  /**
  11   * Core class to search through all WordPress content via the REST API.
  12   *
  13   * @since 5.0.0
  14   *
  15   * @see WP_REST_Controller
  16   */
  17  class WP_REST_Search_Controller extends WP_REST_Controller {
  18  
  19      /**
  20       * ID property name.
  21       */
  22      const PROP_ID = 'id';
  23  
  24      /**
  25       * Title property name.
  26       */
  27      const PROP_TITLE = 'title';
  28  
  29      /**
  30       * URL property name.
  31       */
  32      const PROP_URL = 'url';
  33  
  34      /**
  35       * Type property name.
  36       */
  37      const PROP_TYPE = 'type';
  38  
  39      /**
  40       * Subtype property name.
  41       */
  42      const PROP_SUBTYPE = 'subtype';
  43  
  44      /**
  45       * Identifier for the 'any' type.
  46       */
  47      const TYPE_ANY = 'any';
  48  
  49      /**
  50       * Search handlers used by the controller.
  51       *
  52       * @since 5.0.0
  53       * @var WP_REST_Search_Handler[]
  54       */
  55      protected $search_handlers = array();
  56  
  57      /**
  58       * Constructor.
  59       *
  60       * @since 5.0.0
  61       *
  62       * @param array $search_handlers List of search handlers to use in the controller. Each search
  63       *                               handler instance must extend the `WP_REST_Search_Handler` class.
  64       */
  65  	public function __construct( array $search_handlers ) {
  66          $this->namespace = 'wp/v2';
  67          $this->rest_base = 'search';
  68  
  69          foreach ( $search_handlers as $search_handler ) {
  70              if ( ! $search_handler instanceof WP_REST_Search_Handler ) {
  71                  _doing_it_wrong(
  72                      __METHOD__,
  73                      /* translators: %s: PHP class name. */
  74                      sprintf( __( 'REST search handlers must extend the %s class.' ), 'WP_REST_Search_Handler' ),
  75                      '5.0.0'
  76                  );
  77                  continue;
  78              }
  79  
  80              $this->search_handlers[ $search_handler->get_type() ] = $search_handler;
  81          }
  82      }
  83  
  84      /**
  85       * Registers the routes for the search controller.
  86       *
  87       * @since 5.0.0
  88       *
  89       * @see register_rest_route()
  90       */
  91  	public function register_routes() {
  92          register_rest_route(
  93              $this->namespace,
  94              '/' . $this->rest_base,
  95              array(
  96                  array(
  97                      'methods'             => WP_REST_Server::READABLE,
  98                      'callback'            => array( $this, 'get_items' ),
  99                      'permission_callback' => array( $this, 'get_items_permission_check' ),
 100                      'args'                => $this->get_collection_params(),
 101                  ),
 102                  'schema' => array( $this, 'get_public_item_schema' ),
 103              )
 104          );
 105      }
 106  
 107      /**
 108       * Checks if a given request has access to search content.
 109       *
 110       * @since 5.0.0
 111       *
 112       * @param WP_REST_Request $request Full details about the request.
 113       * @return true|WP_Error True if the request has search access, WP_Error object otherwise.
 114       */
 115  	public function get_items_permission_check( $request ) {
 116          return true;
 117      }
 118  
 119      /**
 120       * Retrieves a collection of search results.
 121       *
 122       * @since 5.0.0
 123       *
 124       * @param WP_REST_Request $request Full details about the request.
 125       * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
 126       */
 127  	public function get_items( $request ) {
 128          $handler = $this->get_search_handler( $request );
 129          if ( is_wp_error( $handler ) ) {
 130              return $handler;
 131          }
 132  
 133          $result = $handler->search_items( $request );
 134  
 135          if ( ! isset( $result[ WP_REST_Search_Handler::RESULT_IDS ] ) || ! is_array( $result[ WP_REST_Search_Handler::RESULT_IDS ] ) || ! isset( $result[ WP_REST_Search_Handler::RESULT_TOTAL ] ) ) {
 136              return new WP_Error(
 137                  'rest_search_handler_error',
 138                  __( 'Internal search handler error.' ),
 139                  array( 'status' => 500 )
 140              );
 141          }
 142  
 143          $ids = $result[ WP_REST_Search_Handler::RESULT_IDS ];
 144  
 145          $results = array();
 146  
 147          foreach ( $ids as $id ) {
 148              $data      = $this->prepare_item_for_response( $id, $request );
 149              $results[] = $this->prepare_response_for_collection( $data );
 150          }
 151  
 152          $total     = (int) $result[ WP_REST_Search_Handler::RESULT_TOTAL ];
 153          $page      = (int) $request['page'];
 154          $per_page  = (int) $request['per_page'];
 155          $max_pages = (int) ceil( $total / $per_page );
 156  
 157          if ( $page > $max_pages && $total > 0 ) {
 158              return new WP_Error(
 159                  'rest_search_invalid_page_number',
 160                  __( 'The page number requested is larger than the number of pages available.' ),
 161                  array( 'status' => 400 )
 162              );
 163          }
 164  
 165          $response = rest_ensure_response( $results );
 166          $response->header( 'X-WP-Total', $total );
 167          $response->header( 'X-WP-TotalPages', $max_pages );
 168  
 169          $request_params = $request->get_query_params();
 170          $base           = add_query_arg( urlencode_deep( $request_params ), rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ) );
 171  
 172          if ( $page > 1 ) {
 173              $prev_link = add_query_arg( 'page', $page - 1, $base );
 174              $response->link_header( 'prev', $prev_link );
 175          }
 176          if ( $page < $max_pages ) {
 177              $next_link = add_query_arg( 'page', $page + 1, $base );
 178              $response->link_header( 'next', $next_link );
 179          }
 180  
 181          return $response;
 182      }
 183  
 184      /**
 185       * Prepares a single search result for response.
 186       *
 187       * @since 5.0.0
 188       * @since 5.6.0 The `$id` parameter can accept a string.
 189       * @since 5.9.0 Renamed `$id` to `$item` to match parent class for PHP 8 named parameter support.
 190       *
 191       * @param int|string      $item    ID of the item to prepare.
 192       * @param WP_REST_Request $request Request object.
 193       * @return WP_REST_Response Response object.
 194       */
 195  	public function prepare_item_for_response( $item, $request ) {
 196          // Restores the more descriptive, specific name for use within this method.
 197          $item_id = $item;
 198  
 199          $handler = $this->get_search_handler( $request );
 200          if ( is_wp_error( $handler ) ) {
 201              return new WP_REST_Response();
 202          }
 203  
 204          $fields = $this->get_fields_for_response( $request );
 205  
 206          $data = $handler->prepare_item( $item_id, $fields );
 207          $data = $this->add_additional_fields_to_object( $data, $request );
 208  
 209          $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
 210          $data    = $this->filter_response_by_context( $data, $context );
 211  
 212          $response = rest_ensure_response( $data );
 213  
 214          if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
 215              $links               = $handler->prepare_item_links( $item_id );
 216              $links['collection'] = array(
 217                  'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
 218              );
 219              $response->add_links( $links );
 220          }
 221  
 222          return $response;
 223      }
 224  
 225      /**
 226       * Retrieves the item schema, conforming to JSON Schema.
 227       *
 228       * @since 5.0.0
 229       *
 230       * @return array Item schema data.
 231       */
 232  	public function get_item_schema() {
 233          if ( $this->schema ) {
 234              return $this->add_additional_fields_schema( $this->schema );
 235          }
 236  
 237          $types    = array();
 238          $subtypes = array();
 239  
 240          foreach ( $this->search_handlers as $search_handler ) {
 241              $types[]  = $search_handler->get_type();
 242              $subtypes = array_merge( $subtypes, $search_handler->get_subtypes() );
 243          }
 244  
 245          $types    = array_unique( $types );
 246          $subtypes = array_unique( $subtypes );
 247  
 248          $schema = array(
 249              '$schema'    => 'http://json-schema.org/draft-04/schema#',
 250              'title'      => 'search-result',
 251              'type'       => 'object',
 252              'properties' => array(
 253                  self::PROP_ID      => array(
 254                      'description' => __( 'Unique identifier for the object.' ),
 255                      'type'        => array( 'integer', 'string' ),
 256                      'context'     => array( 'view', 'embed' ),
 257                      'readonly'    => true,
 258                  ),
 259                  self::PROP_TITLE   => array(
 260                      'description' => __( 'The title for the object.' ),
 261                      'type'        => 'string',
 262                      'context'     => array( 'view', 'embed' ),
 263                      'readonly'    => true,
 264                  ),
 265                  self::PROP_URL     => array(
 266                      'description' => __( 'URL to the object.' ),
 267                      'type'        => 'string',
 268                      'format'      => 'uri',
 269                      'context'     => array( 'view', 'embed' ),
 270                      'readonly'    => true,
 271                  ),
 272                  self::PROP_TYPE    => array(
 273                      'description' => __( 'Object type.' ),
 274                      'type'        => 'string',
 275                      'enum'        => $types,
 276                      'context'     => array( 'view', 'embed' ),
 277                      'readonly'    => true,
 278                  ),
 279                  self::PROP_SUBTYPE => array(
 280                      'description' => __( 'Object subtype.' ),
 281                      'type'        => 'string',
 282                      'enum'        => $subtypes,
 283                      'context'     => array( 'view', 'embed' ),
 284                      'readonly'    => true,
 285                  ),
 286              ),
 287          );
 288  
 289          $this->schema = $schema;
 290  
 291          return $this->add_additional_fields_schema( $this->schema );
 292      }
 293  
 294      /**
 295       * Retrieves the query params for the search results collection.
 296       *
 297       * @since 5.0.0
 298       *
 299       * @return array Collection parameters.
 300       */
 301  	public function get_collection_params() {
 302          $types    = array();
 303          $subtypes = array();
 304  
 305          foreach ( $this->search_handlers as $search_handler ) {
 306              $types[]  = $search_handler->get_type();
 307              $subtypes = array_merge( $subtypes, $search_handler->get_subtypes() );
 308          }
 309  
 310          $types    = array_unique( $types );
 311          $subtypes = array_unique( $subtypes );
 312  
 313          $query_params = parent::get_collection_params();
 314  
 315          $query_params['context']['default'] = 'view';
 316  
 317          $query_params[ self::PROP_TYPE ] = array(
 318              'default'     => $types[0],
 319              'description' => __( 'Limit results to items of an object type.' ),
 320              'type'        => 'string',
 321              'enum'        => $types,
 322          );
 323  
 324          $query_params[ self::PROP_SUBTYPE ] = array(
 325              'default'           => self::TYPE_ANY,
 326              'description'       => __( 'Limit results to items of one or more object subtypes.' ),
 327              'type'              => 'array',
 328              'items'             => array(
 329                  'enum' => array_merge( $subtypes, array( self::TYPE_ANY ) ),
 330                  'type' => 'string',
 331              ),
 332              'sanitize_callback' => array( $this, 'sanitize_subtypes' ),
 333          );
 334  
 335          $query_params['exclude'] = array(
 336              'description' => __( 'Ensure result set excludes specific IDs.' ),
 337              'type'        => 'array',
 338              'items'       => array(
 339                  'type' => 'integer',
 340              ),
 341              'default'     => array(),
 342          );
 343  
 344          $query_params['include'] = array(
 345              'description' => __( 'Limit result set to specific IDs.' ),
 346              'type'        => 'array',
 347              'items'       => array(
 348                  'type' => 'integer',
 349              ),
 350              'default'     => array(),
 351          );
 352  
 353          return $query_params;
 354      }
 355  
 356      /**
 357       * Sanitizes the list of subtypes, to ensure only subtypes of the passed type are included.
 358       *
 359       * @since 5.0.0
 360       *
 361       * @param string|array    $subtypes  One or more subtypes.
 362       * @param WP_REST_Request $request   Full details about the request.
 363       * @param string          $parameter Parameter name.
 364       * @return string[]|WP_Error List of valid subtypes, or WP_Error object on failure.
 365       */
 366  	public function sanitize_subtypes( $subtypes, $request, $parameter ) {
 367          $subtypes = wp_parse_slug_list( $subtypes );
 368  
 369          $subtypes = rest_parse_request_arg( $subtypes, $request, $parameter );
 370          if ( is_wp_error( $subtypes ) ) {
 371              return $subtypes;
 372          }
 373  
 374          // 'any' overrides any other subtype.
 375          if ( in_array( self::TYPE_ANY, $subtypes, true ) ) {
 376              return array( self::TYPE_ANY );
 377          }
 378  
 379          $handler = $this->get_search_handler( $request );
 380          if ( is_wp_error( $handler ) ) {
 381              return $handler;
 382          }
 383  
 384          return array_intersect( $subtypes, $handler->get_subtypes() );
 385      }
 386  
 387      /**
 388       * Gets the search handler to handle the current request.
 389       *
 390       * @since 5.0.0
 391       *
 392       * @param WP_REST_Request $request Full details about the request.
 393       * @return WP_REST_Search_Handler|WP_Error Search handler for the request type, or WP_Error object on failure.
 394       */
 395  	protected function get_search_handler( $request ) {
 396          $type = $request->get_param( self::PROP_TYPE );
 397  
 398          if ( ! $type || ! is_string( $type ) || ! isset( $this->search_handlers[ $type ] ) ) {
 399              return new WP_Error(
 400                  'rest_search_invalid_type',
 401                  __( 'Invalid type parameter.' ),
 402                  array( 'status' => 400 )
 403              );
 404          }
 405  
 406          return $this->search_handlers[ $type ];
 407      }
 408  }


Generated : Fri Apr 19 08:20:01 2024 Cross-referenced by PHPXref