run:R W Run
10.24 KB
2026-03-11 16:18:52
R W Run
6.87 KB
2026-03-11 16:18:52
R W Run
23.75 KB
2026-03-11 16:18:52
R W Run
52.96 KB
2026-03-11 16:18:52
R W Run
14.88 KB
2026-03-11 16:18:52
R W Run
9.71 KB
2026-03-11 16:18:52
R W Run
9.08 KB
2026-03-11 16:18:52
R W Run
5.7 KB
2026-03-11 16:18:52
R W Run
26.25 KB
2026-03-11 16:18:52
R W Run
3.1 KB
2026-03-11 16:18:52
R W Run
61.54 KB
2026-03-11 16:18:52
R W Run
18.62 KB
2026-03-11 16:18:52
R W Run
2.06 KB
2026-03-11 16:18:52
R W Run
10.47 KB
2026-03-11 16:18:52
R W Run
29.11 KB
2026-03-11 16:18:52
R W Run
17.1 KB
2026-03-11 16:18:52
R W Run
20.58 KB
2026-03-11 16:18:52
R W Run
32.49 KB
2026-03-11 16:18:52
R W Run
8.75 KB
2026-03-11 16:18:52
R W Run
16.68 KB
2026-03-11 16:18:52
R W Run
5.05 KB
2026-03-11 16:18:52
R W Run
12.64 KB
2026-03-11 16:18:52
R W Run
27.86 KB
2026-03-11 16:18:52
R W Run
10.07 KB
2026-03-11 16:18:52
R W Run
13.95 KB
2026-03-11 16:18:52
R W Run
100.05 KB
2026-03-11 16:18:52
R W Run
26.17 KB
2026-03-11 16:18:52
R W Run
11.21 KB
2026-03-11 16:18:52
R W Run
10.11 KB
2026-03-11 16:18:52
R W Run
15.82 KB
2026-03-11 16:18:52
R W Run
9.61 KB
2026-03-11 16:18:52
R W Run
13.69 KB
2026-03-11 16:18:52
R W Run
7.64 KB
2026-03-11 16:18:52
R W Run
8.52 KB
2026-03-11 16:18:52
R W Run
37.41 KB
2026-03-11 16:18:52
R W Run
34.61 KB
2026-03-11 16:18:52
R W Run
22.77 KB
2026-03-11 16:18:52
R W Run
20.07 KB
2026-03-11 16:18:52
R W Run
48.72 KB
2026-03-11 16:18:52
R W Run
18.78 KB
2026-03-11 16:18:52
R W Run
26.26 KB
2026-03-11 16:18:52
R W Run
error_log
📄class-wp-rest-controller.php
1<?php
2/**
3 * REST API: WP_REST_Controller class
4 *
5 * @package WordPress
6 * @subpackage REST_API
7 * @since 4.7.0
8 */
9
10/**
11 * Core base controller for managing and interacting with REST API items.
12 *
13 * @since 4.7.0
14 */
15#[AllowDynamicProperties]
16abstract class WP_REST_Controller {
17
18 /**
19 * The namespace of this controller's route.
20 *
21 * @since 4.7.0
22 * @var string
23 */
24 protected $namespace;
25
26 /**
27 * The base of this controller's route.
28 *
29 * @since 4.7.0
30 * @var string
31 */
32 protected $rest_base;
33
34 /**
35 * Cached results of get_item_schema.
36 *
37 * @since 5.3.0
38 * @var array
39 */
40 protected $schema;
41
42 /**
43 * Registers the routes for the objects of the controller.
44 *
45 * @since 4.7.0
46 *
47 * @see register_rest_route()
48 */
49 public function register_routes() {
50 _doing_it_wrong(
51 'WP_REST_Controller::register_routes',
52 /* translators: %s: register_routes() */
53 sprintf( __( "Method '%s' must be overridden." ), __METHOD__ ),
54 '4.7.0'
55 );
56 }
57
58 /**
59 * Checks if a given request has access to get items.
60 *
61 * @since 4.7.0
62 *
63 * @param WP_REST_Request $request Full details about the request.
64 * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
65 */
66 public function get_items_permissions_check( $request ) {
67 return new WP_Error(
68 'invalid-method',
69 /* translators: %s: Method name. */
70 sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ),
71 array( 'status' => 405 )
72 );
73 }
74
75 /**
76 * Retrieves a collection of items.
77 *
78 * @since 4.7.0
79 *
80 * @param WP_REST_Request $request Full details about the request.
81 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
82 */
83 public function get_items( $request ) {
84 return new WP_Error(
85 'invalid-method',
86 /* translators: %s: Method name. */
87 sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ),
88 array( 'status' => 405 )
89 );
90 }
91
92 /**
93 * Checks if a given request has access to get a specific item.
94 *
95 * @since 4.7.0
96 *
97 * @param WP_REST_Request $request Full details about the request.
98 * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise.
99 */
100 public function get_item_permissions_check( $request ) {
101 return new WP_Error(
102 'invalid-method',
103 /* translators: %s: Method name. */
104 sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ),
105 array( 'status' => 405 )
106 );
107 }
108
109 /**
110 * Retrieves one item from the collection.
111 *
112 * @since 4.7.0
113 *
114 * @param WP_REST_Request $request Full details about the request.
115 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
116 */
117 public function get_item( $request ) {
118 return new WP_Error(
119 'invalid-method',
120 /* translators: %s: Method name. */
121 sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ),
122 array( 'status' => 405 )
123 );
124 }
125
126 /**
127 * Checks if a given request has access to create items.
128 *
129 * @since 4.7.0
130 *
131 * @param WP_REST_Request $request Full details about the request.
132 * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise.
133 */
134 public function create_item_permissions_check( $request ) {
135 return new WP_Error(
136 'invalid-method',
137 /* translators: %s: Method name. */
138 sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ),
139 array( 'status' => 405 )
140 );
141 }
142
143 /**
144 * Creates one item from the collection.
145 *
146 * @since 4.7.0
147 *
148 * @param WP_REST_Request $request Full details about the request.
149 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
150 */
151 public function create_item( $request ) {
152 return new WP_Error(
153 'invalid-method',
154 /* translators: %s: Method name. */
155 sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ),
156 array( 'status' => 405 )
157 );
158 }
159
160 /**
161 * Checks if a given request has access to update a specific item.
162 *
163 * @since 4.7.0
164 *
165 * @param WP_REST_Request $request Full details about the request.
166 * @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise.
167 */
168 public function update_item_permissions_check( $request ) {
169 return new WP_Error(
170 'invalid-method',
171 /* translators: %s: Method name. */
172 sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ),
173 array( 'status' => 405 )
174 );
175 }
176
177 /**
178 * Updates one item from the collection.
179 *
180 * @since 4.7.0
181 *
182 * @param WP_REST_Request $request Full details about the request.
183 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
184 */
185 public function update_item( $request ) {
186 return new WP_Error(
187 'invalid-method',
188 /* translators: %s: Method name. */
189 sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ),
190 array( 'status' => 405 )
191 );
192 }
193
194 /**
195 * Checks if a given request has access to delete a specific item.
196 *
197 * @since 4.7.0
198 *
199 * @param WP_REST_Request $request Full details about the request.
200 * @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise.
201 */
202 public function delete_item_permissions_check( $request ) {
203 return new WP_Error(
204 'invalid-method',
205 /* translators: %s: Method name. */
206 sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ),
207 array( 'status' => 405 )
208 );
209 }
210
211 /**
212 * Deletes one item from the collection.
213 *
214 * @since 4.7.0
215 *
216 * @param WP_REST_Request $request Full details about the request.
217 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
218 */
219 public function delete_item( $request ) {
220 return new WP_Error(
221 'invalid-method',
222 /* translators: %s: Method name. */
223 sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ),
224 array( 'status' => 405 )
225 );
226 }
227
228 /**
229 * Prepares one item for create or update operation.
230 *
231 * @since 4.7.0
232 *
233 * @param WP_REST_Request $request Request object.
234 * @return object|WP_Error The prepared item, or WP_Error object on failure.
235 */
236 protected function prepare_item_for_database( $request ) {
237 return new WP_Error(
238 'invalid-method',
239 /* translators: %s: Method name. */
240 sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ),
241 array( 'status' => 405 )
242 );
243 }
244
245 /**
246 * Prepares the item for the REST response.
247 *
248 * @since 4.7.0
249 *
250 * @param mixed $item WordPress representation of the item.
251 * @param WP_REST_Request $request Request object.
252 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
253 */
254 public function prepare_item_for_response( $item, $request ) {
255 return new WP_Error(
256 'invalid-method',
257 /* translators: %s: Method name. */
258 sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ),
259 array( 'status' => 405 )
260 );
261 }
262
263 /**
264 * Prepares a response for insertion into a collection.
265 *
266 * @since 4.7.0
267 *
268 * @param WP_REST_Response $response Response object.
269 * @return array|mixed Response data, ready for insertion into collection data.
270 */
271 public function prepare_response_for_collection( $response ) {
272 if ( ! ( $response instanceof WP_REST_Response ) ) {
273 return $response;
274 }
275
276 $data = (array) $response->get_data();
277 $server = rest_get_server();
278 $links = $server::get_compact_response_links( $response );
279
280 if ( ! empty( $links ) ) {
281 $data['_links'] = $links;
282 }
283
284 return $data;
285 }
286
287 /**
288 * Filters a response based on the context defined in the schema.
289 *
290 * @since 4.7.0
291 *
292 * @param array $response_data Response data to filter.
293 * @param string $context Context defined in the schema.
294 * @return array Filtered response.
295 */
296 public function filter_response_by_context( $response_data, $context ) {
297
298 $schema = $this->get_item_schema();
299
300 return rest_filter_response_by_context( $response_data, $schema, $context );
301 }
302
303 /**
304 * Retrieves the item's schema, conforming to JSON Schema.
305 *
306 * @since 4.7.0
307 *
308 * @return array Item schema data.
309 */
310 public function get_item_schema() {
311 return $this->add_additional_fields_schema( array() );
312 }
313
314 /**
315 * Retrieves the item's schema for display / public consumption purposes.
316 *
317 * @since 4.7.0
318 *
319 * @return array Public item schema data.
320 */
321 public function get_public_item_schema() {
322
323 $schema = $this->get_item_schema();
324
325 if ( ! empty( $schema['properties'] ) ) {
326 foreach ( $schema['properties'] as &$property ) {
327 unset( $property['arg_options'] );
328 }
329 }
330
331 return $schema;
332 }
333
334 /**
335 * Retrieves the query params for the collections.
336 *
337 * @since 4.7.0
338 *
339 * @return array Query parameters for the collection.
340 */
341 public function get_collection_params() {
342 return array(
343 'context' => $this->get_context_param(),
344 'page' => array(
345 'description' => __( 'Current page of the collection.' ),
346 'type' => 'integer',
347 'default' => 1,
348 'sanitize_callback' => 'absint',
349 'validate_callback' => 'rest_validate_request_arg',
350 'minimum' => 1,
351 ),
352 'per_page' => array(
353 'description' => __( 'Maximum number of items to be returned in result set.' ),
354 'type' => 'integer',
355 'default' => 10,
356 'minimum' => 1,
357 'maximum' => 100,
358 'sanitize_callback' => 'absint',
359 'validate_callback' => 'rest_validate_request_arg',
360 ),
361 'search' => array(
362 'description' => __( 'Limit results to those matching a string.' ),
363 'type' => 'string',
364 'sanitize_callback' => 'sanitize_text_field',
365 'validate_callback' => 'rest_validate_request_arg',
366 ),
367 );
368 }
369
370 /**
371 * Retrieves the magical context param.
372 *
373 * Ensures consistent descriptions between endpoints, and populates enum from schema.
374 *
375 * @since 4.7.0
376 *
377 * @param array $args Optional. Additional arguments for context parameter. Default empty array.
378 * @return array Context parameter details.
379 */
380 public function get_context_param( $args = array() ) {
381 $param_details = array(
382 'description' => __( 'Scope under which the request is made; determines fields present in response.' ),
383 'type' => 'string',
384 'sanitize_callback' => 'sanitize_key',
385 'validate_callback' => 'rest_validate_request_arg',
386 );
387
388 $schema = $this->get_item_schema();
389
390 if ( empty( $schema['properties'] ) ) {
391 return array_merge( $param_details, $args );
392 }
393
394 $contexts = array();
395
396 foreach ( $schema['properties'] as $attributes ) {
397 if ( ! empty( $attributes['context'] ) ) {
398 $contexts = array_merge( $contexts, $attributes['context'] );
399 }
400 }
401
402 if ( ! empty( $contexts ) ) {
403 $param_details['enum'] = array_unique( $contexts );
404 rsort( $param_details['enum'] );
405 }
406
407 return array_merge( $param_details, $args );
408 }
409
410 /**
411 * Adds the values from additional fields to a data object.
412 *
413 * @since 4.7.0
414 *
415 * @param array $response_data Prepared response array.
416 * @param WP_REST_Request $request Full details about the request.
417 * @return array Modified data object with additional fields.
418 */
419 protected function add_additional_fields_to_object( $response_data, $request ) {
420
421 $additional_fields = $this->get_additional_fields();
422
423 $requested_fields = $this->get_fields_for_response( $request );
424
425 foreach ( $additional_fields as $field_name => $field_options ) {
426 if ( ! $field_options['get_callback'] ) {
427 continue;
428 }
429
430 if ( ! rest_is_field_included( $field_name, $requested_fields ) ) {
431 continue;
432 }
433
434 $response_data[ $field_name ] = call_user_func(
435 $field_options['get_callback'],
436 $response_data,
437 $field_name,
438 $request,
439 $this->get_object_type()
440 );
441 }
442
443 return $response_data;
444 }
445
446 /**
447 * Updates the values of additional fields added to a data object.
448 *
449 * @since 4.7.0
450 *
451 * @param object $data_object Data model like WP_Term or WP_Post.
452 * @param WP_REST_Request $request Full details about the request.
453 * @return true|WP_Error True on success, WP_Error object if a field cannot be updated.
454 */
455 protected function update_additional_fields_for_object( $data_object, $request ) {
456 $additional_fields = $this->get_additional_fields();
457
458 foreach ( $additional_fields as $field_name => $field_options ) {
459 if ( ! $field_options['update_callback'] ) {
460 continue;
461 }
462
463 // Don't run the update callbacks if the data wasn't passed in the request.
464 if ( ! isset( $request[ $field_name ] ) ) {
465 continue;
466 }
467
468 $result = call_user_func(
469 $field_options['update_callback'],
470 $request[ $field_name ],
471 $data_object,
472 $field_name,
473 $request,
474 $this->get_object_type()
475 );
476
477 if ( is_wp_error( $result ) ) {
478 return $result;
479 }
480 }
481
482 return true;
483 }
484
485 /**
486 * Adds the schema from additional fields to a schema array.
487 *
488 * The type of object is inferred from the passed schema.
489 *
490 * @since 4.7.0
491 *
492 * @param array $schema Schema array.
493 * @return array Modified Schema array.
494 */
495 protected function add_additional_fields_schema( $schema ) {
496 if ( empty( $schema['title'] ) ) {
497 return $schema;
498 }
499
500 // Can't use $this->get_object_type otherwise we cause an inf loop.
501 $object_type = $schema['title'];
502
503 $additional_fields = $this->get_additional_fields( $object_type );
504
505 foreach ( $additional_fields as $field_name => $field_options ) {
506 if ( ! $field_options['schema'] ) {
507 continue;
508 }
509
510 $schema['properties'][ $field_name ] = $field_options['schema'];
511 }
512
513 return $schema;
514 }
515
516 /**
517 * Retrieves all of the registered additional fields for a given object-type.
518 *
519 * @since 4.7.0
520 *
521 * @global array $wp_rest_additional_fields Holds registered fields, organized by object type.
522 *
523 * @param string $object_type Optional. The object type.
524 * @return array Registered additional fields (if any), empty array if none or if the object type
525 * could not be inferred.
526 */
527 protected function get_additional_fields( $object_type = null ) {
528 global $wp_rest_additional_fields;
529
530 if ( ! $object_type ) {
531 $object_type = $this->get_object_type();
532 }
533
534 if ( ! $object_type ) {
535 return array();
536 }
537
538 if ( ! $wp_rest_additional_fields || ! isset( $wp_rest_additional_fields[ $object_type ] ) ) {
539 return array();
540 }
541
542 return $wp_rest_additional_fields[ $object_type ];
543 }
544
545 /**
546 * Retrieves the object type this controller is responsible for managing.
547 *
548 * @since 4.7.0
549 *
550 * @return string Object type for the controller.
551 */
552 protected function get_object_type() {
553 $schema = $this->get_item_schema();
554
555 if ( ! $schema || ! isset( $schema['title'] ) ) {
556 return null;
557 }
558
559 return $schema['title'];
560 }
561
562 /**
563 * Gets an array of fields to be included on the response.
564 *
565 * Included fields are based on item schema and `_fields=` request argument.
566 *
567 * @since 4.9.6
568 *
569 * @param WP_REST_Request $request Full details about the request.
570 * @return string[] Fields to be included in the response.
571 */
572 public function get_fields_for_response( $request ) {
573 $schema = $this->get_item_schema();
574 $properties = isset( $schema['properties'] ) ? $schema['properties'] : array();
575
576 $additional_fields = $this->get_additional_fields();
577
578 foreach ( $additional_fields as $field_name => $field_options ) {
579 /*
580 * For back-compat, include any field with an empty schema
581 * because it won't be present in $this->get_item_schema().
582 */
583 if ( is_null( $field_options['schema'] ) ) {
584 $properties[ $field_name ] = $field_options;
585 }
586 }
587
588 // Exclude fields that specify a different context than the request context.
589 $context = $request['context'];
590 if ( $context ) {
591 foreach ( $properties as $name => $options ) {
592 if ( ! empty( $options['context'] ) && ! in_array( $context, $options['context'], true ) ) {
593 unset( $properties[ $name ] );
594 }
595 }
596 }
597
598 $fields = array_keys( $properties );
599
600 /*
601 * '_links' and '_embedded' are not typically part of the item schema,
602 * but they can be specified in '_fields', so they are added here as a
603 * convenience for checking with rest_is_field_included().
604 */
605 $fields[] = '_links';
606 if ( $request->has_param( '_embed' ) ) {
607 $fields[] = '_embedded';
608 }
609
610 $fields = array_unique( $fields );
611
612 if ( ! isset( $request['_fields'] ) ) {
613 return $fields;
614 }
615 $requested_fields = wp_parse_list( $request['_fields'] );
616 if ( 0 === count( $requested_fields ) ) {
617 return $fields;
618 }
619 // Trim off outside whitespace from the comma delimited list.
620 $requested_fields = array_map( 'trim', $requested_fields );
621 // Always persist 'id', because it can be needed for add_additional_fields_to_object().
622 if ( in_array( 'id', $fields, true ) ) {
623 $requested_fields[] = 'id';
624 }
625 // Return the list of all requested fields which appear in the schema.
626 return array_reduce(
627 $requested_fields,
628 static function ( $response_fields, $field ) use ( $fields ) {
629 if ( in_array( $field, $fields, true ) ) {
630 $response_fields[] = $field;
631 return $response_fields;
632 }
633 // Check for nested fields if $field is not a direct match.
634 $nested_fields = explode( '.', $field );
635 /*
636 * A nested field is included so long as its top-level property
637 * is present in the schema.
638 */
639 if ( in_array( $nested_fields[0], $fields, true ) ) {
640 $response_fields[] = $field;
641 }
642 return $response_fields;
643 },
644 array()
645 );
646 }
647
648 /**
649 * Retrieves an array of endpoint arguments from the item schema for the controller.
650 *
651 * @since 4.7.0
652 *
653 * @param string $method Optional. HTTP method of the request. The arguments for `CREATABLE` requests are
654 * checked for required values and may fall-back to a given default, this is not done
655 * on `EDITABLE` requests. Default WP_REST_Server::CREATABLE.
656 * @return array Endpoint arguments.
657 */
658 public function get_endpoint_args_for_item_schema( $method = WP_REST_Server::CREATABLE ) {
659 return rest_get_endpoint_args_for_schema( $this->get_item_schema(), $method );
660 }
661
662 /**
663 * Sanitizes the slug value.
664 *
665 * {@internal We can't use sanitize_title() directly, as the second
666 * parameter is the fallback title, which would end up being set to the
667 * request object.}
668 *
669 * @since 4.7.0
670 *
671 * @see https://github.com/WP-API/WP-API/issues/1585
672 *
673 * @todo Remove this in favour of https://core.trac.wordpress.org/ticket/34659
674 *
675 * @param string $slug Slug value passed in request.
676 * @return string Sanitized value for the slug.
677 */
678 public function sanitize_slug( $slug ) {
679 return sanitize_title( $slug );
680 }
681}
682