[ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * REST API: WP_REST_Meta_Fields class 4 * 5 * @package WordPress 6 * @subpackage REST_API 7 * @since 4.7.0 8 */ 9 10 /** 11 * Core class to manage meta values for an object via the REST API. 12 * 13 * @since 4.7.0 14 */ 15 #[AllowDynamicProperties] 16 abstract class WP_REST_Meta_Fields { 17 18 /** 19 * Retrieves the object meta type. 20 * 21 * @since 4.7.0 22 * 23 * @return string One of 'post', 'comment', 'term', 'user', or anything 24 * else supported by `_get_meta_table()`. 25 */ 26 abstract protected function get_meta_type(); 27 28 /** 29 * Retrieves the object meta subtype. 30 * 31 * @since 4.9.8 32 * 33 * @return string Subtype for the meta type, or empty string if no specific subtype. 34 */ 35 protected function get_meta_subtype() { 36 return ''; 37 } 38 39 /** 40 * Retrieves the object type for register_rest_field(). 41 * 42 * @since 4.7.0 43 * 44 * @return string The REST field type, such as post type name, taxonomy name, 'comment', or `user`. 45 */ 46 abstract protected function get_rest_field_type(); 47 48 /** 49 * Registers the meta field. 50 * 51 * @since 4.7.0 52 * @deprecated 5.6.0 53 * 54 * @see register_rest_field() 55 */ 56 public function register_field() { 57 _deprecated_function( __METHOD__, '5.6.0' ); 58 59 register_rest_field( 60 $this->get_rest_field_type(), 61 'meta', 62 array( 63 'get_callback' => array( $this, 'get_value' ), 64 'update_callback' => array( $this, 'update_value' ), 65 'schema' => $this->get_field_schema(), 66 ) 67 ); 68 } 69 70 /** 71 * Retrieves the meta field value. 72 * 73 * @since 4.7.0 74 * 75 * @param int $object_id Object ID to fetch meta for. 76 * @param WP_REST_Request $request Full details about the request. 77 * @return array Array containing the meta values keyed by name. 78 */ 79 public function get_value( $object_id, $request ) { 80 $fields = $this->get_registered_fields(); 81 $response = array(); 82 83 foreach ( $fields as $meta_key => $args ) { 84 $name = $args['name']; 85 $all_values = get_metadata( $this->get_meta_type(), $object_id, $meta_key, false ); 86 87 if ( $args['single'] ) { 88 if ( empty( $all_values ) ) { 89 $value = $args['schema']['default']; 90 } else { 91 $value = $all_values[0]; 92 } 93 94 $value = $this->prepare_value_for_response( $value, $request, $args ); 95 } else { 96 $value = array(); 97 98 if ( is_array( $all_values ) ) { 99 foreach ( $all_values as $row ) { 100 $value[] = $this->prepare_value_for_response( $row, $request, $args ); 101 } 102 } 103 } 104 105 $response[ $name ] = $value; 106 } 107 108 return $response; 109 } 110 111 /** 112 * Prepares a meta value for a response. 113 * 114 * This is required because some native types cannot be stored correctly 115 * in the database, such as booleans. We need to cast back to the relevant 116 * type before passing back to JSON. 117 * 118 * @since 4.7.0 119 * 120 * @param mixed $value Meta value to prepare. 121 * @param WP_REST_Request $request Current request object. 122 * @param array $args Options for the field. 123 * @return mixed Prepared value. 124 */ 125 protected function prepare_value_for_response( $value, $request, $args ) { 126 if ( ! empty( $args['prepare_callback'] ) ) { 127 $value = call_user_func( $args['prepare_callback'], $value, $request, $args ); 128 } 129 130 return $value; 131 } 132 133 /** 134 * Updates meta values. 135 * 136 * @since 4.7.0 137 * 138 * @param array $meta Array of meta parsed from the request. 139 * @param int $object_id Object ID to fetch meta for. 140 * @return null|WP_Error Null on success, WP_Error object on failure. 141 */ 142 public function update_value( $meta, $object_id ) { 143 $fields = $this->get_registered_fields(); 144 $error = new WP_Error(); 145 146 foreach ( $fields as $meta_key => $args ) { 147 $name = $args['name']; 148 if ( ! array_key_exists( $name, $meta ) ) { 149 continue; 150 } 151 152 $value = $meta[ $name ]; 153 154 /* 155 * A null value means reset the field, which is essentially deleting it 156 * from the database and then relying on the default value. 157 * 158 * Non-single meta can also be removed by passing an empty array. 159 */ 160 if ( is_null( $value ) || ( array() === $value && ! $args['single'] ) ) { 161 $args = $this->get_registered_fields()[ $meta_key ]; 162 163 if ( $args['single'] ) { 164 $current = get_metadata( $this->get_meta_type(), $object_id, $meta_key, true ); 165 166 if ( is_wp_error( rest_validate_value_from_schema( $current, $args['schema'] ) ) ) { 167 $error->add( 168 'rest_invalid_stored_value', 169 /* translators: %s: Custom field key. */ 170 sprintf( __( 'The %s property has an invalid stored value, and cannot be updated to null.' ), $name ), 171 array( 'status' => 500 ) 172 ); 173 continue; 174 } 175 } 176 177 $result = $this->delete_meta_value( $object_id, $meta_key, $name ); 178 if ( is_wp_error( $result ) ) { 179 $error->merge_from( $result ); 180 } 181 continue; 182 } 183 184 if ( ! $args['single'] && is_array( $value ) && count( array_filter( $value, 'is_null' ) ) ) { 185 $error->add( 186 'rest_invalid_stored_value', 187 /* translators: %s: Custom field key. */ 188 sprintf( __( 'The %s property has an invalid stored value, and cannot be updated to null.' ), $name ), 189 array( 'status' => 500 ) 190 ); 191 continue; 192 } 193 194 $is_valid = rest_validate_value_from_schema( $value, $args['schema'], 'meta.' . $name ); 195 if ( is_wp_error( $is_valid ) ) { 196 $is_valid->add_data( array( 'status' => 400 ) ); 197 $error->merge_from( $is_valid ); 198 continue; 199 } 200 201 $value = rest_sanitize_value_from_schema( $value, $args['schema'] ); 202 203 if ( $args['single'] ) { 204 $result = $this->update_meta_value( $object_id, $meta_key, $name, $value ); 205 } else { 206 $result = $this->update_multi_meta_value( $object_id, $meta_key, $name, $value ); 207 } 208 209 if ( is_wp_error( $result ) ) { 210 $error->merge_from( $result ); 211 continue; 212 } 213 } 214 215 if ( $error->has_errors() ) { 216 return $error; 217 } 218 219 return null; 220 } 221 222 /** 223 * Deletes a meta value for an object. 224 * 225 * @since 4.7.0 226 * 227 * @param int $object_id Object ID the field belongs to. 228 * @param string $meta_key Key for the field. 229 * @param string $name Name for the field that is exposed in the REST API. 230 * @return true|WP_Error True if meta field is deleted, WP_Error otherwise. 231 */ 232 protected function delete_meta_value( $object_id, $meta_key, $name ) { 233 $meta_type = $this->get_meta_type(); 234 235 if ( ! current_user_can( "delete_{$meta_type}_meta", $object_id, $meta_key ) ) { 236 return new WP_Error( 237 'rest_cannot_delete', 238 /* translators: %s: Custom field key. */ 239 sprintf( __( 'Sorry, you are not allowed to edit the %s custom field.' ), $name ), 240 array( 241 'key' => $name, 242 'status' => rest_authorization_required_code(), 243 ) 244 ); 245 } 246 247 if ( null === get_metadata_raw( $meta_type, $object_id, wp_slash( $meta_key ) ) ) { 248 return true; 249 } 250 251 if ( ! delete_metadata( $meta_type, $object_id, wp_slash( $meta_key ) ) ) { 252 return new WP_Error( 253 'rest_meta_database_error', 254 __( 'Could not delete meta value from database.' ), 255 array( 256 'key' => $name, 257 'status' => WP_Http::INTERNAL_SERVER_ERROR, 258 ) 259 ); 260 } 261 262 return true; 263 } 264 265 /** 266 * Updates multiple meta values for an object. 267 * 268 * Alters the list of values in the database to match the list of provided values. 269 * 270 * @since 4.7.0 271 * @since 6.7.0 Stores values into DB even if provided registered default value. 272 * 273 * @param int $object_id Object ID to update. 274 * @param string $meta_key Key for the custom field. 275 * @param string $name Name for the field that is exposed in the REST API. 276 * @param array $values List of values to update to. 277 * @return true|WP_Error True if meta fields are updated, WP_Error otherwise. 278 */ 279 protected function update_multi_meta_value( $object_id, $meta_key, $name, $values ) { 280 $meta_type = $this->get_meta_type(); 281 282 if ( ! current_user_can( "edit_{$meta_type}_meta", $object_id, $meta_key ) ) { 283 return new WP_Error( 284 'rest_cannot_update', 285 /* translators: %s: Custom field key. */ 286 sprintf( __( 'Sorry, you are not allowed to edit the %s custom field.' ), $name ), 287 array( 288 'key' => $name, 289 'status' => rest_authorization_required_code(), 290 ) 291 ); 292 } 293 294 $current_values = get_metadata_raw( $meta_type, $object_id, $meta_key, false ); 295 $subtype = get_object_subtype( $meta_type, $object_id ); 296 297 if ( ! is_array( $current_values ) ) { 298 $current_values = array(); 299 } 300 301 $to_remove = $current_values; 302 $to_add = $values; 303 304 foreach ( $to_add as $add_key => $value ) { 305 $remove_keys = array_keys( 306 array_filter( 307 $current_values, 308 function ( $stored_value ) use ( $meta_key, $subtype, $value ) { 309 return $this->is_meta_value_same_as_stored_value( $meta_key, $subtype, $stored_value, $value ); 310 } 311 ) 312 ); 313 314 if ( empty( $remove_keys ) ) { 315 continue; 316 } 317 318 if ( count( $remove_keys ) > 1 ) { 319 // To remove, we need to remove first, then add, so don't touch. 320 continue; 321 } 322 323 $remove_key = $remove_keys[0]; 324 325 unset( $to_remove[ $remove_key ] ); 326 unset( $to_add[ $add_key ] ); 327 } 328 329 /* 330 * `delete_metadata` removes _all_ instances of the value, so only call once. Otherwise, 331 * `delete_metadata` will return false for subsequent calls of the same value. 332 * Use serialization to produce a predictable string that can be used by array_unique. 333 */ 334 $to_remove = array_map( 'maybe_unserialize', array_unique( array_map( 'maybe_serialize', $to_remove ) ) ); 335 336 foreach ( $to_remove as $value ) { 337 if ( ! delete_metadata( $meta_type, $object_id, wp_slash( $meta_key ), wp_slash( $value ) ) ) { 338 return new WP_Error( 339 'rest_meta_database_error', 340 /* translators: %s: Custom field key. */ 341 sprintf( __( 'Could not update the meta value of %s in database.' ), $meta_key ), 342 array( 343 'key' => $name, 344 'status' => WP_Http::INTERNAL_SERVER_ERROR, 345 ) 346 ); 347 } 348 } 349 350 foreach ( $to_add as $value ) { 351 if ( ! add_metadata( $meta_type, $object_id, wp_slash( $meta_key ), wp_slash( $value ) ) ) { 352 return new WP_Error( 353 'rest_meta_database_error', 354 /* translators: %s: Custom field key. */ 355 sprintf( __( 'Could not update the meta value of %s in database.' ), $meta_key ), 356 array( 357 'key' => $name, 358 'status' => WP_Http::INTERNAL_SERVER_ERROR, 359 ) 360 ); 361 } 362 } 363 364 return true; 365 } 366 367 /** 368 * Updates a meta value for an object. 369 * 370 * @since 4.7.0 371 * @since 6.7.0 Stores values into DB even if provided registered default value. 372 * 373 * @param int $object_id Object ID to update. 374 * @param string $meta_key Key for the custom field. 375 * @param string $name Name for the field that is exposed in the REST API. 376 * @param mixed $value Updated value. 377 * @return true|WP_Error True if the meta field was updated, WP_Error otherwise. 378 */ 379 protected function update_meta_value( $object_id, $meta_key, $name, $value ) { 380 $meta_type = $this->get_meta_type(); 381 382 // Do the exact same check for a duplicate value as in update_metadata() to avoid update_metadata() returning false. 383 $old_value = get_metadata_raw( $meta_type, $object_id, $meta_key ); 384 $subtype = get_object_subtype( $meta_type, $object_id ); 385 386 if ( is_array( $old_value ) && 1 === count( $old_value ) 387 && $this->is_meta_value_same_as_stored_value( $meta_key, $subtype, $old_value[0], $value ) 388 ) { 389 return true; 390 } 391 392 if ( ! current_user_can( "edit_{$meta_type}_meta", $object_id, $meta_key ) ) { 393 return new WP_Error( 394 'rest_cannot_update', 395 /* translators: %s: Custom field key. */ 396 sprintf( __( 'Sorry, you are not allowed to edit the %s custom field.' ), $name ), 397 array( 398 'key' => $name, 399 'status' => rest_authorization_required_code(), 400 ) 401 ); 402 } 403 404 if ( ! update_metadata( $meta_type, $object_id, wp_slash( $meta_key ), wp_slash( $value ) ) ) { 405 return new WP_Error( 406 'rest_meta_database_error', 407 /* translators: %s: Custom field key. */ 408 sprintf( __( 'Could not update the meta value of %s in database.' ), $meta_key ), 409 array( 410 'key' => $name, 411 'status' => WP_Http::INTERNAL_SERVER_ERROR, 412 ) 413 ); 414 } 415 416 return true; 417 } 418 419 /** 420 * Checks if the user provided value is equivalent to a stored value for the given meta key. 421 * 422 * @since 5.5.0 423 * 424 * @param string $meta_key The meta key being checked. 425 * @param string $subtype The object subtype. 426 * @param mixed $stored_value The currently stored value retrieved from get_metadata(). 427 * @param mixed $user_value The value provided by the user. 428 * @return bool 429 */ 430 protected function is_meta_value_same_as_stored_value( $meta_key, $subtype, $stored_value, $user_value ) { 431 $args = $this->get_registered_fields()[ $meta_key ]; 432 $sanitized = sanitize_meta( $meta_key, $user_value, $this->get_meta_type(), $subtype ); 433 434 if ( in_array( $args['type'], array( 'string', 'number', 'integer', 'boolean' ), true ) ) { 435 // The return value of get_metadata will always be a string for scalar types. 436 $sanitized = (string) $sanitized; 437 } 438 439 return $sanitized === $stored_value; 440 } 441 442 /** 443 * Retrieves all the registered meta fields. 444 * 445 * @since 4.7.0 446 * 447 * @return array Registered fields. 448 */ 449 protected function get_registered_fields() { 450 $registered = array(); 451 452 $meta_type = $this->get_meta_type(); 453 $meta_subtype = $this->get_meta_subtype(); 454 455 $meta_keys = get_registered_meta_keys( $meta_type ); 456 if ( ! empty( $meta_subtype ) ) { 457 $meta_keys = array_merge( $meta_keys, get_registered_meta_keys( $meta_type, $meta_subtype ) ); 458 } 459 460 foreach ( $meta_keys as $name => $args ) { 461 if ( empty( $args['show_in_rest'] ) ) { 462 continue; 463 } 464 465 $rest_args = array(); 466 467 if ( is_array( $args['show_in_rest'] ) ) { 468 $rest_args = $args['show_in_rest']; 469 } 470 471 $default_args = array( 472 'name' => $name, 473 'single' => $args['single'], 474 'type' => ! empty( $args['type'] ) ? $args['type'] : null, 475 'schema' => array(), 476 'prepare_callback' => array( $this, 'prepare_value' ), 477 ); 478 479 $default_schema = array( 480 'type' => $default_args['type'], 481 'title' => empty( $args['label'] ) ? '' : $args['label'], 482 'description' => empty( $args['description'] ) ? '' : $args['description'], 483 'default' => isset( $args['default'] ) ? $args['default'] : null, 484 ); 485 486 $rest_args = array_merge( $default_args, $rest_args ); 487 $rest_args['schema'] = array_merge( $default_schema, $rest_args['schema'] ); 488 489 $type = ! empty( $rest_args['type'] ) ? $rest_args['type'] : null; 490 $type = ! empty( $rest_args['schema']['type'] ) ? $rest_args['schema']['type'] : $type; 491 492 if ( null === $rest_args['schema']['default'] ) { 493 $rest_args['schema']['default'] = static::get_empty_value_for_type( $type ); 494 } 495 496 $rest_args['schema'] = rest_default_additional_properties_to_false( $rest_args['schema'] ); 497 498 if ( ! in_array( $type, array( 'string', 'boolean', 'integer', 'number', 'array', 'object' ), true ) ) { 499 continue; 500 } 501 502 if ( empty( $rest_args['single'] ) ) { 503 $rest_args['schema'] = array( 504 'type' => 'array', 505 'items' => $rest_args['schema'], 506 ); 507 } 508 509 $registered[ $name ] = $rest_args; 510 } 511 512 return $registered; 513 } 514 515 /** 516 * Retrieves the object's meta schema, conforming to JSON Schema. 517 * 518 * @since 4.7.0 519 * 520 * @return array Field schema data. 521 */ 522 public function get_field_schema() { 523 $fields = $this->get_registered_fields(); 524 525 $schema = array( 526 'description' => __( 'Meta fields.' ), 527 'type' => 'object', 528 'context' => array( 'view', 'edit' ), 529 'properties' => array(), 530 'arg_options' => array( 531 'sanitize_callback' => null, 532 'validate_callback' => array( $this, 'check_meta_is_array' ), 533 ), 534 ); 535 536 foreach ( $fields as $args ) { 537 $schema['properties'][ $args['name'] ] = $args['schema']; 538 } 539 540 return $schema; 541 } 542 543 /** 544 * Prepares a meta value for output. 545 * 546 * Default preparation for meta fields. Override by passing the 547 * `prepare_callback` in your `show_in_rest` options. 548 * 549 * @since 4.7.0 550 * 551 * @param mixed $value Meta value from the database. 552 * @param WP_REST_Request $request Request object. 553 * @param array $args REST-specific options for the meta key. 554 * @return mixed Value prepared for output. If a non-JsonSerializable object, null. 555 */ 556 public static function prepare_value( $value, $request, $args ) { 557 if ( $args['single'] ) { 558 $schema = $args['schema']; 559 } else { 560 $schema = $args['schema']['items']; 561 } 562 563 if ( '' === $value && in_array( $schema['type'], array( 'boolean', 'integer', 'number' ), true ) ) { 564 $value = static::get_empty_value_for_type( $schema['type'] ); 565 } 566 567 if ( is_wp_error( rest_validate_value_from_schema( $value, $schema ) ) ) { 568 return null; 569 } 570 571 return rest_sanitize_value_from_schema( $value, $schema ); 572 } 573 574 /** 575 * Check the 'meta' value of a request is an associative array. 576 * 577 * @since 4.7.0 578 * 579 * @param mixed $value The meta value submitted in the request. 580 * @param WP_REST_Request $request Full details about the request. 581 * @param string $param The parameter name. 582 * @return array|false The meta array, if valid, false otherwise. 583 */ 584 public function check_meta_is_array( $value, $request, $param ) { 585 if ( ! is_array( $value ) ) { 586 return false; 587 } 588 589 return $value; 590 } 591 592 /** 593 * Recursively add additionalProperties = false to all objects in a schema if no additionalProperties setting 594 * is specified. 595 * 596 * This is needed to restrict properties of objects in meta values to only 597 * registered items, as the REST API will allow additional properties by 598 * default. 599 * 600 * @since 5.3.0 601 * @deprecated 5.6.0 Use rest_default_additional_properties_to_false() instead. 602 * 603 * @param array $schema The schema array. 604 * @return array 605 */ 606 protected function default_additional_properties_to_false( $schema ) { 607 _deprecated_function( __METHOD__, '5.6.0', 'rest_default_additional_properties_to_false()' ); 608 609 return rest_default_additional_properties_to_false( $schema ); 610 } 611 612 /** 613 * Gets the empty value for a schema type. 614 * 615 * @since 5.3.0 616 * 617 * @param string $type The schema type. 618 * @return mixed 619 */ 620 protected static function get_empty_value_for_type( $type ) { 621 switch ( $type ) { 622 case 'string': 623 return ''; 624 case 'boolean': 625 return false; 626 case 'integer': 627 return 0; 628 case 'number': 629 return 0.0; 630 case 'array': 631 case 'object': 632 return array(); 633 default: 634 return null; 635 } 636 } 637 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated : Thu Nov 21 08:20:01 2024 | Cross-referenced by PHPXref |