diff --git a/lib/class-wp-rest-widget-updater-controller.php b/lib/class-wp-rest-widget-updater-controller.php index 23c46887ba573b..5e528473517128 100644 --- a/lib/class-wp-rest-widget-updater-controller.php +++ b/lib/class-wp-rest-widget-updater-controller.php @@ -34,8 +34,7 @@ public function __construct() { public function register_routes() { register_rest_route( $this->namespace, - // Regex representing a PHP class extracted from http://php.net/manual/en/language.oop5.basic.php. - '/' . $this->rest_base . '/(?P[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)/', + '/' . $this->rest_base . '/(?P[\w-_]+)/', array( 'args' => array( 'identifier' => array( @@ -76,53 +75,107 @@ public function compute_new_widget_permissions_check() { } /** - * Returns the new widget instance and the form that represents it. + * Checks if the widget being referenced is valid. * * @since 5.2.0 - * @access public + * @param string $identifier Widget id for callback widgets or widget class name for class widgets. + * @param boolean $is_callback_widget If true the widget is a back widget if false the widget is a class widget. * - * @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. + * @return boolean True if widget being referenced exists and false otherwise. */ - public function compute_new_widget( $request ) { - $url_params = $request->get_url_params(); - - $widget = $request->get_param( 'identifier' ); - + private function is_valid_widget( $identifier, $is_callback_widget ) { + if ( null === $identifier ) { + return false; + } + if ( $is_callback_widget ) { + global $wp_registered_widget_controls; + return isset( $wp_registered_widget_controls[ $identifier ]['callback'] ) && + is_callable( $wp_registered_widget_controls[ $identifier ]['callback'] ); + } global $wp_widget_factory; + return isset( $wp_widget_factory->widgets[ $identifier ] ) && + ( $wp_widget_factory->widgets[ $identifier ] instanceof WP_Widget ); + } - if ( - null === $widget || - ! isset( $wp_widget_factory->widgets[ $widget ] ) || - ! ( $wp_widget_factory->widgets[ $widget ] instanceof WP_Widget ) - ) { - return new WP_Error( - 'widget_invalid', - __( 'Invalid widget.', 'gutenberg' ), - array( - 'status' => 404, - ) - ); + /** + * Computes an array with instance changes cleaned of widget specific prefixes and sufixes. + * + * @since 5.2.0 + * @param string $id_base Widget ID Base. + * @param string $id Widget instance identifier. + * @param array $instance_changes Array with the form values being being changed. + * + * @return array An array based on $instance_changes but whose keys have the widget specific sufixes and prefixes removed. + */ + private function parse_instance_changes( $id_base, $id, $instance_changes ) { + $instance_changes_parsed = array(); + $start_position = strlen( 'widget-' . $id_base . '[' . $id . '][' ); + foreach ( $instance_changes as $key => $value ) { + $key_parsed = substr( $key, $start_position, -1 ); + $instance_changes_parsed[ $key_parsed ] = $value; } + return $instance_changes_parsed; + } - $widget_obj = $wp_widget_factory->widgets[ $widget ]; + /** + * Returns the bew callback widget form. + * + * @since 5.2.0 + * @param string $identifier Widget id for callback widgets or widget class name for class widgets. + * @param array $instance_changes Array with the form values being being changed. + * + * @return WP_REST_Response Response object. + */ + private function compute_new_widget_handle_callback_widgets( $identifier, $instance_changes ) { + global $wp_registered_widget_controls; + $control = $wp_registered_widget_controls[ $identifier ]; + $_POST = array_merge( $_POST, $instance_changes ); + ob_start(); + call_user_func_array( $control['callback'], $control['params'] ); + $form = ob_get_clean(); + return rest_ensure_response( + array( + 'instance' => array(), + 'form' => $form, + 'id_base' => $identifier, + 'id' => $identifier, + ) + ); + } - $instance = $request->get_param( 'instance' ); + /** + * Returns the new class widget instance and the form that represents it. + * + * @since 5.2.0 + * @access public + * + * @param string $identifier Widget id for callback widgets or widget class name for class widgets. + * @param array $instance Previous widget instance. + * @param array $instance_changes Array with the form values being being changed. + * @param string $id_to_use Identifier of the specific widget instance. + * @return WP_REST_Response Response object on success, or WP_Error object on failure. + */ + private function compute_new_widget_handle_class_widgets( $identifier, $instance, $instance_changes, $id_to_use ) { if ( null === $instance ) { $instance = array(); } - $id_to_use = $request->get_param( 'id_to_use' ); if ( null === $id_to_use ) { $id_to_use = -1; } + global $wp_widget_factory; + $widget_obj = $wp_widget_factory->widgets[ $identifier ]; + $widget_obj->_set( $id_to_use ); + $id_base = $widget_obj->id_base; + $id = $widget_obj->id; ob_start(); - $instance_changes = $request->get_param( 'instance_changes' ); if ( null !== $instance_changes ) { - $old_instance = $instance; - $instance = $widget_obj->update( $instance_changes, $old_instance ); + $instance_changes = $this->parse_instance_changes( $id_base, $id_to_use, $instance_changes ); + $old_instance = $instance; + $instance = $widget_obj->update( $instance_changes, $old_instance ); + /** * Filters a widget's settings before saving. * @@ -166,10 +219,7 @@ public function compute_new_widget( $request ) { */ do_action_ref_array( 'in_widget_form', array( &$widget_obj, &$return, $instance ) ); } - - $id_base = $widget_obj->id_base; - $id = $widget_obj->id; - $form = ob_get_clean(); + $form = ob_get_clean(); return rest_ensure_response( array( @@ -180,6 +230,46 @@ public function compute_new_widget( $request ) { ) ); } + + /** + * Returns the new widget instance and the form that represents it. + * + * @since 5.2.0 + * @access public + * + * @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 compute_new_widget( $request ) { + $identifier = $request->get_param( 'identifier' ); + $is_callback_widget = $request->get_param( 'is_callback_widget' ); + if ( null === $is_callback_widget ) { + $is_callback_widget = false; + } + + if ( ! $this->is_valid_widget( $identifier, $is_callback_widget ) ) { + return new WP_Error( + 'widget_invalid', + __( 'Invalid widget.', 'gutenberg' ), + array( + 'status' => 404, + ) + ); + } + + if ( $is_callback_widget ) { + return $this->compute_new_widget_handle_callback_widgets( + $identifier, + $request->get_param( 'instance_changes' ) + ); + } + return $this->compute_new_widget_handle_class_widgets( + $identifier, + $request->get_param( 'instance' ), + $request->get_param( 'instance_changes' ), + $request->get_param( 'id_to_use' ) + ); + } } /** * End: Include for phase 2 diff --git a/lib/widgets.php b/lib/widgets.php index e35ccf5b0ef385..2ef775c6859bdb 100644 --- a/lib/widgets.php +++ b/lib/widgets.php @@ -90,7 +90,9 @@ function gutenberg_legacy_widget_settings( $settings ) { $has_permissions_to_manage_widgets = current_user_can( 'edit_theme_options' ); $available_legacy_widgets = array(); - global $wp_widget_factory, $wp_registered_widgets; + global $wp_widget_factory, $wp_registered_widgets, $wp_registered_widget_controls; + // Add widgets implemented as a class to the available_legacy_widgets widgets array. + // All widgets implemented as a class have an edit form. foreach ( $wp_widget_factory->widgets as $class => $widget_obj ) { if ( ! in_array( $class, $core_widgets ) ) { $available_legacy_widgets[ $class ] = array( @@ -102,10 +104,13 @@ function gutenberg_legacy_widget_settings( $settings ) { html_entity_decode( $widget_obj->widget_options['description'] ) : null, 'isCallbackWidget' => false, + 'hasEditForm' => true, ); } } + // Add widgets registered using wp_register_sidebar_widget. foreach ( $wp_registered_widgets as $widget_id => $widget_obj ) { + // Skip instances of widgets that are implemented as classes. if ( is_array( $widget_obj['callback'] ) && isset( $widget_obj['callback'][0] ) && @@ -113,10 +118,20 @@ function gutenberg_legacy_widget_settings( $settings ) { ) { continue; } + // By default widgets registered with wp_register_sidebar_widget don't have an edit form, but a form may be added. + $has_edit_form = false; + // Checks if an edit form was added. + if ( + isset( $wp_registered_widget_controls[ $widget_id ]['callback'] ) && + is_callable( $wp_registered_widget_controls[ $widget_id ]['callback'] ) + ) { + $has_edit_form = true; + } $available_legacy_widgets[ $widget_id ] = array( 'name' => html_entity_decode( $widget_obj['name'] ), 'description' => html_entity_decode( wp_widget_description( $widget_id ) ), 'isCallbackWidget' => true, + 'hasEditForm' => $has_edit_form, ); } diff --git a/packages/block-library/src/legacy-widget/edit/dom-manager.js b/packages/block-library/src/legacy-widget/edit/dom-manager.js index d4a0b43829f29c..67d58440b57ef5 100644 --- a/packages/block-library/src/legacy-widget/edit/dom-manager.js +++ b/packages/block-library/src/legacy-widget/edit/dom-manager.js @@ -110,28 +110,24 @@ class LegacyWidgetEditDomManager extends Component { retrieveUpdatedInstance() { if ( this.formRef.current ) { - const { idBase, widgetNumber } = this.props; const form = this.formRef.current; const formData = new window.FormData( form ); const updatedInstance = {}; - const keyPrefixLength = `widget-${ idBase }[${ widgetNumber }][`.length; - const keySuffixLength = `]`.length; - for ( const rawKey of formData.keys() ) { + for ( const key of formData.keys() ) { // This fields are added to the form because the widget JavaScript code may use this values. // They are not relevant for the update mechanism. if ( includes( [ 'widget-id', 'id_base', 'widget_number', 'multi_number', 'add_new' ], - rawKey, + key, ) ) { continue; } - const keyParsed = rawKey.substring( keyPrefixLength, rawKey.length - keySuffixLength ); - const value = formData.getAll( rawKey ); + const value = formData.getAll( key ); if ( value.length > 1 ) { - updatedInstance[ keyParsed ] = value; + updatedInstance[ key ] = value; } else { - updatedInstance[ keyParsed ] = value[ 0 ]; + updatedInstance[ key ] = value[ 0 ]; } } return updatedInstance; diff --git a/packages/block-library/src/legacy-widget/edit/handler.js b/packages/block-library/src/legacy-widget/edit/handler.js index 157ab653fb89f5..881421cc7efb1f 100644 --- a/packages/block-library/src/legacy-widget/edit/handler.js +++ b/packages/block-library/src/legacy-widget/edit/handler.js @@ -86,7 +86,7 @@ class LegacyWidgetEditHandler extends Component { } requestWidgetUpdater( instanceChanges, callback ) { - const { identifier, instanceId, instance } = this.props; + const { identifier, instanceId, instance, isCallbackWidget } = this.props; if ( ! identifier ) { return; } @@ -98,6 +98,7 @@ class LegacyWidgetEditHandler extends Component { instance, // use negative ids to make sure the id does not exist on the database. id_to_use: instanceId * -1, + is_callback_widget: isCallbackWidget, instance_changes: instanceChanges, }, method: 'POST', diff --git a/packages/block-library/src/legacy-widget/edit/index.js b/packages/block-library/src/legacy-widget/edit/index.js index 4b645318eb2277..bc4625054906c5 100644 --- a/packages/block-library/src/legacy-widget/edit/index.js +++ b/packages/block-library/src/legacy-widget/edit/index.js @@ -48,7 +48,7 @@ class LegacyWidgetEdit extends Component { setAttributes, } = this.props; const { isPreview } = this.state; - const { identifier, isCallbackWidget } = attributes; + const { identifier, isCallbackWidget, hasEditForm } = attributes; const widgetObject = identifier && availableLegacyWidgets[ identifier ]; if ( ! widgetObject ) { let placeholderContent; @@ -66,6 +66,7 @@ class LegacyWidgetEdit extends Component { instance: {}, identifier: value, isCallbackWidget: availableLegacyWidgets[ value ].isCallbackWidget, + hasEditForm: availableLegacyWidgets[ value ].hasEditForm, } ) } options={ [ { value: 'none', label: 'Select widget' } ].concat( map( availableLegacyWidgets, ( widget, key ) => { @@ -115,8 +116,8 @@ class LegacyWidgetEdit extends Component { icon="update" > - { ! isCallbackWidget && ( - <> + { hasEditForm && ( + - + ) } { inspectorControls } - { ! isCallbackWidget && ( + { hasEditForm && ( { this.props.setAttributes( { @@ -148,7 +150,7 @@ class LegacyWidgetEdit extends Component { } /> ) } - { ( isPreview || isCallbackWidget ) && this.renderWidgetPreview() } + { ( isPreview || ! hasEditForm ) && this.renderWidgetPreview() } ); } diff --git a/packages/block-library/src/legacy-widget/index.php b/packages/block-library/src/legacy-widget/index.php index 7f06f086aa7295..03c42bed6b97be 100644 --- a/packages/block-library/src/legacy-widget/index.php +++ b/packages/block-library/src/legacy-widget/index.php @@ -72,6 +72,9 @@ function register_block_core_legacy_widget() { 'isCallbackWidget' => array( 'type' => 'boolean', ), + 'hasEditForm' => array( + 'type' => 'boolean', + ), ), 'render_callback' => 'render_block_legacy_widget', )