mirror of
https://github.com/gosticks/wordpress-develop.git
synced 2026-08-12 04:40:26 +00:00
Customize: Add selective refresh framework with implementation for widgets and re-implementation for nav menus.
See https://make.wordpress.org/core/2016/02/16/selective-refresh-in-the-customizer/. Props westonruter, valendesigns, DrewAPicture, ocean90. Fixes #27355. git-svn-id: https://develop.svn.wordpress.org/trunk@36586 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
@@ -100,6 +100,10 @@ final class WP_Customize_Widgets {
|
||||
add_action( 'dynamic_sidebar', array( $this, 'tally_rendered_widgets' ) );
|
||||
add_filter( 'is_active_sidebar', array( $this, 'tally_sidebars_via_is_active_sidebar_calls' ), 10, 2 );
|
||||
add_filter( 'dynamic_sidebar_has_widgets', array( $this, 'tally_sidebars_via_dynamic_sidebar_calls' ), 10, 2 );
|
||||
|
||||
// Selective Refresh.
|
||||
add_filter( 'customize_dynamic_partial_args', array( $this, 'customize_dynamic_partial_args' ), 10, 2 );
|
||||
add_action( 'customize_preview_init', array( $this, 'selective_refresh_init' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -682,6 +686,7 @@ final class WP_Customize_Widgets {
|
||||
'widgetReorderNav' => $widget_reorder_nav_tpl,
|
||||
'moveWidgetArea' => $move_widget_area_tpl,
|
||||
),
|
||||
'selectiveRefresh' => isset( $this->manager->selective_refresh ),
|
||||
);
|
||||
|
||||
foreach ( $settings['registeredWidgets'] as &$registered_widget ) {
|
||||
@@ -762,7 +767,7 @@ final class WP_Customize_Widgets {
|
||||
$args = array(
|
||||
'type' => 'option',
|
||||
'capability' => 'edit_theme_options',
|
||||
'transport' => 'refresh',
|
||||
'transport' => isset( $this->manager->selective_refresh ) ? 'postMessage' : 'refresh',
|
||||
'default' => array(),
|
||||
);
|
||||
|
||||
@@ -884,7 +889,7 @@ final class WP_Customize_Widgets {
|
||||
'multi_number' => ( $args['_add'] === 'multi' ) ? $args['_multi_num'] : false,
|
||||
'is_disabled' => $is_disabled,
|
||||
'id_base' => $id_base,
|
||||
'transport' => 'refresh',
|
||||
'transport' => isset( $this->manager->selective_refresh ) ? 'postMessage' : 'refresh',
|
||||
'width' => $wp_registered_widget_controls[$widget['id']]['width'],
|
||||
'height' => $wp_registered_widget_controls[$widget['id']]['height'],
|
||||
'is_wide' => $this->is_wide_widget( $widget['id'] ),
|
||||
@@ -1061,8 +1066,9 @@ final class WP_Customize_Widgets {
|
||||
'registeredSidebars' => array_values( $wp_registered_sidebars ),
|
||||
'registeredWidgets' => $wp_registered_widgets,
|
||||
'l10n' => array(
|
||||
'widgetTooltip' => __( 'Shift-click to edit this widget.' ),
|
||||
'widgetTooltip' => __( 'Shift-click to edit this widget.' ),
|
||||
),
|
||||
'selectiveRefresh' => isset( $this->manager->selective_refresh ),
|
||||
);
|
||||
foreach ( $settings['registeredWidgets'] as &$registered_widget ) {
|
||||
unset( $registered_widget['callback'] ); // may not be JSON-serializeable
|
||||
@@ -1459,9 +1465,325 @@ final class WP_Customize_Widgets {
|
||||
wp_send_json_success( compact( 'form', 'instance' ) );
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* Option Update Capturing
|
||||
***************************************************************************/
|
||||
/*
|
||||
* Selective Refresh Methods
|
||||
*/
|
||||
|
||||
/**
|
||||
* Filter args for dynamic widget partials.
|
||||
*
|
||||
* @since 4.5.0
|
||||
*
|
||||
* @param array|false $partial_args Partial args.
|
||||
* @param string $partial_id Partial ID.
|
||||
* @return array Partial args
|
||||
*/
|
||||
public function customize_dynamic_partial_args( $partial_args, $partial_id ) {
|
||||
|
||||
if ( preg_match( '/^widget\[.+\]$/', $partial_id ) ) {
|
||||
if ( false === $partial_args ) {
|
||||
$partial_args = array();
|
||||
}
|
||||
$partial_args = array_merge(
|
||||
$partial_args,
|
||||
array(
|
||||
'type' => 'widget',
|
||||
'render_callback' => array( $this, 'render_widget_partial' ),
|
||||
'container_inclusive' => true,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $partial_args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add hooks for selective refresh.
|
||||
*
|
||||
* @since 4.5.0
|
||||
* @access public
|
||||
*/
|
||||
public function selective_refresh_init() {
|
||||
if ( ! isset( $this->manager->selective_refresh ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_action( 'wp_enqueue_scripts', array( $this, 'customize_preview_enqueue_deps' ) );
|
||||
add_filter( 'dynamic_sidebar_params', array( $this, 'filter_dynamic_sidebar_params' ) );
|
||||
add_filter( 'wp_kses_allowed_html', array( $this, 'filter_wp_kses_allowed_data_attributes' ) );
|
||||
add_action( 'dynamic_sidebar_before', array( $this, 'start_dynamic_sidebar' ) );
|
||||
add_action( 'dynamic_sidebar_after', array( $this, 'end_dynamic_sidebar' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue scripts for the Customizer preview.
|
||||
*
|
||||
* @since 4.5.0
|
||||
* @access public
|
||||
*/
|
||||
public function customize_preview_enqueue_deps() {
|
||||
if ( isset( $this->manager->selective_refresh ) ) {
|
||||
$script = wp_scripts()->registered['customize-preview-widgets'];
|
||||
$script->deps[] = 'customize-selective-refresh';
|
||||
}
|
||||
|
||||
wp_enqueue_script( 'customize-preview-widgets' );
|
||||
wp_enqueue_style( 'customize-preview' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Inject selective refresh data attributes into widget container elements.
|
||||
*
|
||||
* @param array $params {
|
||||
* Dynamic sidebar params.
|
||||
*
|
||||
* @type array $args Sidebar args.
|
||||
* @type array $widget_args Widget args.
|
||||
* }
|
||||
* @see WP_Customize_Nav_Menus_Partial_Refresh::filter_wp_nav_menu_args()
|
||||
*
|
||||
* @return array Params.
|
||||
*/
|
||||
public function filter_dynamic_sidebar_params( $params ) {
|
||||
$sidebar_args = array_merge(
|
||||
array(
|
||||
'before_widget' => '',
|
||||
'after_widget' => '',
|
||||
),
|
||||
$params[0]
|
||||
);
|
||||
|
||||
// Skip widgets not in a registered sidebar or ones which lack a proper wrapper element to attach the data-* attributes to.
|
||||
$matches = array();
|
||||
$is_valid = (
|
||||
isset( $sidebar_args['id'] )
|
||||
&&
|
||||
is_registered_sidebar( $sidebar_args['id'] )
|
||||
&&
|
||||
( isset( $this->current_dynamic_sidebar_id_stack[0] ) && $this->current_dynamic_sidebar_id_stack[0] === $sidebar_args['id'] )
|
||||
&&
|
||||
preg_match( '#^<(?P<tag_name>\w+)#', $sidebar_args['before_widget'], $matches )
|
||||
);
|
||||
if ( ! $is_valid ) {
|
||||
return $params;
|
||||
}
|
||||
$this->before_widget_tags_seen[ $matches['tag_name'] ] = true;
|
||||
|
||||
$context = array(
|
||||
'sidebar_id' => $sidebar_args['id'],
|
||||
);
|
||||
if ( isset( $this->context_sidebar_instance_number ) ) {
|
||||
$context['sidebar_instance_number'] = $this->context_sidebar_instance_number;
|
||||
} else if ( isset( $sidebar_args['id'] ) && isset( $this->sidebar_instance_count[ $sidebar_args['id'] ] ) ) {
|
||||
$context['sidebar_instance_number'] = $this->sidebar_instance_count[ $sidebar_args['id'] ];
|
||||
}
|
||||
|
||||
$attributes = sprintf( ' data-customize-partial-id="%s"', esc_attr( 'widget[' . $sidebar_args['widget_id'] . ']' ) );
|
||||
$attributes .= ' data-customize-partial-type="widget"';
|
||||
$attributes .= sprintf( ' data-customize-partial-placement-context="%s"', esc_attr( wp_json_encode( $context ) ) );
|
||||
$attributes .= sprintf( ' data-customize-widget-id="%s"', esc_attr( $sidebar_args['widget_id'] ) );
|
||||
$sidebar_args['before_widget'] = preg_replace( '#^(<\w+)#', '$1 ' . $attributes, $sidebar_args['before_widget'] );
|
||||
|
||||
$params[0] = $sidebar_args;
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* List of the tag names seen for before_widget strings.
|
||||
*
|
||||
* This is used in the filter_wp_kses_allowed_html filter to ensure that the
|
||||
* data-* attributes can be whitelisted.
|
||||
*
|
||||
* @since 4.5.0
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
protected $before_widget_tags_seen = array();
|
||||
|
||||
/**
|
||||
* Ensure that the HTML data-* attributes for selective refresh are allowed by kses.
|
||||
*
|
||||
* This is needed in case the $before_widget is run through wp_kses() when printed.
|
||||
*
|
||||
* @since 4.5.0
|
||||
* @access public
|
||||
*
|
||||
* @param array $allowed_html Allowed HTML.
|
||||
* @return array Allowed HTML.
|
||||
*/
|
||||
public function filter_wp_kses_allowed_data_attributes( $allowed_html ) {
|
||||
foreach ( array_keys( $this->before_widget_tags_seen ) as $tag_name ) {
|
||||
if ( ! isset( $allowed_html[ $tag_name ] ) ) {
|
||||
$allowed_html[ $tag_name ] = array();
|
||||
}
|
||||
$allowed_html[ $tag_name ] = array_merge(
|
||||
$allowed_html[ $tag_name ],
|
||||
array_fill_keys( array(
|
||||
'data-customize-partial-id',
|
||||
'data-customize-partial-type',
|
||||
'data-customize-partial-placement-context',
|
||||
'data-customize-partial-widget-id',
|
||||
'data-customize-partial-options',
|
||||
), true )
|
||||
);
|
||||
}
|
||||
return $allowed_html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep track of the number of times that dynamic_sidebar() was called for a given sidebar index.
|
||||
*
|
||||
* This helps facilitate the uncommon scenario where a single sidebar is rendered multiple times on a template.
|
||||
*
|
||||
* @since 4.5.0
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
protected $sidebar_instance_count = array();
|
||||
|
||||
/**
|
||||
* The current request's sidebar_instance_number context.
|
||||
*
|
||||
* @since 4.5.0
|
||||
* @access private
|
||||
* @var int
|
||||
*/
|
||||
protected $context_sidebar_instance_number;
|
||||
|
||||
/**
|
||||
* Current sidebar ID being rendered.
|
||||
*
|
||||
* @since 4.5.0
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
protected $current_dynamic_sidebar_id_stack = array();
|
||||
|
||||
/**
|
||||
* Start keeping track of the current sidebar being rendered.
|
||||
*
|
||||
* Insert marker before widgets are rendered in a dynamic sidebar.
|
||||
*
|
||||
* @since 4.5.0
|
||||
*
|
||||
* @param int|string $index Index, name, or ID of the dynamic sidebar.
|
||||
*/
|
||||
public function start_dynamic_sidebar( $index ) {
|
||||
array_unshift( $this->current_dynamic_sidebar_id_stack, $index );
|
||||
if ( ! isset( $this->sidebar_instance_count[ $index ] ) ) {
|
||||
$this->sidebar_instance_count[ $index ] = 0;
|
||||
}
|
||||
$this->sidebar_instance_count[ $index ] += 1;
|
||||
if ( ! $this->manager->selective_refresh->is_render_partials_request() ) {
|
||||
printf( "\n<!--dynamic_sidebar_before:%s:%d-->\n", esc_html( $index ), intval( $this->sidebar_instance_count[ $index ] ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finish keeping track of the current sidebar being rendered.
|
||||
*
|
||||
* Insert marker after widgets are rendered in a dynamic sidebar.
|
||||
*
|
||||
* @since 4.5.0
|
||||
*
|
||||
* @param int|string $index Index, name, or ID of the dynamic sidebar.
|
||||
*/
|
||||
public function end_dynamic_sidebar( $index ) {
|
||||
if ( ! $this->manager->selective_refresh->is_render_partials_request() ) {
|
||||
printf( "\n<!--dynamic_sidebar_after:%s:%d-->\n", esc_html( $index ), intval( $this->sidebar_instance_count[ $index ] ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Current sidebar being rendered.
|
||||
*
|
||||
* @since 4.5.0
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
protected $rendering_widget_id;
|
||||
|
||||
/**
|
||||
* Current widget being rendered.
|
||||
*
|
||||
* @since 4.5.0
|
||||
* @access private
|
||||
* @var string
|
||||
*/
|
||||
protected $rendering_sidebar_id;
|
||||
|
||||
/**
|
||||
* Filter sidebars_widgets to ensure the currently-rendered widget is the only widget in the current sidebar.
|
||||
*
|
||||
* @since 4.5.0
|
||||
* @access private
|
||||
*
|
||||
* @param array $sidebars_widgets Sidebars widgets.
|
||||
* @return array Sidebars widgets.
|
||||
*/
|
||||
public function filter_sidebars_widgets_for_rendering_widget( $sidebars_widgets ) {
|
||||
$sidebars_widgets[ $this->rendering_sidebar_id ] = array( $this->rendering_widget_id );
|
||||
return $sidebars_widgets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a specific widget using the supplied sidebar arguments.
|
||||
*
|
||||
* @since 4.5.0
|
||||
* @access public
|
||||
*
|
||||
* @see dynamic_sidebar()
|
||||
*
|
||||
* @param WP_Customize_Partial $partial Partial.
|
||||
* @param array $context {
|
||||
* Sidebar args supplied as container context.
|
||||
*
|
||||
* @type string $sidebar_id ID for sidebar for widget to render into.
|
||||
* @type int [$sidebar_instance_number] Disambiguating instance number.
|
||||
* }
|
||||
* @return string|false
|
||||
*/
|
||||
public function render_widget_partial( $partial, $context ) {
|
||||
$id_data = $partial->id_data();
|
||||
$widget_id = array_shift( $id_data['keys'] );
|
||||
|
||||
if ( ! is_array( $context )
|
||||
|| empty( $context['sidebar_id'] )
|
||||
|| ! is_registered_sidebar( $context['sidebar_id'] )
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->rendering_sidebar_id = $context['sidebar_id'];
|
||||
|
||||
if ( isset( $context['sidebar_instance_number'] ) ) {
|
||||
$this->context_sidebar_instance_number = intval( $context['sidebar_instance_number'] );
|
||||
}
|
||||
|
||||
// Filter sidebars_widgets so that only the queried widget is in the sidebar.
|
||||
$this->rendering_widget_id = $widget_id;
|
||||
|
||||
$filter_callback = array( $this, 'filter_sidebars_widgets_for_rendering_widget' );
|
||||
add_filter( 'sidebars_widgets', $filter_callback, 1000 );
|
||||
|
||||
// Render the widget.
|
||||
ob_start();
|
||||
dynamic_sidebar( $this->rendering_sidebar_id = $context['sidebar_id'] );
|
||||
$container = ob_get_clean();
|
||||
|
||||
// Reset variables for next partial render.
|
||||
remove_filter( 'sidebars_widgets', $filter_callback, 1000 );
|
||||
|
||||
$this->context_sidebar_instance_number = null;
|
||||
$this->rendering_sidebar_id = null;
|
||||
$this->rendering_widget_id = null;
|
||||
|
||||
return $container;
|
||||
}
|
||||
|
||||
//
|
||||
// Option Update Capturing
|
||||
//
|
||||
|
||||
/**
|
||||
* List of captured widget option updates.
|
||||
@@ -1611,7 +1933,7 @@ final class WP_Customize_Widgets {
|
||||
return;
|
||||
}
|
||||
|
||||
remove_filter( 'pre_update_option', array( $this, 'capture_filter_pre_update_option' ), 10, 3 );
|
||||
remove_filter( 'pre_update_option', array( $this, 'capture_filter_pre_update_option' ), 10 );
|
||||
|
||||
foreach ( array_keys( $this->_captured_options ) as $option_name ) {
|
||||
remove_filter( "pre_option_{$option_name}", array( $this, 'capture_filter_pre_get_option' ) );
|
||||
|
||||
Reference in New Issue
Block a user