Skip to content

Commit

Permalink
Add legacy widget by id
Browse files Browse the repository at this point in the history
  • Loading branch information
jorgefilipecosta committed Jun 26, 2019
1 parent 66775dc commit c936b9f
Show file tree
Hide file tree
Showing 9 changed files with 325 additions and 110 deletions.
2 changes: 1 addition & 1 deletion lib/class-experimental-wp-widget-blocks-manager.php
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ private static function get_widget_class( $widget_id ) {
* @param string $id Idenfitier of the widget instance.
* @return array Array containing the widget instance.
*/
private static function get_sidebar_widget_instance( $sidebar, $id ) {
public static function get_sidebar_widget_instance( $sidebar, $id ) {
list( $object, $number, $name ) = self::get_widget_info( $id );
if ( ! $object ) {
return array();
Expand Down
180 changes: 148 additions & 32 deletions lib/class-wp-rest-widget-updater-controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,30 @@ 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 . '/',
array(
'args' => array(
'identifier' => array(
'widget_class' => array(
'description' => __( 'Class name of the widget.', 'gutenberg' ),
'type' => 'string',
'required' => false,
'default' => null,
),
'identifier' => array(
'description' => __( 'Identifier of the widget.', 'gutenberg' ),
'type' => 'string',
'required' => false,
'default' => null,
),
'instance' => array(
'description' => __( 'Current widget instance', 'gutenberg' ),
'type' => 'object',
'default' => array(),
),
'instance_changes' => array(
'description' => __( 'Array of instance changes', 'gutenberg' ),
'type' => 'object',
'default' => array(),
),
),
array(
Expand Down Expand Up @@ -76,53 +93,120 @@ 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 Instance identifier of the widget.
* @param string $widget_class Name of the class the widget references.
*
* @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 the 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, $widget_class ) {
global $wp_widget_factory, $wp_registered_widgets;
if ( ! $identifier && ! $widget_class ) {
return false;
}
if ( $identifier ) {
return isset( $wp_registered_widgets[ $identifier ] );
}
return isset( $wp_widget_factory->widgets[ $widget_class ] ) &&
( $wp_widget_factory->widgets[ $widget_class ] instanceof WP_Widget );
}

global $wp_widget_factory;
/**
* Computes an array with instance changes cleaned of widget specific prefixes and sufixes.
*
* @since 5.7.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 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;
}

/**
* Returns the edit form of the widget being referenced.
*
* @since 5.7.0
* @param string $identifier Instance identifier of the widget.
*
* @return WP_REST_Response Response object.
*/
private function handle_reference_widgets( $identifier ) {
global $wp_registered_widget_controls;
$form = '';
$id_base = $identifier;
$id = $identifier;
$number = null;
if (
null === $widget ||
! isset( $wp_widget_factory->widgets[ $widget ] ) ||
! ( $wp_widget_factory->widgets[ $widget ] instanceof WP_Widget )
isset( $wp_registered_widget_controls[ $identifier ]['callback'] ) &&
is_callable( $wp_registered_widget_controls[ $identifier ]['callback'] )
) {
return new WP_Error(
'widget_invalid',
__( 'Invalid widget.', 'gutenberg' ),
array(
'status' => 404,
)
);
$control = $wp_registered_widget_controls[ $identifier ];
ob_start();
call_user_func_array( $control['callback'], $control['params'] );
$form = ob_get_clean();
if ( isset( $control['id_base'] ) ) {
$id_base = $control['id_base'];
}
if ( isset( $control['params'][0]['number'] ) ) {
$number = $control['params'][0]['number'];
}
}

$widget_obj = $wp_widget_factory->widgets[ $widget ];
return rest_ensure_response(
array(
'instance' => array(),
'form' => $form,
'id_base' => $id_base,
'id' => $id,
'number' => $number,
)
);
}

$instance = $request->get_param( 'instance' );
/**
* Returns the new class widget instance and the form that represents it.
*
* @since 5.7.0
* @access public
*
* @param string $widget_class 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 handle_class_widgets( $widget_class, $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[ $widget_class ];

$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,20 +250,52 @@ 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(
'instance' => $instance,
'form' => $form,
'id_base' => $id_base,
'id' => $id,
'number' => $id_to_use,
)
);
}

/**
* Returns the new widget instance and the form that represents it.
*
* @since 5.7.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' );
$widget_class = $request->get_param( 'widget_class' );

if ( ! $this->is_valid_widget( $identifier, $widget_class ) ) {
return new WP_Error(
'widget_invalid',
__( 'Invalid widget.', 'gutenberg' ),
array(
'status' => 404,
)
);
}

if ( $identifier ) {
return $this->handle_reference_widgets( $identifier );
}
return $this->handle_class_widgets(
$widget_class,
$request->get_param( 'instance' ),
$request->get_param( 'instance_changes' ),
$request->get_param( 'id_to_use' )
);
}
}
/**
* End: Include for phase 2
Expand Down
2 changes: 2 additions & 0 deletions lib/load.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
}
if ( ! class_exists( 'WP_REST_Widget_Areas_Controller' ) ) {
require dirname( __FILE__ ) . '/class-experimental-wp-widget-blocks-manager.php';
}
if ( ! class_exists( 'WP_REST_Widget_Areas_Controller' ) ) {
require dirname( __FILE__ ) . '/class-wp-rest-widget-areas-controller.php';
}
/**
Expand Down
1 change: 1 addition & 0 deletions lib/widgets-page.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ function gutenberg_widgets_init( $hook ) {
'wp.blocks.unstable__bootstrapServerSideBlockDefinitions(' . wp_json_encode( get_block_editor_server_block_settings() ) . ');'
);
wp_enqueue_script( 'wp-edit-widgets' );
wp_enqueue_script( 'admin-widgets' );
wp_enqueue_script( 'wp-format-library' );
wp_enqueue_style( 'wp-edit-widgets' );
wp_enqueue_style( 'wp-format-library' );
Expand Down
12 changes: 12 additions & 0 deletions lib/widgets.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ function gutenberg_block_editor_admin_print_footer_scripts() {
*/
function gutenberg_block_editor_admin_footer() {
if ( gutenberg_is_block_editor() ) {
echo '<form method="post">';
echo wp_nonce_field( 'save-sidebar-widgets', '_wpnonce_widgets', false );
echo '</form>';
/** This action is documented in wp-admin/admin-footer.php */
// phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
do_action( 'admin_footer-widgets.php' );
Expand Down Expand Up @@ -213,3 +216,12 @@ function gutenberg_create_wp_area_post_type() {
add_action( 'init', 'gutenberg_create_wp_area_post_type' );

add_filter( 'sidebars_widgets', 'Experimental_WP_Widget_Blocks_Manager::swap_out_sidebars_blocks_for_block_widgets' );

/**
* Function to enqueue admin-widgets as part of the block editor assets.
*/
function gutenberg_enqueue_widget_scripts() {
wp_enqueue_script( 'admin-widgets' );
}

add_action( 'enqueue_block_editor_assets', 'gutenberg_enqueue_widget_scripts' );
38 changes: 26 additions & 12 deletions packages/block-library/src/legacy-widget/edit/dom-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ class LegacyWidgetEditDomManager extends Component {
this.containerRef = createRef();
this.formRef = createRef();
this.widgetContentRef = createRef();
this.idBaseInputRef = createRef();
this.widgetNumberInputRef = createRef();
this.triggerWidgetEvent = this.triggerWidgetEvent.bind( this );
}

Expand All @@ -27,11 +29,22 @@ class LegacyWidgetEditDomManager extends Component {
}

shouldComponentUpdate( nextProps ) {
let shouldTriggerWidgetUpdateEvent = false;
// We can not leverage react render otherwise we would destroy dom changes applied by the plugins.
// We manually update the required dom node replicating what the widget screen and the customizer do.
if ( nextProps.idBase !== this.props.idBase && this.idBaseInputRef.current ) {
this.idBaseInputRef.current.value = nextProps.idBase;
shouldTriggerWidgetUpdateEvent = true;
}
if ( nextProps.widgetNumber !== this.props.widgetNumber && this.widgetNumberInputRef.current ) {
this.widgetNumberInputRef.current.value = nextProps.widgetNumber;
}
if ( nextProps.form !== this.props.form && this.widgetContentRef.current ) {
const widgetContent = this.widgetContentRef.current;
widgetContent.innerHTML = nextProps.form;
shouldTriggerWidgetUpdateEvent = true;
}
if ( shouldTriggerWidgetUpdateEvent ) {
this.triggerWidgetEvent( 'widget-updated' );
this.previousFormData = new window.FormData(
this.formRef.current
Expand All @@ -41,7 +54,7 @@ class LegacyWidgetEditDomManager extends Component {
}

render() {
const { id, idBase, widgetNumber, form } = this.props;
const { id, idBase, widgetNumber, form, identifier } = this.props;
return (
<div className="widget open" ref={ this.containerRef }>
<div className="widget-inside">
Expand All @@ -50,6 +63,11 @@ class LegacyWidgetEditDomManager extends Component {
method="post"
onBlur={ () => {
if ( this.shouldTriggerInstanceUpdate() ) {
if ( identifier ) {
if ( this.containerRef.current ) {
window.wpWidgets.save( window.$( this.containerRef.current ) );
}
}
this.props.onInstanceChange(
this.retrieveUpdatedInstance()
);
Expand All @@ -62,8 +80,8 @@ class LegacyWidgetEditDomManager extends Component {
dangerouslySetInnerHTML={ { __html: form } }
/>
<input type="hidden" name="widget-id" className="widget-id" value={ id } />
<input type="hidden" name="id_base" className="id_base" value={ idBase } />
<input type="hidden" name="widget_number" className="widget_number" value={ widgetNumber } />
<input ref={ this.idBaseInputRef } type="hidden" name="id_base" className="id_base" value={ idBase } />
<input ref={ this.widgetNumberInputRef } type="hidden" name="widget_number" className="widget_number" value={ widgetNumber } />
<input type="hidden" name="multi_number" className="multi_number" value="" />
<input type="hidden" name="add_new" className="add_new" value="" />
</form>
Expand Down Expand Up @@ -110,28 +128,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
Loading

0 comments on commit c936b9f

Please sign in to comment.