Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use iframes in legacy widget preview. #14643

Merged
merged 7 commits into from
Sep 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions lib/widget-preview-template.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php
/**
* Start: Include for phase 2
* Template used to render widget previews.
*
* @package gutenberg
* @since 5.4.0
*/

if ( ! function_exists( 'wp_head' ) ) {
exit;
}
?>
<!doctype html>
<html <?php language_attributes(); ?>>
<head>
<meta charset="<?php bloginfo( 'charset' ); ?>" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="profile" href="https://gmpg.org/xfn/11" />
<?php wp_head(); ?>
<style>
/* Reset theme styles */
html, body, #page, #content {
background: #FFF !important;
padding: 0 !important;
margin: 0 !important;
}
</style>
</head>

<body <?php body_class(); ?>>
<div id="page" class="site">
<div id="content" class="site-content">
<?php
$registry = WP_Block_Type_Registry::get_instance();
$block = $registry->get_registered( 'core/legacy-widget' );
echo $block->render( $_GET['widgetPreview'] );
?>
</div><!-- #content -->
</div><!-- #page -->

<?php wp_footer(); ?>
</body>
</html>
21 changes: 21 additions & 0 deletions lib/widgets.php
Original file line number Diff line number Diff line change
Expand Up @@ -323,3 +323,24 @@ function gutenberg_register_widgets() {
}

add_action( 'widgets_init', 'gutenberg_register_widgets' );

/**
* Overwrites the template WordPress would use to render the currrent request,
* to a widget preview template if widgetPreview parameter was passed in the url.
*
* @param string $template Original template.
*
* @return string The original or the path of widget-preview-template.php file.
*/
function change_post_template_to_widget_preview( $template ) {
if (
isset( $_GET['widgetPreview'] ) &&
current_user_can( 'edit_theme_options' )
) {
add_filter( 'show_admin_bar', '__return_false' );
return dirname( __FILE__ ) . '/widget-preview-template.php';
}
return $template;
}
add_filter( 'template_include', 'change_post_template_to_widget_preview' );

1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions packages/edit-widgets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"@wordpress/notices": "file:../notices",
"@wordpress/plugins": "file:../plugins",
"@wordpress/server-side-render": "file:../server-side-render",
"@wordpress/url": "file:../url",
"classnames": "^2.2.5",
"lodash": "^4.17.19",
"rememo": "^3.0.0"
Expand Down
17 changes: 9 additions & 8 deletions packages/edit-widgets/src/blocks/legacy-widget/edit/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ import { Button, PanelBody, ToolbarGroup } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import { withSelect } from '@wordpress/data';
import { BlockControls, InspectorControls } from '@wordpress/block-editor';
import ServerSideRender from '@wordpress/server-side-render';
import { update } from '@wordpress/icons';

/**
* Internal dependencies
*/
import LegacyWidgetEditHandler from './handler';
import LegacyWidgetPlaceholder from './placeholder';
import WidgetPreview from './widget-preview';

class LegacyWidgetEdit extends Component {
constructor() {
Expand Down Expand Up @@ -188,15 +188,12 @@ class LegacyWidgetEdit extends Component {
}

renderWidgetPreview() {
const { widgetId, attributes } = this.props;
const { attributes, widgetAreaId } = this.props;
return (
<ServerSideRender
<WidgetPreview
className="wp-block-legacy-widget__preview"
block="core/legacy-widget"
attributes={ {
widgetId,
...omit( attributes, 'referenceWidgetName' ),
} }
widgetAreaId={ widgetAreaId }
attributes={ omit( attributes, 'widgetId' ) }
/>
);
}
Expand All @@ -211,6 +208,9 @@ export default withSelect(
clientId
);
const widget = select( 'core/edit-widgets' ).getWidget( widgetId );
const widgetArea = select(
'core/edit-widgets'
).getWidgetAreaForClientId( clientId );
const editorSettings = select( 'core/block-editor' ).getSettings();
const {
availableLegacyWidgets,
Expand All @@ -231,6 +231,7 @@ export default withSelect(
hasPermissionsToManageWidgets,
availableLegacyWidgets,
widgetId,
widgetAreaId: widgetArea?.id,
WPWidget,
prerenderedEditForm: widget ? widget.rendered_form : '',
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* External dependencies
*/
import { get } from 'lodash';

/**
* WordPress dependencies
*/
import { addQueryArgs } from '@wordpress/url';
import { Disabled, FocusableIframe } from '@wordpress/components';
import { useState } from '@wordpress/element';

function WidgetPreview( { widgetAreaId, attributes, ...props } ) {
const DEFAULT_HEIGHT = 300;
const HEIGHT_MARGIN = 20;
const [ height, setHeight ] = useState( DEFAULT_HEIGHT );
const currentUrl = document.location.href;
const siteUrl = currentUrl.substr( 0, currentUrl.indexOf( 'wp-admin/' ) );
Copy link
Contributor

@adamziel adamziel Sep 18, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anyone aware of any better way of getting site's URL?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is available in the REST API index at wp-json.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually there is another place in navigation editor that could also use this correction - let's address both in a separate PR

const iframeUrl = addQueryArgs( siteUrl, {
widgetPreview: {
...attributes,
sidebarId: widgetAreaId,
},
} );
return (
<Disabled>
<FocusableIframe
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is FocusableIframe used if the onFocus prop is not?

onLoad={ ( event ) => {
const iframeContentHeight = get( event, [
'currentTarget',
'contentDocument',
'body',
'scrollHeight',
] );
if ( iframeContentHeight !== height ) {
setHeight( iframeContentHeight );
}
} }
src={ iframeUrl }
height={ height + HEIGHT_MARGIN }
{ ...props }
/>
</Disabled>
);
}

export default WidgetPreview;
136 changes: 62 additions & 74 deletions packages/edit-widgets/src/blocks/legacy-widget/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,107 +6,95 @@
*/

/**
* Returns the result of rendering a widget having its instance id.
* Register legacy widget block.
*/
function register_block_core_legacy_widget() {
register_block_type_from_metadata(
__DIR__ . '/legacy-widget',
array(
'render_callback' => 'render_block_core_legacy_widget',
)
);
}

/**
* Renders the `core/legacy-widget` block on server.
*
* @param string $id Widget id.
* @param array $attributes The block attributes.
*
* @return string Returns the rendered widget as a string.
* @return string Returns the post content with the legacy widget added.
* @see WP_Widget
*/
function block_core_legacy_widget_render_widget_by_id( $id ) {
// Code extracted from src/wp-includes/widgets.php dynamic_sidebar function.
// Todo: When merging to core extract this part of dynamic_sidebar into its own function.
global $wp_registered_widgets;
function render_block_core_legacy_widget( $attributes ) {
global $wp_widget_factory, $wp_registered_sidebars;

if ( ! isset( $wp_registered_widgets[ $id ] ) ) {
return false;
if ( isset( $attributes['widgetId'] ) ) {
return __( 'Rendering legacy widget block using widgetId is unsupported.', 'gutenberg' );
}
$widget_id = - 1;

if ( ! isset( $attributes['sidebarId'] ) || ! isset( $wp_registered_sidebars[ $attributes['sidebarId'] ] ) ) {
return '';
}
$sidebar_id = $attributes['sidebarId'];

if ( ! isset( $attributes['widgetClass'] ) || ! isset( $wp_widget_factory->widgets[ $attributes['widgetClass'] ] ) ) {
return '';
}
$widget_class = $attributes['widgetClass'];
$widget_obj = $wp_widget_factory->widgets[ $widget_class ];

$instance = isset( $attributes['instance'] ) ? $attributes['instance'] : null;

$widget_params = array_merge(
array(
'classname' => array(),
),
$widget_obj->widget_options
);

/** This filter is documented in wp-includes/widgets/widgets.php */
do_action( 'dynamic_sidebar_before', $sidebar_id, true );
$sidebar = $wp_registered_sidebars[ $sidebar_id ];

$params = array_merge(
array(
array_merge(
$sidebar,
array(
'before_widget' => '<div class="widget %s">',
'after_widget' => '</div>',
'before_title' => '<h2 class="widgettitle">',
'after_title' => '</h2>',
),
array(
'widget_id' => $id,
'widget_name' => $wp_registered_widgets[ $id ]['name'],
'widget_id' => $widget_id,
'widget_name' => $widget_obj->name,
)
),
),
(array) $wp_registered_widgets[ $id ]['params']
array(
$instance,
)
);

// Substitute HTML id and class attributes into before_widget.
// Substitute HTML `id` and `class` attributes into `before_widget`.
$classname_ = '';
foreach ( (array) $wp_registered_widgets[ $id ]['classname'] as $cn ) {
foreach ( (array) $widget_params['classname'] as $cn ) {
if ( is_string( $cn ) ) {
$classname_ .= '_' . $cn;
} elseif ( is_object( $cn ) ) {
$classname_ .= '_' . get_class( $cn );
}
}
$classname_ = ltrim( $classname_, '_' );
$params[0]['before_widget'] = sprintf( $params[0]['before_widget'], $id, $classname_ );
$params[0]['before_widget'] = sprintf( $params[0]['before_widget'], $widget_id, $classname_ );

$params = apply_filters( 'dynamic_sidebar_params', $params );
$callback = $wp_registered_widgets[ $id ]['callback'];
do_action( 'dynamic_sidebar', $wp_registered_widgets[ $id ] );
/** This filter is documented in wp-includes/widgets/widgets.php */
$params = apply_filters( 'dynamic_sidebar_params', $params );

if ( is_callable( $callback ) ) {
ob_start();
call_user_func_array( $callback, $params );
return ob_get_clean();
}
return false;
}

/**
* Renders the `core/legacy-widget` block on server.
*
* @see WP_Widget
*
* @param array $attributes The block attributes.
*
* @return string Returns the post content with the legacy widget added.
*/
function render_block_core_legacy_widget( $attributes ) {
$id = null;
$widget_class = null;
if ( isset( $attributes['widgetId'] ) ) {
$id = $attributes['widgetId'];
}
if ( isset( $attributes['widgetClass'] ) ) {
$widget_class = $attributes['widgetClass'];
}

if ( $id ) {
return block_core_legacy_widget_render_widget_by_id( $id );
}
if ( ! $widget_class ) {
return '';
}
/** This filter is documented in wp-includes/widgets/widgets.php */
do_action( 'dynamic_sidebar', $widget_params );

ob_start();
$instance = null;
if ( isset( $attributes['instance'] ) ) {
$instance = $attributes['instance'];
}
the_widget( $widget_class, $instance );
return ob_get_clean();
}
$widget_obj->_set( - 1 );
call_user_func_array( array( $widget_obj, 'widget' ), $params );

/**
* Register legacy widget block.
*/
function register_block_core_legacy_widget() {
register_block_type_from_metadata(
__DIR__ . '/legacy-widget',
array(
'render_callback' => 'render_block_core_legacy_widget',
)
);
return ob_get_clean();
}

add_action( 'init', 'register_block_core_legacy_widget' );
23 changes: 23 additions & 0 deletions packages/edit-widgets/src/store/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,29 @@ export const getWidgetIdForClientId = ( state, clientId ) => {
return clientIdToWidgetId[ clientId ];
};

/**
* Returns widgetArea containing a block identify by given clientId
*
* @param {string} clientId The ID of the block.
* @return {Object} Containing widget area.
*/
export const getWidgetAreaForClientId = createRegistrySelector(
( select ) => ( state, clientId ) => {
const widgetAreas = select( 'core/edit-widgets' ).getWidgetAreas();
for ( const widgetArea of widgetAreas ) {
const post = select( 'core' ).getEditedEntityRecord(
KIND,
POST_TYPE,
buildWidgetAreaPostId( widgetArea.id )
);
const clientIds = post.blocks.map( ( block ) => block.clientId );
if ( clientIds.includes( clientId ) ) {
return widgetArea;
}
}
}
);

export const getEditedWidgetAreas = createRegistrySelector(
( select ) => ( state, ids ) => {
let widgetAreas = select( 'core/edit-widgets' ).getWidgetAreas();
Expand Down