Extending WordPress Core (or other 3rd-party) Widgets
How long have you been craving for a text size setting on the default Text widget, or a background color on the Calendar widget? Did you know you could implement these yourself? And without modifying the core files? And without jeopardizing the update-ability of your website?
Yup! It can be done! Of course, some things are more easily done, and some things may be impossible to do at all. Let’s see however what is the general approach of actually doing it.
Let’s start by creating a plugin that our code will live into. I will attempt to add a font size setting which will change the widget’s title size, so let’s name the plugin “Widgets Font Size”. Inside wp-admin/plugins/ create a new folder called widgets-font-size and in there create a new PHP file, widgets-font-size.php with the following contents:
Then, go ahead and activate the new plugin from your dashboard. It will do nothing, but everything great starts small!
If you don’t know what the above code does, then perhaps you need to do some reading before continuing with this article. The PHP documentation covers the basics of Object Oriented Programming, and the WordPress Codex walks you through on writing a plugin from scratch. We won’t be using any advanced OOP techniques or features however; we’ll only use a class to avoid prefixing all our functions. Yes, I’m lazy.
It’s otherwise a pretty standard plugin file. It includes a commented header with the necessary information that enables WordPress to recognize it as a plugin, and creates and instantiates the class Widgets_Font_Size. Once activated, we can simply add code inside the class and it will be executed on the next page refresh.
Default values
Our code will be manipulating a lot the widgets’ $instance, which isn’t an object instance, but an array of values of a widget’s instance. Instead of always checking whether a specific setting exists in the $instance variable, we can wp_parse_args() it with our defaults on the beginning of our functions, and then continue assuming the appropriate array indexes are there.
So, let’s declare a $defaults property (a variable that belongs to a class) right before the constructor. It will be an array with our setting’s name as the array key and the default value as the array value. Let’s say the setting will be named title_size, and the default value will be an empty string. Only widgets where title_size is set to a number will be affected by our code.
Adding a setting in the form
The Widgets API provides only one action related to the widgets’ form (the interface you see when configuring/customizing widgets), and that is ‘in_widget_form’. It fires after the widget’s form callback is finished, which really means we can only append stuff on the bottom of the widget’s form. Somewhere is better than nowhere. Let’s hook on this action:
The three parameters (also documented on the Developer Reference) are:
- $widget (renamed since $this is reserved) – The widget’s object instance, which is a subclass of WP_Widget. We use some of its methods to add our own form field.
- $return – The value returned by the widget’s form callback. It should be null when fields are added by the callback. We don’t need this currently.
- $instance – An array of the widget’s settings and their respective values. We use this array to read our own settings.
If you’ve ever built your own widget, you’ll find that the add_settings() method is very similar to the form() method that you need to implement when building the widget. In fact, we could have named our method form() as well.
Perhaps the most important line inside the add_settings() method is line 15. It takes care of assigning the default value on all $instance arrays, so that any widgets without the title_size setting (i.e. all previously assigned widgets) will not throw an “Undefined index” notice. Other than that, we just use the $widget‘s get_field_name() so that our input will be recognized as part of the widget, and get_field_id() so that the same setting in different widgets will have unique IDs and the labels will keep working properly.
Save the file and go to Appearance → Widgets.
We can see that the Recent Post widget now displays a Title Size setting. In fact, expand any widget and the setting will be there. How cool is that? “Not really cool” you’ll say. “I’ve just set a value, pressed Save, and the setting appears to be empty. You suck!”. Yes. We’re merely displaying the setting. Let’s also save its value!
Saving the setting
In order to save our setting, the Widget API provides us the ‘widget_update_callback‘ filter. Again, this is called after the plugin had a chance to process its own settings, but before they are saved. It’s almost identical to a widget’s update() method, although there are a few more parameters passed.
The four parameters are pretty much self-explanatory:
- $instance – The widget’s settings that are going to be saved. This is what is returned by the update() method, where $new_instance values have been sanitized.
- $new_instance – The widget’s settings, as submitted by its form.
- $old_instance – The previously saved settings that are going to be overwritten. This is set to an empty array when first assigning a widget to a sidebar.
- $widget – Like previously, renamed since $this is reserved. The widget’s object instance.
Inside the save_settings() method we, again, make sure our default is in place by running $instance through wp_parse_args(). This will ensure that our default will get saved. Now, lines 22-24 are the meat of this method, as we check that our setting is present in the $new_instance array, i.e. submitted from the widget’s form. If it’s there, and it has a value larger than zero, we replace our default in $instance with this value, converted to an integer. We then return $instance so it gets saved by the API behind the scenes.
Go ahead and refresh the Widgets page, assign a value and press Save. Voila! Our value persists.
But that’s not very useful right now, is it? Let’s see what we can do with the (now saved) setting.
Implementing the setting
Our setting needs to affect the font size of the widget’s title. Naturally, this should be done with CSS, and since we can’t know each widget’s settings before they actually get processed, we’ll need to employ the use of wp_add_inline_style(). You might find our “How to late enqueue inline CSS in WordPress” tutorial useful.
The Widgets API provides us with the ‘widget_display_callback‘ filter. Now, this fires before the widget’s display callback is called, so we have a chance to manipulate the $instance array as needed.
The available parameters are again very familiar:
- $instance – The widget’s settings that are going to be passed to the display callback.
- $widget – Like previously, renamed since $this is reserved. The widget’s object instance.
- $args – Kinda misleading by the WordPress’ own inline documentation, this array contains the sidebar‘s arguments passed to the widget. For example, the sidebar’s before_title, after_title, before_widget and after_widget arguments are included in this array.
On lines 3 and 33-36 we just registered a style without a source file, which will act as the dependency for our dynamic CSS. Line 6 simply hooks our frontend_settings() method to the widget_display_callback filter.
Now, inside frontend_settings() we first make sure our defaults are in place (see the pattern yet?) as the displayed widget may have never been re-saved since the time we activated our little plugin. Line 23 then does a quick check on the value of our setting. If it’s not falsey (i.e. empty string, false, 0, etc) it goes on to construct a CSS statement using the $args[‘widget_id’] as part of the selector, so that only the specific widget instance will be affected. Of course, $instance[‘title_size’] is used as part of a font-size rule in order to change the size.
Last, you might have noticed that wp_enqueue_style() is called right there with wp_add_inline_style(). This is needed as a) there’s no reason to enqueue it beforehand if we don’t have any inline style to add, and b) the dependency ‘wfs-front‘ needs to be printed along with the inline styles; that is, inline styles added before the wp_head action need to have their dependent style handle also enqueued before the wp_head action. Same thing goes for the wp_footer action. Since widgets are processed way after the wp_head action, we need to enqueue ‘wfs-front‘ later as well.
So, go ahead, set a title size to one of your widgets and visit your website. This is what I see on the blog of a local installation with our newest theme, Benson:
Hooray! It works!
But what if we only want this setting to apply on specific widgets? Or if we want it to apply on all widgets except some?
Targeting specific widgets
It’s actually very easy to make our setting appear, save, and work, only on specific widgets. All we really need to do is create a list of widget IDs (id_base actually) that we want to whitelist (or blacklist), and check against this list before we do anything inside our hooks. In order to determine the id_base of a widget, we need to inspect its source code. Luckily, WordPress and its ecosystem are open source, so we can access the source code easily. Let’s open the file wp-includes/widgets/class-wp-widget-recent-posts.php and inspect its constructor:
On line 7, the first argument of parent::__contruct() is ‘recent-posts’. This is the id_base of the widget. Each widget assigned gets a number suffixed, hence creating a unique widget ID, e.g. ‘recent-posts-2‘. We are only interested in the first part of the ID however.
Now that we have what we need, let’s create a property in our class that will hold the id_base of the widgets we want to whitelist.
Let’s also create a helper method, that will return true or false, depending if the specific widget is supported or not:
We’ve set the first parameter to be the widget’s object instanc, so that we can check its id_base property. We could expect the property passed to be the id_base itself instead, passing the whole object however, makes calling the method easier, and gives us the freedom to later modify is_supported() to check any of the object’s properties. If we wanted to blacklist the widgets instead, we would just negate ( ! ) the in_array() check on line 2.
Finally, we need to use our helper on each hook, returning early when the widget isn’t supported.
That should be it. Refresh your widgets page. The Title Size setting should disappear from all widgets except from Recent Posts. This is what my sidebar looks like:
Hooray! Everything works!
All together
Just in case you missed a step, this is the complete code that we ended up with:
There you have it. Did you find it easy? Did you find it useful? Let us know in the comments.
2 responses to “Extending WordPress Core (or other 3rd-party) Widgets”
Don’t forget to to use “before_widget” parameter like this while registering sidebars;
‘before_widget’ => ”,
or you will go nuts like me while searching why ID attributes not printed to make inline CSS work :)
The parameter was probably stripped from your comment, so I’m giving it a try as well:
Any theme worth using, either free or premium, should have something similar to the above.