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


Generated : Sun Oct 11 08:20:32 2026 Cross-referenced by PHPXref