[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

/wp-includes/rest-api/fields/ -> class-wp-rest-meta-fields.php (source)

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


Generated: Mon Sep 16 01:00:03 2019 Cross-referenced by PHPXref 0.7.1