Background:
The reason why filtering with dynamic_sidebar_params
doesn't work with HTML
is because WordPress strips HTML
from Widget Heading in wp_widget_control()
function like this:
$widget_title = esc_html( strip_tags( $sidebar_args['widget_name'] ) );
WordPress also strips HTML
in default JavaScript in wp-admin/js/widgets.js
So without a customised solution, there is no default filter or option either with PHP or with JavaScript to achieve what you want.
Custom PHP Solution:
A custom PHP solution is possible that'll only work in wp-admin -> Appearance -> Widgets
, but not in Customizer -> Widgets
.
WordPress adds wp_widget_control()
function (that generates Widget Control UI) with dynamic_sidebar_params
hook, so it's possible to override it using this filter hook. However, in customizer, WordPress calls wp_widget_control()
function directly, so this solution will not work for the customizer.
The solution works like the following (add this code in a custom plugin):
add_filter( 'dynamic_sidebar_params', 'wpse261042_list_widget_controls_dynamic_sidebar', 20 );
function wpse261042_list_widget_controls_dynamic_sidebar( $params ) {
global $wp_registered_widgets;
// we only want this in wp-admin (may need different check to enable page builder)
if( is_admin() ) {
if ( is_array( $params ) && is_array( $params[0] ) && $params[0]['id'] !== 'wp_inactive_widgets' ) {
$widget_id = $params[0]['widget_id'];
if ( $wp_registered_widgets[$widget_id]['callback'] === 'wp_widget_control' ) {
// here we are replacing wp_widget_control() function
// with our custom wpse261042_widget_control() function
$wp_registered_widgets[$widget_id]['callback'] = 'wpse261042_widget_control';
}
}
}
return $params;
}
function wpse261042_widget_control( $sidebar_args ) {
// here copy everything from the core wp_widget_control() function
// and change only the part related to heading as you need
}
As I said before, this solution doesn't work for the customizer and more likely to require future updates as we are overriding a core function.
Custom JavaScript Solution (Recommended):
Fortunately, it's possible to customize this behaviour with JavaScript. WordPress updates Widget Control Heading with JavaScript anyway. To do that WordPress keeps a placeholder in-widget-title
CSS class and updates it with the widget title
field value from JavaScript CODE. We can manipulate this to achieve our goal.
Related Core JS files:
First you need to know that WordPress loads wp-admin/js/customize-widgets.js
file (with customize-widgets
handle) in wp-admin -> Customize -> Widgets
(customizer) and wp-admin/js/widgets.js
file (with admin-widgets
handle) in wp-admin -> Appearance -> Widgets
to manipulate Widget Control UI. These two files do similar operations for Widget UI markups and Widget Heading UI manipulation, but a lot of different things as well. So we need to consider that for our JavaScript based solution.
Considerations for Customizer:
Customizer doesn't load Widget UI markup immediately after page load, instead, it loads with JavaScript when the corresponding Widgets -> Sidebar
panel is open. So we need to manipulate the Widget UI after WordPress loads it. For example, since customizer CODE is event based, I've used the following line of CODE to set the event handler at the correct moment:
section.expanded.bind( onExpanded );
Also, customizer used AJAX to load changes immediately, that's why I've used the following line to tap into the data change:
control.setting.bind( updateTitle );
Also, I needed to tap into widget-added
event with the following line of CODE:
$( document ).on( 'widget-added', add_widget );
Common for Customizer & wp-admin -> Appearance -> Widgets
:
Both the above mentioned JavaScript files trigger widget-updated
event when an widget is updated. Although customizer does it immediately with AJAX, while traditional Widget
admin does it after you Click the Save button. I've used the following line of CODE for this:
$( document ).on( 'widget-updated', modify_widget );
Considerations for wp-admin -> Appearance -> Widgets
:
Contrary to the customizer, traditional Widgets
admin loads the Widget Control UI with PHP, so I've traversed the UI HTML to make the initial changes like this:
$( '#widgets-right div.widgets-sortables div.widget' ).each( function() { // code } );
Custom Plugin CODE:
Following is a complete Plugin with JavaScript based solution that will work both in wp-admin -> Appearance -> Widgets
and Customizer -> Widgets
:
wpse-widget-control.php
Plugin PHP file:
<?php
/**
* Plugin Name: Widget Control
* Plugin URI: https://wordpress.stackexchange.com/questions/261042/how-to-influence-the-information-displayed-on-widget-inside-wp-admin
* Description: Display additional info on Widget Heading in wp-admin & customizer using JS
* Author: Fayaz
* Version: 1.0
* Author URI: http://fmy.me/
*/
if( is_admin() ) {
add_action( 'current_screen', 'wpse261042_widget_screen' );
}
function wpse261042_widget_screen() {
$currentScreen = get_current_screen();
if( $currentScreen->id === 'customize' ) {
add_action( 'customize_controls_enqueue_scripts', 'wpse261042_customizer_widgets', 99 );
}
else if( $currentScreen->id === 'widgets' ) {
add_action( 'admin_enqueue_scripts', 'wpse261042_admin_widgets', 99 );
}
}
function wpse261042_customizer_widgets() {
wp_enqueue_script( 'custom-widget-heading', plugin_dir_url( __FILE__ ) . 'custom-widget-heading.js', array( 'jquery', 'customize-widgets' ) );
}
function wpse261042_admin_widgets() {
wp_enqueue_script( 'custom-widget-heading', plugin_dir_url( __FILE__ ) . 'custom-widget-heading.js', array( 'jquery', 'admin-widgets' ) );
}
custom-widget-heading.js
JavaScript file:
(function( wp, $ ) {
var compare = {
// field to compare
field: 'title',
// value to be compared with
value: 'yes',
// heading if compare.value matches with compare.field value
heading: ' <i>(mobile/desktop)</i> ',
// alternate heading
alt_heading: ' <i>(desktop only)</i> ',
// WP adds <span class="in-widget-title"></span> in each widget heading by default
heading_selector: '.in-widget-title'
};
var widgetIdSelector = '> .widget-inside > form > .widget-id';
// heading is added as prefix of already existing heading, modify this as needed
function modify_heading( $elm, isMain ) {
var html = $elm.html();
if ( html.indexOf( compare.heading ) == -1 && html.indexOf( compare.alt_heading ) == -1 ) {
if( isMain ) {
$elm.html( compare.heading + html );
}
else {
$elm.html( compare.alt_heading + html );
}
}
};
function parseFieldSelector( widgetId ) {
return 'input[name="' + widgetIdToFieldPrefix( widgetId ) + '[' + compare.field + ']"]';
};
// @note: change this function if you don't want custom Heading change to appear for all the widgets.
// If field variable is empty, then that means that field doesn't exist for that widget.
// So use this logic if you don't want the custom heading manipulation if the field doesn't exist for a widget
function modify_widget( evt, $widget, content ) {
var field = null;
var field_value = '';
if( content ) {
field = $( content ).find( parseFieldSelector( $widget.find( widgetIdSelector ).val() ) );
}
else {
field = $widget.find( parseFieldSelector( $widget.find( widgetIdSelector ).val() ) );
}
if( field ) {
field_value = ( ( field.attr( 'type' ) != 'radio' && field.attr( 'type' ) != 'checkbox' )
|| field.is( ':checked' ) ) ? field.val() : '';
}
modify_heading( $widget.find( compare.heading_selector ), field_value == compare.value );
}
function parseWidgetId( widgetId ) {
var matches, parsed = {
number: null,
id_base: null
};
matches = widgetId.match( /^(.+)-(\d+)$/ );
if ( matches ) {
parsed.id_base = matches[1];
parsed.number = parseInt( matches[2], 10 );
} else {
parsed.id_base = widgetId;
}
return parsed;
}
function widgetIdToSettingId( widgetId ) {
var parsed = parseWidgetId( widgetId ), settingId;
settingId = 'widget_' + parsed.id_base;
if ( parsed.number ) {
settingId += '[' + parsed.number + ']';
}
return settingId;
}
function widgetIdToFieldPrefix( widgetId ) {
var parsed = parseWidgetId( widgetId ), settingId;
settingId = 'widget-' + parsed.id_base;
if ( parsed.number ) {
settingId += '[' + parsed.number + ']';
}
return settingId;
}
var api = wp.customize;
if( api ) {
// We ate in the customizer
widgetIdSelector = '> .widget-inside > .form > .widget-id';
api.bind( 'ready', function() {
function add_widget( evt, $widget ) {
var control;
control = api.control( widgetIdToSettingId( $widget.find( widgetIdSelector ).val() ) );
function updateTitle( evt ) {
modify_widget( null, $widget );
};
if ( control ) {
control.setting.bind( updateTitle );
}
updateTitle();
};
api.control.each( function ( control ) {
if( control.id && 0 === control.id.indexOf( 'widget_' ) ) {
api.section( control.section.get(), function( section ) {
function onExpanded( isExpanded ) {
if ( isExpanded ) {
section.expanded.unbind( onExpanded );
modify_widget( null, control.container.find( '.widget' ), control.params.widget_content );
}
};
if ( section.expanded() ) {
onExpanded( true );
} else {
section.expanded.bind( onExpanded );
}
} );
}
} );
$( document ).on( 'widget-added', add_widget );
} );
}
else {
// We are in wp-admin -> Appearance -> Widgets
// Use proper condition and CODE if you want to target any page builder
// that doesn't use WP Core Widget Markup or Core JavaScript
$( window ).on( 'load', function() {
$( '#widgets-right div.widgets-sortables div.widget' ).each( function() {
modify_widget( 'non-customizer', $( this ) );
} );
$( document ).on( 'widget-added', modify_widget );
} );
}
$( document ).on( 'widget-updated', modify_widget );
})( window.wp, jQuery );
Plugin Usage:
Note: With the above sample Plugin CODE as it is, if you set the title
of a Widget to yes
, then it'll show (mobile/desktop) within the Widget Control UI heading, all the other Widget's will have (desktop only) in the Heading. In the customizer the change will be immediate, in wp-admin -> widgets
the change will show after you save
the changes. Of course you can change this behaviour by modifying the CODE (in JavaScript) to do the heading change for a different field_name
or to only show that specific heading to some widgets and not to all of them.
For example, say you have a field named use_mobile
, and you want to set the heading to (mobile/desktop) when it's set to yes
. Then set the compare
variable to something like:
var compare = {
field: 'use_mobile',
value: 'yes',
heading: ' <i>(mobile/desktop)</i> ',
alt_heading: ' <i>(desktop only)</i> ',
heading_selector: '.in-widget-title'
};
Also, if you want to change the entire heading (instead of just .in-widget-title
), then you may change the heading_selector
setting along with the correct markup for heading
& alt_heading
to do so. The possibilities are endless, but make sure WordPress core CODE doesn't produce any error if you want to be too much creative with the resulting markup.
Page builder integration:
Whether or not either of these solutions will work for a page builder will depend on that page builder implementation. If it uses WordPress supplied methods to load Widget Control UI then it should work without any modification, otherwise similar (but modified) implication may be possible for page builders as well.