[ Index ]

PHP Cross Reference of WordPress

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 = 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          $handler = $this->get_search_handler( $request );
 199          if ( is_wp_error( $handler ) ) {
 200              return new WP_REST_Response();
 201          }
 202  
 203          $fields = $this->get_fields_for_response( $request );
 204  
 205          $data = $handler->prepare_item( $item_id, $fields );
 206          $data = $this->add_additional_fields_to_object( $data, $request );
 207  
 208          $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
 209          $data    = $this->filter_response_by_context( $data, $context );
 210  
 211          $response = rest_ensure_response( $data );
 212  
 213          $links               = $handler->prepare_item_links( $item_id );
 214          $links['collection'] = array(
 215              'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
 216          );
 217          $response->add_links( $links );
 218  
 219          return $response;
 220      }
 221  
 222      /**
 223       * Retrieves the item schema, conforming to JSON Schema.
 224       *
 225       * @since 5.0.0
 226       *
 227       * @return array Item schema data.
 228       */
 229  	public function get_item_schema() {
 230          if ( $this->schema ) {
 231              return $this->add_additional_fields_schema( $this->schema );
 232          }
 233  
 234          $types    = array();
 235          $subtypes = array();
 236  
 237          foreach ( $this->search_handlers as $search_handler ) {
 238              $types[]  = $search_handler->get_type();
 239              $subtypes = array_merge( $subtypes, $search_handler->get_subtypes() );
 240          }
 241  
 242          $types    = array_unique( $types );
 243          $subtypes = array_unique( $subtypes );
 244  
 245          $schema = array(
 246              '$schema'    => 'http://json-schema.org/draft-04/schema#',
 247              'title'      => 'search-result',
 248              'type'       => 'object',
 249              'properties' => array(
 250                  self::PROP_ID      => array(
 251                      'description' => __( 'Unique identifier for the object.' ),
 252                      'type'        => array( 'integer', 'string' ),
 253                      'context'     => array( 'view', 'embed' ),
 254                      'readonly'    => true,
 255                  ),
 256                  self::PROP_TITLE   => array(
 257                      'description' => __( 'The title for the object.' ),
 258                      'type'        => 'string',
 259                      'context'     => array( 'view', 'embed' ),
 260                      'readonly'    => true,
 261                  ),
 262                  self::PROP_URL     => array(
 263                      'description' => __( 'URL to the object.' ),
 264                      'type'        => 'string',
 265                      'format'      => 'uri',
 266                      'context'     => array( 'view', 'embed' ),
 267                      'readonly'    => true,
 268                  ),
 269                  self::PROP_TYPE    => array(
 270                      'description' => __( 'Object type.' ),
 271                      'type'        => 'string',
 272                      'enum'        => $types,
 273                      'context'     => array( 'view', 'embed' ),
 274                      'readonly'    => true,
 275                  ),
 276                  self::PROP_SUBTYPE => array(
 277                      'description' => __( 'Object subtype.' ),
 278                      'type'        => 'string',
 279                      'enum'        => $subtypes,
 280                      'context'     => array( 'view', 'embed' ),
 281                      'readonly'    => true,
 282                  ),
 283              ),
 284          );
 285  
 286          $this->schema = $schema;
 287  
 288          return $this->add_additional_fields_schema( $this->schema );
 289      }
 290  
 291      /**
 292       * Retrieves the query params for the search results collection.
 293       *
 294       * @since 5.0.0
 295       *
 296       * @return array Collection parameters.
 297       */
 298  	public function get_collection_params() {
 299          $types    = array();
 300          $subtypes = array();
 301  
 302          foreach ( $this->search_handlers as $search_handler ) {
 303              $types[]  = $search_handler->get_type();
 304              $subtypes = array_merge( $subtypes, $search_handler->get_subtypes() );
 305          }
 306  
 307          $types    = array_unique( $types );
 308          $subtypes = array_unique( $subtypes );
 309  
 310          $query_params = parent::get_collection_params();
 311  
 312          $query_params['context']['default'] = 'view';
 313  
 314          $query_params[ self::PROP_TYPE ] = array(
 315              'default'     => $types[0],
 316              'description' => __( 'Limit results to items of an object type.' ),
 317              'type'        => 'string',
 318              'enum'        => $types,
 319          );
 320  
 321          $query_params[ self::PROP_SUBTYPE ] = array(
 322              'default'           => self::TYPE_ANY,
 323              'description'       => __( 'Limit results to items of one or more object subtypes.' ),
 324              'type'              => 'array',
 325              'items'             => array(
 326                  'enum' => array_merge( $subtypes, array( self::TYPE_ANY ) ),
 327                  'type' => 'string',
 328              ),
 329              'sanitize_callback' => array( $this, 'sanitize_subtypes' ),
 330          );
 331  
 332          return $query_params;
 333      }
 334  
 335      /**
 336       * Sanitizes the list of subtypes, to ensure only subtypes of the passed type are included.
 337       *
 338       * @since 5.0.0
 339       *
 340       * @param string|array    $subtypes  One or more subtypes.
 341       * @param WP_REST_Request $request   Full details about the request.
 342       * @param string          $parameter Parameter name.
 343       * @return array|WP_Error List of valid subtypes, or WP_Error object on failure.
 344       */
 345  	public function sanitize_subtypes( $subtypes, $request, $parameter ) {
 346          $subtypes = wp_parse_slug_list( $subtypes );
 347  
 348          $subtypes = rest_parse_request_arg( $subtypes, $request, $parameter );
 349          if ( is_wp_error( $subtypes ) ) {
 350              return $subtypes;
 351          }
 352  
 353          // 'any' overrides any other subtype.
 354          if ( in_array( self::TYPE_ANY, $subtypes, true ) ) {
 355              return array( self::TYPE_ANY );
 356          }
 357  
 358          $handler = $this->get_search_handler( $request );
 359          if ( is_wp_error( $handler ) ) {
 360              return $handler;
 361          }
 362  
 363          return array_intersect( $subtypes, $handler->get_subtypes() );
 364      }
 365  
 366      /**
 367       * Gets the search handler to handle the current request.
 368       *
 369       * @since 5.0.0
 370       *
 371       * @param WP_REST_Request $request Full details about the request.
 372       * @return WP_REST_Search_Handler|WP_Error Search handler for the request type, or WP_Error object on failure.
 373       */
 374  	protected function get_search_handler( $request ) {
 375          $type = $request->get_param( self::PROP_TYPE );
 376  
 377          if ( ! $type || ! isset( $this->search_handlers[ $type ] ) ) {
 378              return new WP_Error(
 379                  'rest_search_invalid_type',
 380                  __( 'Invalid type parameter.' ),
 381                  array( 'status' => 400 )
 382              );
 383          }
 384  
 385          return $this->search_handlers[ $type ];
 386      }
 387  }


Generated: Tue Mar 19 01:00:02 2024 Cross-referenced by PHPXref 0.7.1