Diff: STRATO-apps/wordpress_03/app/wp-includes/rest-api/endpoints/class-wp-rest-search-controller.php

Keine Baseline-Datei – Diff nur gegen leer.
Zur Liste
1 -
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 + $is_head_request = $request->is_method( 'HEAD' );
146 + if ( ! $is_head_request ) {
147 + $results = array();
148 +
149 + foreach ( $ids as $id ) {
150 + $data = $this->prepare_item_for_response( $id, $request );
151 + $results[] = $this->prepare_response_for_collection( $data );
152 + }
153 + }
154 +
155 + $total = (int) $result[ WP_REST_Search_Handler::RESULT_TOTAL ];
156 + $page = (int) $request['page'];
157 + $per_page = (int) $request['per_page'];
158 + $max_pages = (int) ceil( $total / $per_page );
159 +
160 + if ( $page > $max_pages && $total > 0 ) {
161 + return new WP_Error(
162 + 'rest_search_invalid_page_number',
163 + __( 'The page number requested is larger than the number of pages available.' ),
164 + array( 'status' => 400 )
165 + );
166 + }
167 +
168 + $response = $is_head_request ? new WP_REST_Response( array() ) : rest_ensure_response( $results );
169 + $response->header( 'X-WP-Total', $total );
170 + $response->header( 'X-WP-TotalPages', $max_pages );
171 +
172 + $request_params = $request->get_query_params();
173 + $base = add_query_arg( urlencode_deep( $request_params ), rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ) );
174 +
175 + if ( $page > 1 ) {
176 + $prev_link = add_query_arg( 'page', $page - 1, $base );
177 + $response->link_header( 'prev', $prev_link );
178 + }
179 + if ( $page < $max_pages ) {
180 + $next_link = add_query_arg( 'page', $page + 1, $base );
181 + $response->link_header( 'next', $next_link );
182 + }
183 +
184 + return $response;
185 + }
186 +
187 + /**
188 + * Prepares a single search result for response.
189 + *
190 + * @since 5.0.0
191 + * @since 5.6.0 The `$id` parameter can accept a string.
192 + * @since 5.9.0 Renamed `$id` to `$item` to match parent class for PHP 8 named parameter support.
193 + *
194 + * @param int|string $item ID of the item to prepare.
195 + * @param WP_REST_Request $request Request object.
196 + * @return WP_REST_Response Response object.
197 + */
198 + public function prepare_item_for_response( $item, $request ) {
199 + // Restores the more descriptive, specific name for use within this method.
200 + $item_id = $item;
201 +
202 + $handler = $this->get_search_handler( $request );
203 + if ( is_wp_error( $handler ) ) {
204 + return new WP_REST_Response();
205 + }
206 +
207 + $fields = $this->get_fields_for_response( $request );
208 +
209 + $data = $handler->prepare_item( $item_id, $fields );
210 + $data = $this->add_additional_fields_to_object( $data, $request );
211 +
212 + $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
213 + $data = $this->filter_response_by_context( $data, $context );
214 +
215 + $response = rest_ensure_response( $data );
216 +
217 + if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
218 + $links = $handler->prepare_item_links( $item_id );
219 + $links['collection'] = array(
220 + 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
221 + );
222 + $response->add_links( $links );
223 + }
224 +
225 + return $response;
226 + }
227 +
228 + /**
229 + * Retrieves the item schema, conforming to JSON Schema.
230 + *
231 + * @since 5.0.0
232 + *
233 + * @return array Item schema data.
234 + */
235 + public function get_item_schema() {
236 + if ( $this->schema ) {
237 + return $this->add_additional_fields_schema( $this->schema );
238 + }
239 +
240 + $types = array();
241 + $subtypes = array();
242 +
243 + foreach ( $this->search_handlers as $search_handler ) {
244 + $types[] = $search_handler->get_type();
245 + $subtypes = array_merge( $subtypes, $search_handler->get_subtypes() );
246 + }
247 +
248 + $types = array_unique( $types );
249 + $subtypes = array_unique( $subtypes );
250 +
251 + $schema = array(
252 + '$schema' => 'http://json-schema.org/draft-04/schema#',
253 + 'title' => 'search-result',
254 + 'type' => 'object',
255 + 'properties' => array(
256 + self::PROP_ID => array(
257 + 'description' => __( 'Unique identifier for the object.' ),
258 + 'type' => array( 'integer', 'string' ),
259 + 'context' => array( 'view', 'embed' ),
260 + 'readonly' => true,
261 + ),
262 + self::PROP_TITLE => array(
263 + 'description' => __( 'The title for the object.' ),
264 + 'type' => 'string',
265 + 'context' => array( 'view', 'embed' ),
266 + 'readonly' => true,
267 + ),
268 + self::PROP_URL => array(
269 + 'description' => __( 'URL to the object.' ),
270 + 'type' => 'string',
271 + 'format' => 'uri',
272 + 'context' => array( 'view', 'embed' ),
273 + 'readonly' => true,
274 + ),
275 + self::PROP_TYPE => array(
276 + 'description' => __( 'Object type.' ),
277 + 'type' => 'string',
278 + 'enum' => $types,
279 + 'context' => array( 'view', 'embed' ),
280 + 'readonly' => true,
281 + ),
282 + self::PROP_SUBTYPE => array(
283 + 'description' => __( 'Object subtype.' ),
284 + 'type' => 'string',
285 + 'enum' => $subtypes,
286 + 'context' => array( 'view', 'embed' ),
287 + 'readonly' => true,
288 + ),
289 + ),
290 + );
291 +
292 + $this->schema = $schema;
293 +
294 + return $this->add_additional_fields_schema( $this->schema );
295 + }
296 +
297 + /**
298 + * Retrieves the query params for the search results collection.
299 + *
300 + * @since 5.0.0
301 + *
302 + * @return array Collection parameters.
303 + */
304 + public function get_collection_params() {
305 + $types = array();
306 + $subtypes = array();
307 +
308 + foreach ( $this->search_handlers as $search_handler ) {
309 + $types[] = $search_handler->get_type();
310 + $subtypes = array_merge( $subtypes, $search_handler->get_subtypes() );
311 + }
312 +
313 + $types = array_unique( $types );
314 + $subtypes = array_unique( $subtypes );
315 +
316 + $query_params = parent::get_collection_params();
317 +
318 + $query_params['context']['default'] = 'view';
319 +
320 + $query_params[ self::PROP_TYPE ] = array(
321 + 'default' => $types[0],
322 + 'description' => __( 'Limit results to items of an object type.' ),
323 + 'type' => 'string',
324 + 'enum' => $types,
325 + );
326 +
327 + $query_params[ self::PROP_SUBTYPE ] = array(
328 + 'default' => self::TYPE_ANY,
329 + 'description' => __( 'Limit results to items of one or more object subtypes.' ),
330 + 'type' => 'array',
331 + 'items' => array(
332 + 'enum' => array_merge( $subtypes, array( self::TYPE_ANY ) ),
333 + 'type' => 'string',
334 + ),
335 + 'sanitize_callback' => array( $this, 'sanitize_subtypes' ),
336 + );
337 +
338 + $query_params['exclude'] = array(
339 + 'description' => __( 'Ensure result set excludes specific IDs.' ),
340 + 'type' => 'array',
341 + 'items' => array(
342 + 'type' => 'integer',
343 + ),
344 + 'default' => array(),
345 + );
346 +
347 + $query_params['include'] = array(
348 + 'description' => __( 'Limit result set to specific IDs.' ),
349 + 'type' => 'array',
350 + 'items' => array(
351 + 'type' => 'integer',
352 + ),
353 + 'default' => array(),
354 + );
355 +
356 + return $query_params;
357 + }
358 +
359 + /**
360 + * Sanitizes the list of subtypes, to ensure only subtypes of the passed type are included.
361 + *
362 + * @since 5.0.0
363 + *
364 + * @param string|array $subtypes One or more subtypes.
365 + * @param WP_REST_Request $request Full details about the request.
366 + * @param string $parameter Parameter name.
367 + * @return string[]|WP_Error List of valid subtypes, or WP_Error object on failure.
368 + */
369 + public function sanitize_subtypes( $subtypes, $request, $parameter ) {
370 + $subtypes = wp_parse_slug_list( $subtypes );
371 +
372 + $subtypes = rest_parse_request_arg( $subtypes, $request, $parameter );
373 + if ( is_wp_error( $subtypes ) ) {
374 + return $subtypes;
375 + }
376 +
377 + // 'any' overrides any other subtype.
378 + if ( in_array( self::TYPE_ANY, $subtypes, true ) ) {
379 + return array( self::TYPE_ANY );
380 + }
381 +
382 + $handler = $this->get_search_handler( $request );
383 + if ( is_wp_error( $handler ) ) {
384 + return $handler;
385 + }
386 +
387 + return array_intersect( $subtypes, $handler->get_subtypes() );
388 + }
389 +
390 + /**
391 + * Gets the search handler to handle the current request.
392 + *
393 + * @since 5.0.0
394 + *
395 + * @param WP_REST_Request $request Full details about the request.
396 + * @return WP_REST_Search_Handler|WP_Error Search handler for the request type, or WP_Error object on failure.
397 + */
398 + protected function get_search_handler( $request ) {
399 + $type = $request->get_param( self::PROP_TYPE );
400 +
401 + if ( ! $type || ! is_string( $type ) || ! isset( $this->search_handlers[ $type ] ) ) {
402 + return new WP_Error(
403 + 'rest_search_invalid_type',
404 + __( 'Invalid type parameter.' ),
405 + array( 'status' => 400 )
406 + );
407 + }
408 +
409 + return $this->search_handlers[ $type ];
410 + }
411 + }
412 +