Skip to content

Commit

Permalink
Fix: Legacy widget: allow to use the edit form of callback widgets wi…
Browse files Browse the repository at this point in the history
…th a custom form.
  • Loading branch information
jorgefilipecosta committed May 15, 2019
1 parent 5bbda36 commit 3045a2d
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 50 deletions.
156 changes: 123 additions & 33 deletions lib/class-wp-rest-widget-updater-controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -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<identifier>[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)/',
'/' . $this->rest_base . '/(?P<identifier>[\w-_]+)/',
array(
'args' => array(
'identifier' => array(
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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(
Expand All @@ -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
Expand Down
17 changes: 16 additions & 1 deletion lib/widgets.php
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -102,21 +104,34 @@ 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] ) &&
( $widget_obj['callback'][0] instanceof WP_Widget )
) {
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,
);
}

Expand Down
14 changes: 5 additions & 9 deletions packages/block-library/src/legacy-widget/edit/dom-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 2 additions & 1 deletion packages/block-library/src/legacy-widget/edit/handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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',
Expand Down
14 changes: 8 additions & 6 deletions packages/block-library/src/legacy-widget/edit/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 ) => {
Expand Down Expand Up @@ -115,8 +116,8 @@ class LegacyWidgetEdit extends Component {
icon="update"
>
</IconButton>
{ ! isCallbackWidget && (
<>
{ hasEditForm && (
<Fragment>
<Button
className={ `components-tab-button ${ ! isPreview ? 'is-active' : '' }` }
onClick={ this.switchToEdit }
Expand All @@ -129,16 +130,17 @@ class LegacyWidgetEdit extends Component {
>
<span>{ __( 'Preview' ) }</span>
</Button>
</>
</Fragment>
) }
</Toolbar>
</BlockControls>
{ inspectorControls }
{ ! isCallbackWidget && (
{ hasEditForm && (
<LegacyWidgetEditHandler
isVisible={ ! isPreview }
identifier={ attributes.identifier }
instance={ attributes.instance }
isCallbackWidget={ isCallbackWidget }
onInstanceChange={
( newInstance ) => {
this.props.setAttributes( {
Expand All @@ -148,7 +150,7 @@ class LegacyWidgetEdit extends Component {
}
/>
) }
{ ( isPreview || isCallbackWidget ) && this.renderWidgetPreview() }
{ ( isPreview || ! hasEditForm ) && this.renderWidgetPreview() }
</>
);
}
Expand Down
3 changes: 3 additions & 0 deletions packages/block-library/src/legacy-widget/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ function register_block_core_legacy_widget() {
'isCallbackWidget' => array(
'type' => 'boolean',
),
'hasEditForm' => array(
'type' => 'boolean',
),
),
'render_callback' => 'render_block_legacy_widget',
)
Expand Down

0 comments on commit 3045a2d

Please sign in to comment.