WordPress/wp-includes/rest-api/endpoints/class-wp-rest-widget-types-controller.php
hellofromTonya 41307cd4e6 Code Modernization: Fix parameter name mismatches for parent/child classes in WP_REST_Controller::prepare_item_for_response().
In each child and grandchild class, renames the first parameter to match the parent's method signature.

Why? PHP 8 introduces the ability to pass named arguments to function/method calls. This means the child and parent method signatures (i.e. parameter names) need to match.

Changes for readability:

- `@since` clearly specifies the original parameter name and its new name as well as why the change happened.

- In methods longer than a single line, the generic parameter is reassigned to the original parameter restoring it for context for use within the method. An inline comment is added to explain why this reassignment is made.

Follow-up to [38832], [39011], [39015], [39021], [39024], [39025], [39031], [39036], [43519], [43735], [43739], [43768], [46821], [48173], [48242], [49088], [50995], [51003], [51021].

Props jrf, hellofromTonya, sergeybiryukov, azaozz, desrosj, johnbillion.
See #51553.
Built from https://develop.svn.wordpress.org/trunk@51786


git-svn-id: http://core.svn.wordpress.org/trunk@51393 1a063a9b-81f0-0310-95a4-ce76da25c4cd
2021-09-09 18:36:57 +00:00

573 lines
16 KiB
PHP

<?php
/**
* REST API: WP_REST_Widget_Types_Controller class
*
* @package WordPress
* @subpackage REST_API
* @since 5.8.0
*/
/**
* Core class to access widget types via the REST API.
*
* @since 5.8.0
*
* @see WP_REST_Controller
*/
class WP_REST_Widget_Types_Controller extends WP_REST_Controller {
/**
* Constructor.
*
* @since 5.8.0
*/
public function __construct() {
$this->namespace = 'wp/v2';
$this->rest_base = 'widget-types';
}
/**
* Registers the widget type routes.
*
* @since 5.8.0
*
* @see register_rest_route()
*/
public function register_routes() {
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/(?P<id>[a-zA-Z0-9_-]+)',
array(
'args' => array(
'id' => array(
'description' => __( 'The widget type id.' ),
'type' => 'string',
),
),
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => $this->get_collection_params(),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/(?P<id>[a-zA-Z0-9_-]+)/encode',
array(
'args' => array(
'id' => array(
'description' => __( 'The widget type id.' ),
'type' => 'string',
'required' => true,
),
'instance' => array(
'description' => __( 'Current instance settings of the widget.' ),
'type' => 'object',
),
'form_data' => array(
'description' => __( 'Serialized widget form data to encode into instance settings.' ),
'type' => 'string',
'sanitize_callback' => static function( $string ) {
$array = array();
wp_parse_str( $string, $array );
return $array;
},
),
),
array(
'methods' => WP_REST_Server::CREATABLE,
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'callback' => array( $this, 'encode_form_data' ),
),
)
);
}
/**
* Checks whether a given request has permission to read widget types.
*
* @since 5.8.0
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has read access, WP_Error object otherwise.
*/
public function get_items_permissions_check( $request ) {
return $this->check_read_permission();
}
/**
* Retrieves the list of all widget types.
*
* @since 5.8.0
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_items( $request ) {
$data = array();
foreach ( $this->get_widgets() as $widget ) {
$widget_type = $this->prepare_item_for_response( $widget, $request );
$data[] = $this->prepare_response_for_collection( $widget_type );
}
return rest_ensure_response( $data );
}
/**
* Checks if a given request has access to read a widget type.
*
* @since 5.8.0
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise.
*/
public function get_item_permissions_check( $request ) {
$check = $this->check_read_permission();
if ( is_wp_error( $check ) ) {
return $check;
}
$widget_id = $request['id'];
$widget_type = $this->get_widget( $widget_id );
if ( is_wp_error( $widget_type ) ) {
return $widget_type;
}
return true;
}
/**
* Checks whether the user can read widget types.
*
* @since 5.8.0
*
* @return true|WP_Error True if the widget type is visible, WP_Error otherwise.
*/
protected function check_read_permission() {
if ( ! current_user_can( 'edit_theme_options' ) ) {
return new WP_Error(
'rest_cannot_manage_widgets',
__( 'Sorry, you are not allowed to manage widgets on this site.' ),
array(
'status' => rest_authorization_required_code(),
)
);
}
return true;
}
/**
* Gets the details about the requested widget.
*
* @since 5.8.0
*
* @param string $id The widget type id.
* @return array|WP_Error The array of widget data if the name is valid, WP_Error otherwise.
*/
public function get_widget( $id ) {
foreach ( $this->get_widgets() as $widget ) {
if ( $id === $widget['id'] ) {
return $widget;
}
}
return new WP_Error( 'rest_widget_type_invalid', __( 'Invalid widget type.' ), array( 'status' => 404 ) );
}
/**
* Normalize array of widgets.
*
* @since 5.8.0
*
* @global WP_Widget_Factory $wp_widget_factory
* @global array $wp_registered_widgets The list of registered widgets.
*
* @return array Array of widgets.
*/
protected function get_widgets() {
global $wp_widget_factory, $wp_registered_widgets;
$widgets = array();
foreach ( $wp_registered_widgets as $widget ) {
$parsed_id = wp_parse_widget_id( $widget['id'] );
$widget_object = $wp_widget_factory->get_widget_object( $parsed_id['id_base'] );
$widget['id'] = $parsed_id['id_base'];
$widget['is_multi'] = (bool) $widget_object;
if ( isset( $widget['name'] ) ) {
$widget['name'] = html_entity_decode( $widget['name'], ENT_QUOTES, get_bloginfo( 'charset' ) );
}
if ( isset( $widget['description'] ) ) {
$widget['description'] = html_entity_decode( $widget['description'], ENT_QUOTES, get_bloginfo( 'charset' ) );
}
unset( $widget['callback'] );
$classname = '';
foreach ( (array) $widget['classname'] as $cn ) {
if ( is_string( $cn ) ) {
$classname .= '_' . $cn;
} elseif ( is_object( $cn ) ) {
$classname .= '_' . get_class( $cn );
}
}
$widget['classname'] = ltrim( $classname, '_' );
$widgets[ $widget['id'] ] = $widget;
}
return $widgets;
}
/**
* Retrieves a single widget type from the collection.
*
* @since 5.8.0
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function get_item( $request ) {
$widget_id = $request['id'];
$widget_type = $this->get_widget( $widget_id );
if ( is_wp_error( $widget_type ) ) {
return $widget_type;
}
$data = $this->prepare_item_for_response( $widget_type, $request );
return rest_ensure_response( $data );
}
/**
* Prepares a widget type object for serialization.
*
* @since 5.8.0
* @since 5.9.0 Renamed `$widget_type` to `$item` to match parent class for PHP 8 named parameter support.
*
* @param array $item Widget type data.
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response Widget type data.
*/
public function prepare_item_for_response( $item, $request ) {
// Restores the more descriptive, specific name for use within this method.
$widget_type = $item;
$fields = $this->get_fields_for_response( $request );
$data = array(
'id' => $widget_type['id'],
);
$schema = $this->get_item_schema();
$extra_fields = array(
'name',
'description',
'is_multi',
'classname',
'widget_class',
'option_name',
'customize_selective_refresh',
);
foreach ( $extra_fields as $extra_field ) {
if ( ! rest_is_field_included( $extra_field, $fields ) ) {
continue;
}
if ( isset( $widget_type[ $extra_field ] ) ) {
$field = $widget_type[ $extra_field ];
} elseif ( array_key_exists( 'default', $schema['properties'][ $extra_field ] ) ) {
$field = $schema['properties'][ $extra_field ]['default'];
} else {
$field = '';
}
$data[ $extra_field ] = rest_sanitize_value_from_schema( $field, $schema['properties'][ $extra_field ] );
}
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $widget_type ) );
/**
* Filters the REST API response for a widget type.
*
* @since 5.8.0
*
* @param WP_REST_Response $response The response object.
* @param array $widget_type The array of widget data.
* @param WP_REST_Request $request The request object.
*/
return apply_filters( 'rest_prepare_widget_type', $response, $widget_type, $request );
}
/**
* Prepares links for the widget type.
*
* @since 5.8.0
*
* @param array $widget_type Widget type data.
* @return array Links for the given widget type.
*/
protected function prepare_links( $widget_type ) {
return array(
'collection' => array(
'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
),
'self' => array(
'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $widget_type['id'] ) ),
),
);
}
/**
* Retrieves the widget type's schema, conforming to JSON Schema.
*
* @since 5.8.0
*
* @return array Item schema data.
*/
public function get_item_schema() {
if ( $this->schema ) {
return $this->add_additional_fields_schema( $this->schema );
}
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'widget-type',
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Unique slug identifying the widget type.' ),
'type' => 'string',
'context' => array( 'embed', 'view', 'edit' ),
'readonly' => true,
),
'name' => array(
'description' => __( 'Human-readable name identifying the widget type.' ),
'type' => 'string',
'default' => '',
'context' => array( 'embed', 'view', 'edit' ),
'readonly' => true,
),
'description' => array(
'description' => __( 'Description of the widget.' ),
'type' => 'string',
'default' => '',
'context' => array( 'view', 'edit', 'embed' ),
),
'is_multi' => array(
'description' => __( 'Whether the widget supports multiple instances' ),
'type' => 'boolean',
'context' => array( 'view', 'edit', 'embed' ),
'readonly' => true,
),
'classname' => array(
'description' => __( 'Class name' ),
'type' => 'string',
'default' => '',
'context' => array( 'embed', 'view', 'edit' ),
'readonly' => true,
),
),
);
$this->schema = $schema;
return $this->add_additional_fields_schema( $this->schema );
}
/**
* An RPC-style endpoint which can be used by clients to turn user input in
* a widget admin form into an encoded instance object.
*
* Accepts:
*
* - id: A widget type ID.
* - instance: A widget's encoded instance object. Optional.
* - form_data: Form data from submitting a widget's admin form. Optional.
*
* Returns:
* - instance: The encoded instance object after updating the widget with
* the given form data.
* - form: The widget's admin form after updating the widget with the
* given form data.
*
* @since 5.8.0
*
* @global WP_Widget_Factory $wp_widget_factory
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
*/
public function encode_form_data( $request ) {
global $wp_widget_factory;
$id = $request['id'];
$widget_object = $wp_widget_factory->get_widget_object( $id );
if ( ! $widget_object ) {
return new WP_Error(
'rest_invalid_widget',
__( 'Cannot preview a widget that does not extend WP_Widget.' ),
array( 'status' => 400 )
);
}
// Set the widget's number so that the id attributes in the HTML that we
// return are predictable.
if ( isset( $request['number'] ) && is_numeric( $request['number'] ) ) {
$widget_object->_set( (int) $request['number'] );
} else {
$widget_object->_set( -1 );
}
if ( isset( $request['instance']['encoded'], $request['instance']['hash'] ) ) {
$serialized_instance = base64_decode( $request['instance']['encoded'] );
if ( ! hash_equals( wp_hash( $serialized_instance ), $request['instance']['hash'] ) ) {
return new WP_Error(
'rest_invalid_widget',
__( 'The provided instance is malformed.' ),
array( 'status' => 400 )
);
}
$instance = unserialize( $serialized_instance );
} else {
$instance = array();
}
if (
isset( $request['form_data'][ "widget-$id" ] ) &&
is_array( $request['form_data'][ "widget-$id" ] )
) {
$new_instance = array_values( $request['form_data'][ "widget-$id" ] )[0];
$old_instance = $instance;
$instance = $widget_object->update( $new_instance, $old_instance );
/** This filter is documented in wp-includes/class-wp-widget.php */
$instance = apply_filters(
'widget_update_callback',
$instance,
$new_instance,
$old_instance,
$widget_object
);
}
$serialized_instance = serialize( $instance );
$widget_key = $wp_widget_factory->get_widget_key( $id );
$response = array(
'form' => trim(
$this->get_widget_form(
$widget_object,
$instance
)
),
'preview' => trim(
$this->get_widget_preview(
$widget_key,
$instance
)
),
'instance' => array(
'encoded' => base64_encode( $serialized_instance ),
'hash' => wp_hash( $serialized_instance ),
),
);
if ( ! empty( $widget_object->widget_options['show_instance_in_rest'] ) ) {
// Use new stdClass so that JSON result is {} and not [].
$response['instance']['raw'] = empty( $instance ) ? new stdClass : $instance;
}
return rest_ensure_response( $response );
}
/**
* Returns the output of WP_Widget::widget() when called with the provided
* instance. Used by encode_form_data() to preview a widget.
* @since 5.8.0
*
* @param string $widget The widget's PHP class name (see class-wp-widget.php).
* @param array $instance Widget instance settings.
* @return string
*/
private function get_widget_preview( $widget, $instance ) {
ob_start();
the_widget( $widget, $instance );
return ob_get_clean();
}
/**
* Returns the output of WP_Widget::form() when called with the provided
* instance. Used by encode_form_data() to preview a widget's form.
*
* @since 5.8.0
*
* @param WP_Widget $widget_object Widget object to call widget() on.
* @param array $instance Widget instance settings.
* @return string
*/
private function get_widget_form( $widget_object, $instance ) {
ob_start();
/** This filter is documented in wp-includes/class-wp-widget.php */
$instance = apply_filters(
'widget_form_callback',
$instance,
$widget_object
);
if ( false !== $instance ) {
$return = $widget_object->form( $instance );
/** This filter is documented in wp-includes/class-wp-widget.php */
do_action_ref_array(
'in_widget_form',
array( &$widget_object, &$return, $instance )
);
}
return ob_get_clean();
}
/**
* Retrieves the query params for collections.
*
* @since 5.8.0
*
* @return array Collection parameters.
*/
public function get_collection_params() {
return array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
);
}
}