the request. * @return bool|WP_Error True on success, WP_Error object if a field cannot be updated. */ protected function update_additional_fields_for_object( $object, $request ) { $additional_fields = $this->get_additional_fields(); foreach ( $additional_fields as $field_name => $field_options ) { if ( ! $field_options['update_callback'] ) { continue; } // Don't run the update callbacks if the data wasn't passed in the request. if ( ! isset( $request[ $field_name ] ) ) { continue; } $result = call_user_func( $field_options['update_callback'], $request[ $field_name ], $object, $field_name, $request, $this->get_object_type() ); if ( is_wp_error( $result ) ) { return $result; } } return true; } /** * Adds the schema from additional fields to a schema array. * * The type of object is inferred from the passed schema. * * @since 4.7.0 * * @param array $schema Schema array. * @return array Modified Schema array. */ protected function add_additional_fields_schema( $schema ) { if ( empty( $schema['title'] ) ) { return $schema; } // Can't use $this->get_object_type otherwise we cause an inf loop. $object_type = $schema['title']; $additional_fields = $this->get_additional_fields( $object_type ); foreach ( $additional_fields as $field_name => $field_options ) { if ( ! $field_options['schema'] ) { continue; } $schema['properties'][ $field_name ] = $field_options['schema']; } return $schema; } /** * Retrieves all of the registered additional fields for a given object-type. * * @since 4.7.0 * * @param string $object_type Optional. The object type. * @return array Registered additional fields (if any), empty array if none or if the object type could * not be inferred. */ protected function get_additional_fields( $object_type = null ) { if ( ! $object_type ) { $object_type = $this->get_object_type(); } if ( ! $object_type ) { return array(); } global $wp_rest_additional_fields; if ( ! $wp_rest_additional_fields || ! isset( $wp_rest_additional_fields[ $object_type ] ) ) { return array(); } return $wp_rest_additional_fields[ $object_type ]; } /** * Retrieves the object type this controller is responsible for managing. * * @since 4.7.0 * * @return string Object type for the controller. */ protected function get_object_type() { $schema = $this->get_item_schema(); if ( ! $schema || ! isset( $schema['title'] ) ) { return null; } return $schema['title']; } /** * Gets an array of fields to be included on the response. * * Included fields are based on item schema and `_fields=` request argument. * * @since 4.9.6 * * @param WP_REST_Request $request Full details about the request. * @return array Fields to be included in the response. */ public function get_fields_for_response( $request ) { $schema = $this->get_item_schema(); $properties = isset( $schema['properties'] ) ? $schema['properties'] : array(); $additional_fields = $this->get_additional_fields(); foreach ( $additional_fields as $field_name => $field_options ) { // For back-compat, include any field with an empty schema // because it won't be present in $this->get_item_schema(). if ( is_null( $field_options['schema'] ) ) { $properties[ $field_name ] = $field_options; } } // Exclude fields that specify a different context than the request context. $context = $request['context']; if ( $context ) { foreach ( $properties as $name => $options ) { if ( ! empty( $options['context'] ) && ! in_array( $context, $options['context'], true ) ) { unset( $properties[ $name ] ); } } } $fields = array_keys( $properties ); if ( ! isset( $request['_fields'] ) ) { return $fields; } $requested_fields = wp_parse_list( $request['_fields'] ); if ( 0 === count( $requested_fields ) ) { return $fields; } // Trim off outside whitespace from the comma delimited list. $requested_fields = array_map( 'trim', $requested_fields ); // Always persist 'id', because it can be needed for add_additional_fields_to_object(). if ( in_array( 'id', $fields, true ) ) { $requested_fields[] = 'id'; } // Return the list of all requested fields which appear in the schema. return array_reduce( $requested_fields, function( $response_fields, $field ) use ( $fields ) { if ( in_array( $field, $fields, true ) ) { $response_fields[] = $field; return $response_fields; } // Check for nested fields if $field is not a direct match. $nested_fields = explode( '.', $field ); // A nested field is included so long as its top-level property // is present in the schema. if ( in_array( $nested_fields[0], $fields, true ) ) { $response_fields[] = $field; } return $response_fields; }, array() ); } /** * Retrieves an array of endpoint arguments from the item schema for the controller. * * @since 4.7.0 * * @param string $method Optional. HTTP method of the request. The arguments for `CREATABLE` requests are * checked for required values and may fall-back to a given default, this is not done * on `EDITABLE` requests. Default WP_REST_Server::CREATABLE. * @return array Endpoint arguments. */ public function get_endpoint_args_for_item_schema( $method = WP_REST_Server::CREATABLE ) { $schema = $this->get_item_schema(); $schema_properties = ! empty( $schema['properties'] ) ? $schema['properties'] : array(); $endpoint_args = array(); $valid_schema_properties = array( 'type', 'format', 'enum', 'items', 'properties', 'additionalProperties', 'minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum', 'minLength', 'maxLength', 'pattern', 'minItems', 'maxItems', 'uniqueItems', ); foreach ( $schema_properties as $field_id => $params ) { // Arguments specified as `readonly` are not allowed to be set. if ( ! empty( $params['readonly'] ) ) { continue; } $endpoint_args[ $field_id ] = array( 'validate_callback' => 'rest_validate_request_arg', 'sanitize_callback' => 'rest_sanitize_request_arg', ); if ( isset( $params['description'] ) ) { $endpoint_args[ $field_id ]['description'] = $params['description']; } if ( WP_REST_Server::CREATABLE === $method && isset( $params['default'] ) ) { $endpoint_args[ $field_id ]['default'] = $params['default']; } if ( WP_REST_Server::CREATABLE === $method && ! empty( $params['required'] ) ) { $endpoint_args[ $field_id ]['required'] = true; } foreach ( $valid_schema_properties as $schema_prop ) { if ( isset( $params[ $schema_prop ] ) ) { $endpoint_args[ $field_id ][ $schema_prop ] = $params[ $schema_prop ]; } } // Merge in any options provided by the schema property. if ( isset( $params['arg_options'] ) ) { // Only use required / default from arg_options on CREATABLE endpoints. if ( WP_REST_Server::CREATABLE !== $method ) { $params['arg_options'] = array_diff_key( $params['arg_options'], array( 'required' => '', 'default' => '', ) ); } $endpoint_args[ $field_id ] = array_merge( $endpoint_args[ $field_id ], $params['arg_options'] ); } } return $endpoint_args; } /** * Sanitizes the slug value. * * @since 4.7.0 * * @internal We can't use sanitize_title() directly, as the second * parameter is the fallback title, which would end up being set to the * request object. * * @see https://github.com/WP-API/WP-API/issues/1585 * * @todo Remove this in favour of https://core.trac.wordpress.org/ticket/34659 * * @param string $slug Slug value passed in request. * @return string Sanitized value for the slug. */ public function sanitize_slug( $slug ) { return sanitize_title( $slug ); } }