: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* @see WP_Customize_Setting
* @param array $args Array of Customizer setting arguments.
* @param string $id Widget setting ID.
return apply_filters( 'widget_customizer_setting_args', $args, $id );
* Ensures sidebar widget arrays only ever contain widget IDS.
* Used as the 'sanitize_callback' for each $sidebars_widgets setting.
* @param string[] $widget_ids Array of widget IDs.
* @return string[] Array of sanitized widget IDs.
public function sanitize_sidebar_widgets( $widget_ids ) {
$widget_ids = array_map( 'strval', (array) $widget_ids );
$sanitized_widget_ids = array();
foreach ( $widget_ids as $widget_id ) {
$sanitized_widget_ids[] = preg_replace( '/[^a-z0-9_\-]/', '', $widget_id );
return $sanitized_widget_ids;
* Builds up an index of all available widgets for use in Backbone models.
* @global array $wp_registered_widgets
* @global array $wp_registered_widget_controls
* @return array List of available widgets.
public function get_available_widgets() {
static $available_widgets = array();
if ( ! empty( $available_widgets ) ) {
return $available_widgets;
global $wp_registered_widgets, $wp_registered_widget_controls;
require_once ABSPATH . 'wp-admin/includes/widgets.php'; // For next_widget_id_number().
$sort = $wp_registered_widgets;
usort( $sort, array( $this, '_sort_name_callback' ) );
foreach ( $sort as $widget ) {
if ( in_array( $widget['callback'], $done, true ) ) { // We already showed this multi-widget.
$sidebar = is_active_widget( $widget['callback'], $widget['id'], false, false );
$done[] = $widget['callback'];
if ( ! isset( $widget['params'][0] ) ) {
$widget['params'][0] = array();
$available_widget = $widget;
unset( $available_widget['callback'] ); // Not serializable to JSON.
'widget_id' => $widget['id'],
'widget_name' => $widget['name'],
'_display' => 'template',
$is_multi_widget = ( isset( $wp_registered_widget_controls[ $widget['id'] ]['id_base'] ) && isset( $widget['params'][0]['number'] ) );
if ( $is_multi_widget ) {
$id_base = $wp_registered_widget_controls[ $widget['id'] ]['id_base'];
$args['_temp_id'] = "$id_base-__i__";
$args['_multi_num'] = next_widget_id_number( $id_base );
$args['_add'] = 'single';
if ( $sidebar && 'wp_inactive_widgets' !== $sidebar ) {
$id_base = $widget['id'];
$list_widget_controls_args = wp_list_widget_controls_dynamic_sidebar(
1 => $widget['params'][0],
$control_tpl = $this->get_widget_control( $list_widget_controls_args );
// The properties here are mapped to the Backbone Widget model.
$available_widget = array_merge(
'temp_id' => isset( $args['_temp_id'] ) ? $args['_temp_id'] : null,
'is_multi' => $is_multi_widget,
'control_tpl' => $control_tpl,
'multi_number' => ( 'multi' === $args['_add'] ) ? $args['_multi_num'] : false,
'is_disabled' => $is_disabled,
'transport' => $this->is_widget_selective_refreshable( $id_base ) ? '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'] ),
$available_widgets[] = $available_widget;
return $available_widgets;
* Naturally orders available widgets by name.
* @param array $widget_a The first widget to compare.
* @param array $widget_b The second widget to compare.
* @return int Reorder position for the current widget comparison.
protected function _sort_name_callback( $widget_a, $widget_b ) {
return strnatcasecmp( $widget_a['name'], $widget_b['name'] );
* Retrieves the widget control markup.
* @param array $args Widget control arguments.
* @return string Widget control form HTML markup.
public function get_widget_control( $args ) {
$args[0]['before_form'] = '<div class="form">';
$args[0]['after_form'] = '</div><!-- .form -->';
$args[0]['before_widget_content'] = '<div class="widget-content">';
$args[0]['after_widget_content'] = '</div><!-- .widget-content -->';
wp_widget_control( ...$args );
$control_tpl = ob_get_clean();
* Retrieves the widget control markup parts.
* @param array $args Widget control arguments.
* @type string $control Markup for widget control wrapping form.
* @type string $content The contents of the widget form itself.
public function get_widget_control_parts( $args ) {
$args[0]['before_widget_content'] = '<div class="widget-content">';
$args[0]['after_widget_content'] = '</div><!-- .widget-content -->';
$control_markup = $this->get_widget_control( $args );
$content_start_pos = strpos( $control_markup, $args[0]['before_widget_content'] );
$content_end_pos = strrpos( $control_markup, $args[0]['after_widget_content'] );
$control = substr( $control_markup, 0, $content_start_pos + strlen( $args[0]['before_widget_content'] ) );
$control .= substr( $control_markup, $content_end_pos );
$content_start_pos + strlen( $args[0]['before_widget_content'] ),
$content_end_pos - $content_start_pos - strlen( $args[0]['before_widget_content'] )
return compact( 'control', 'content' );
* Adds hooks for the Customizer preview.
public function customize_preview_init() {
add_action( 'wp_enqueue_scripts', array( $this, 'customize_preview_enqueue' ) );
add_action( 'wp_print_styles', array( $this, 'print_preview_css' ), 1 );
add_action( 'wp_footer', array( $this, 'export_preview_data' ), 20 );
* Refreshes the nonce for widget updates.
* @param array $nonces Array of nonces.
* @return array Array of nonces.
public function refresh_nonces( $nonces ) {
$nonces['update-widget'] = wp_create_nonce( 'update-widget' );
* Tells the script loader to load the scripts and styles of custom blocks
* if the widgets block editor is enabled.
* @param bool $is_block_editor_screen Current decision about loading block assets.
* @return bool Filtered decision about loading block assets.
public function should_load_block_editor_scripts_and_styles( $is_block_editor_screen ) {
if ( wp_use_widgets_block_editor() ) {
return $is_block_editor_screen;
* When previewing, ensures the proper previewing widgets are used.
* Because wp_get_sidebars_widgets() gets called early at {@see 'init' } (via
* wp_convert_widget_settings()) and can set global variable `$_wp_sidebars_widgets`
* to the value of `get_option( 'sidebars_widgets' )` before the Customizer preview
* filter is added, it has to be reset after the filter has been added.
* @param array $sidebars_widgets List of widgets for the current sidebar.
public function preview_sidebars_widgets( $sidebars_widgets ) {
$sidebars_widgets = get_option( 'sidebars_widgets', array() );
unset( $sidebars_widgets['array_version'] );
return $sidebars_widgets;
* Enqueues scripts for the Customizer preview.
public function customize_preview_enqueue() {
wp_enqueue_script( 'customize-preview-widgets' );
* Inserts default style for highlighted widget at early point so theme
* stylesheet can override.
public function print_preview_css() {
.widget-customizer-highlighted-widget {
-webkit-box-shadow: 0 0 2px rgba(30, 140, 190, 0.8);
box-shadow: 0 0 2px rgba(30, 140, 190, 0.8);
* Communicates the sidebars that appeared on the page at the very end of the page,
* and at the very end of the wp_footer,
* @global array $wp_registered_sidebars
* @global array $wp_registered_widgets
public function export_preview_data() {
global $wp_registered_sidebars, $wp_registered_widgets;
$switched_locale = switch_to_user_locale( get_current_user_id() );
'widgetTooltip' => __( 'Shift-click to edit this widget.' ),
if ( $switched_locale ) {
restore_previous_locale();
$rendered_sidebars = array_filter( $this->rendered_sidebars );
$rendered_widgets = array_filter( $this->rendered_widgets );
// Prepare Customizer settings to pass to JavaScript.
'renderedSidebars' => array_fill_keys( array_keys( $rendered_sidebars ), true ),
'renderedWidgets' => array_fill_keys( array_keys( $rendered_widgets ), true ),
'registeredSidebars' => array_values( $wp_registered_sidebars ),
'registeredWidgets' => $wp_registered_widgets,
'selectiveRefreshableWidgets' => $this->get_selective_refreshable_widgets(),
foreach ( $settings['registeredWidgets'] as &$registered_widget ) {
unset( $registered_widget['callback'] ); // May not be JSON-serializable.
wp_print_inline_script_tag(
sprintf( 'var _wpWidgetCustomizerPreviewSettings = %s;', wp_json_encode( $settings ) )
* Tracks the widgets that were rendered.
* @param array $widget Rendered widget to tally.
public function tally_rendered_widgets( $widget ) {
$this->rendered_widgets[ $widget['id'] ] = true;
* Determine if a widget is rendered on the page.
* @param string $widget_id Widget ID to check.
* @return bool Whether the widget is rendered.
public function is_widget_rendered( $widget_id ) {
return ! empty( $this->rendered_widgets[ $widget_id ] );
* Determines if a sidebar is rendered on the page.
* @param string $sidebar_id Sidebar ID to check.
* @return bool Whether the sidebar is rendered.
public function is_sidebar_rendered( $sidebar_id ) {
return ! empty( $this->rendered_sidebars[ $sidebar_id ] );
* Tallies the sidebars rendered via is_active_sidebar().
* Keep track of the times that is_active_sidebar() is called in the template,
* and assume that this means that the sidebar would be rendered on the template
* if there were widgets populating it.
* @param bool $is_active Whether the sidebar is active.
* @param string $sidebar_id Sidebar ID.
* @return bool Whether the sidebar is active.
public function tally_sidebars_via_is_active_sidebar_calls( $is_active, $sidebar_id ) {
if ( is_registered_sidebar( $sidebar_id ) ) {
$this->rendered_sidebars[ $sidebar_id ] = true;
* We may need to force this to true, and also force-true the value
* for 'dynamic_sidebar_has_widgets' if we want to ensure that there
* is an area to drop widgets into, if the sidebar is empty.
* Tallies the sidebars rendered via dynamic_sidebar().
* Keep track of the times that dynamic_sidebar() is called in the template,
* and assume this means the sidebar would be rendered on the template if
* there were widgets populating it.
* @param bool $has_widgets Whether the current sidebar has widgets.
* @param string $sidebar_id Sidebar ID.
* @return bool Whether the current sidebar has widgets.
public function tally_sidebars_via_dynamic_sidebar_calls( $has_widgets, $sidebar_id ) {
if ( is_registered_sidebar( $sidebar_id ) ) {
$this->rendered_sidebars[ $sidebar_id ] = true;
* We may need to force this to true, and also force-true the value
* for 'is_active_sidebar' if we want to ensure there is an area to
* drop widgets into, if the sidebar is empty.
* Retrieves MAC for a serialized widget instance string.
* Allows values posted back from JS to be rejected if any tampering of the
* @param string $serialized_instance Widget instance.
* @return string MAC for serialized widget instance.
protected function get_instance_hash_key( $serialized_instance ) {
return wp_hash( $serialized_instance );
* Sanitizes a widget instance.
* Unserialize the JS-instance for storing in the options. It's important that this filter
* only get applied to an instance *once*.
* @since 5.8.0 Added the `$id_base` parameter.
* @global WP_Widget_Factory $wp_widget_factory
* @param array $value Widget instance to sanitize.
* @param string $id_base Optional. Base of the ID of the widget being sanitized. Default null.
* @return array|void Sanitized widget instance.
public function sanitize_widget_instance( $value, $id_base = null ) {
global $wp_widget_factory;
if ( array() === $value ) {
if ( isset( $value['raw_instance'] ) && $id_base && wp_use_widgets_block_editor() ) {
$widget_object = $wp_widget_factory->get_widget_object( $id_base );
if ( ! empty( $widget_object->widget_options['show_instance_in_rest'] ) ) {
if ( 'block' === $id_base && ! current_user_can( 'unfiltered_html' ) ) {
* The content of the 'block' widget is not filtered on the fly while editing.
* Filter the content here to prevent vulnerabilities.
$value['raw_instance']['content'] = wp_kses_post( $value['raw_instance']['content'] );
return $value['raw_instance'];
empty( $value['is_widget_customizer_js_value'] ) ||
empty( $value['instance_hash_key'] ) ||
empty( $value['encoded_serialized_instance'] )
$decoded = base64_decode( $value['encoded_serialized_instance'], true );
if ( false === $decoded ) {
if ( ! hash_equals( $this->get_instance_hash_key( $decoded ), $value['instance_hash_key'] ) ) {
$instance = unserialize( $decoded );
if ( false === $instance ) {
* Converts a widget instance into JSON-representable format.
* @since 5.8.0 Added the `$id_base` parameter.
* @global WP_Widget_Factory $wp_widget_factory
* @param array $value Widget instance to convert to JSON.
* @param string $id_base Optional. Base of the ID of the widget being sanitized. Default null.
* @return array JSON-converted widget instance.
public function sanitize_widget_js_instance( $value, $id_base = null ) {
global $wp_widget_factory;
if ( empty( $value['is_widget_customizer_js_value'] ) ) {
$serialized = serialize( $value );