Advertisement

Dissecting the WordPress Text Widget

by

In this tutorial, we will take a close look under the hood of the WordPress text widget to uncover the process of creating a widget that can be added to multiple widget locations.

The Problem

If you know the basics of programming in PHP, creating a WordPress widget is not difficult at all. I have covered the basics in an earlier tutorial, The Anatomy of a WordPress Plugin -- it's a good idea to check that one out before reading any further, if you haven't built a widget before.

While that basic way of creating a widget is good enough for many plugins, it has one major drawback: the widget can be added to only one slot in a blog.

If you add the widget to one of the sidebars, the widget's "Add" button disappears and the widget cannot be added to any other slot before removing it from the first one.


Notice how the "Archives" widget cannot be added anymore.

In some cases, that's not a problem, because the nature of the widget is that it's shown only once. For example, it's quite OK to only list the blog archive in one place in the layout. But there are many cases where more than one instance can be very useful. One such example is the Text widget that comes bundled with WordPress.

The Text widget is a simple widget that lets you specify a header and a text, also allowing HTML, and renders its contents to the blog's sidebar. Because of this simplicity, the widget can be used for a wide range of purposes. For example, in the next image -- a screenshot from my personal blog -- you can see how I use the text widget in my sidebar first to show a welcome text, and then again to show some ads at the bottom of the sidebar. In WordPress documentation, widgets like this are called "multi-widgets."

But How Do You Create Your Own Multi-Widget?

Because WordPress is an open source project, we can dig into the source code and use it as a reference for our own projects. For this tutorial, it means that we can take a closer look at the Text widget and see how it has been implemented, using it as an example for turning our own widgets into multi-widgets.

Step 1: Locating the WordPress Text Widget

The first step is to search through the WordPress codebase and locate the text widget. It's an interesting step and I recommend you check out some other files here and there too to gain an overall glimpse of what goes on inside of the blogging platform.

But if you just can't wait to get to action, here's where you will find the widget code:


The widget code is located in wordpress/wp-includes/widgets.php

Open the file and scroll down to the very end of it, around line 1958 (in version 2.7.1), and there, hidden in the middle of the source code, you'll find an example widget, commented out and labeled "Pattern for multi-widget (allows multiple instances such as the text widget)."

At first, the example code can look rather daunting, so we'll go through it line by line, making sense of the ideas around which the pattern is built. The code snippets in this tutorial have been copied from the example pattern, but I have made a few small changes here and there to make the code easier to follow (mainly splitting long lines to shorter ones). Apart from those small adjustments, the source code is exactly the same as you'll find by opening widgets.php from the WordPress codebase.

Step 2: Understanding Widget Data

When the user presses the "Save Changes" button in the Widgets menu, WordPress collects the form parameters from each widget listed in the sidebars and sends them to each of the registered widgets. This feature is what makes creating multi-widgets possible: As the widget controller function gets notified to save a widget instance, all the parameters are passed to it, and it can update every instance of this widget type at one go, adding new ones and removing unused ones as needed.

We will look into the details of the saving process in a short while, but first, a good place to start looking at how the widget data for a multi-widget are stored is to look at the part of the code where the data are used. Here is the code for the method used for showing an instance of the example widget:

<?php
function widget_many( $args, $widget_args = 1 ) {
	extract( $args, EXTR_SKIP );
	if ( is_numeric($widget_args) )
		$widget_args = array( 'number' => $widget_args );
	$widget_args = wp_parse_args( $widget_args, array( 'number' => -1 ) );
	extract( $widget_args, EXTR_SKIP );

	// Data should be stored as array
	$options = get_option('widget_many');
	if ( !isset($options[$number]) )
		return;

	echo $before_widget;

	// Do stuff for this widget, drawing data from $options[$number]

	echo $after_widget;
}
?>
  1. The first parameter passed to the widget rendering function, $args, contains generic widget settings, such as what should be printed before and after the widget, and how the widget heading should be formatted. The code on line 3 is used to split that information into local variables, from which $before_widget and $after_widget are used in the example.

  2. The second parameter, $widget_args is more interesting to us: it contains the id of the widget instance to be rendered. Lines 4 to 7 are there to make sure the parameter comes in a correct format, and that in the end, after the extract call on line 7, we can find the widget index in the local variable $number.

  3. Just like widget settings for a simple, one-time-only widget, the settings for a widget instance are stored as an array containing key-value pairs for each setting. But as we now need to store multiple instances, instead of saving each array with its own call to update_option, we put them all in one array using the instance id ($number) as the index and then save the whole thing with the id of the widget type.

  4. In the above example, we have a widget named "widget_many", and let's say we have added three instances of it to our blog sidebar. When rendering one of them, we first get an array containing all of the widget_many instances by calling get_option('widget_many'); (line 10) and then find the data for the current instance from that array (line 16).

The example code from doesn't show the specifics of what you should do with the data, so let's look at the actual text widget for more insight:

<?php
function wp_widget_text($args, $widget_args = 1) {
	extract( $args, EXTR_SKIP );
	if ( is_numeric($widget_args) )
		$widget_args = array( 'number' => $widget_args );
	$widget_args = wp_parse_args( $widget_args, array( 'number' => -1 ) );
	extract( $widget_args, EXTR_SKIP );

	$options = get_option('widget_text');
	if ( !isset($options[$number]) )
		return;

	$title = apply_filters('widget_title', $options[$number]['title']);
	$text = apply_filters( 'widget_text', $options[$number]['text'] );
?>
		<?php echo $before_widget; ?>
			<?php
				if ( !empty( $title ) ) {
					echo $before_title . $title . $after_title;
				}
			?>
			<div class="textwidget"><?php echo $text; ?></div>
		<?php echo $after_widget; ?>
<?php
}
?>

On lines 13 and 14, you can see how the two parameters needed for the text widget are read from the options using $number as instance id. And on the next ten lines, the parameters are rendered on the screen.

Step 3: Saving Widget Settings

By analyzing the widget rendering function, we have now uncovered the basics behind saving the data for a multi-widget:

  1. Save the data of the specific widget instance to an array with setting names (for example "title") as keys
  2. Save all widget instances into an array using the instance ids as keys
  3. Save the whole thing to the database using WordPress's built-in get_option / update_option system.

Now, we'll look closer and see the tricks and caveats involved.

Using the same approach as before, we will now look into the function used for showing and handling the settings forms and go through it line by line. This function, widget_many_control, is the function that saves the settings and renders the settings form for the "widget_many" example widget. It is called once for each widget_many instance, always passing the instance id in $widget_args.

As the function is a part of the form submission handling, the $_POST array will contain all parameters that have been sent using the widget editing form.

<?php
function widget_many_control( $widget_args = 1 ) {
	global $wp_registered_widgets;
	static $updated = false;

	if ( is_numeric($widget_args) )
		$widget_args = array( 'number' => $widget_args );
	$widget_args = wp_parse_args( $widget_args, array( 'number' => -1 ) );
	extract( $widget_args, EXTR_SKIP );

On the first lines of the function, you'll see something familiar (lines 6-9). This is the same piece of code that was used in the rendering function to make sure $number has been initialized with the numerical id of the current widget instance.

But as you'll soon notice, we won't need that index for anything else than rendering the widget editing form, as we always save every instance of the "widget_many" widget type in one loop.

Next, we'll retrieve the current widget settings, creating them if they don't exist:

	$options = get_option('widget_many');
	if ( !is_array($options) )
		$options = array();

When implementing your own widget, remember to change the key, widget_many to your own widget's id. Apart from that, this code can be reused as is. On lines 2 and 3, the code takes care of the case when we are adding the first widget instance of this type by creating an empty options array to use.

If settings have already been saved at least for one instance, we get them from get_option and can go on updating the settings if a form was submitted:

		if ( !$updated && !empty($_POST['sidebar']) ) {

The above line has two functions: It checks if data was posted ("!empty($_POST['sidebar'])") and makes sure that instances of this widget type are handled only once.

The widget system calls the widget handling function once per each widget instance, but because we need to take care of things like adding and removing new widget instances, we can't do that without knowing all the widgets of this type that exist. This is why the approach is to update every widget instance of this type on the first occurrence, and then set the global variable $updated to true so that the next instance will not do the updating again.

// Tells us what sidebar to put the data in
$sidebar = (string) $_POST['sidebar'];

$sidebars_widgets = wp_get_sidebars_widgets();
if ( isset($sidebars_widgets[$sidebar]) )
	$this_sidebar =& $sidebars_widgets[$sidebar];
else
	$this_sidebar = array();

foreach ( $this_sidebar as $_widget_id ) {
	$widget = $wp_registered_widgets[$_widget_id];
	if ( 'widget_many' == $widget['callback']
		&& isset($widget['params'][0]['number']) ) {
			$widget_number = $widget['params'][0]['number'];

			if ( !in_array( "many-$widget_number", $_POST['widget-id'] ) )
				unset($options[$widget_number]);
	}
}

// Compile data from $widget_many_instance
$widget_data = (array) $_POST['widget-many'];
foreach ( $widget_data as $widget_number => $widget_many_instance ) {
	if ( !isset($widget_many_instance['something'])
		&& isset($options[$widget_number]) ) {
			// user clicked cancel
			continue;
	}
	$something = wp_specialchars( $widget_many_instance['something'] );
	$options[$widget_number] = array( 'something' => $something );
}

update_option('widget_many', $options);

$updated = true; // So that we don't go through this more than once

This code snippet first looks up all the widgets stored in the sidebar that is being updated (lines 2-8) and then goes through the list removing all that match the widget id of this widget type (lines 10-18).

$_POST("sidebar") (line 2) contains the id of the sidebar. This way, if the user has removed a widget, it will be deleted from the data as well. And by deleting everything, we make sure we don't accidentally leave duplicates, for example if the id of some widget has changed between updates.

After removing everything, we start adding the widgets that were posted by the user. In the posted form, there is an array containing all the data for every instance of the widget type. The array is organized like this:

	['widget-many'] =>
		[0] => params for widget instance 0 (array),
		[1] => params for widget instance 1 (array),
		...

On lines 23-31, the code loops over the widget instance data submitted and creates the settings for each instance (lines 29 and 30). As this is just example code, the saving part is using placeholder data such as "something". So, let's again take a look at the text widget to see how this works with real data:

$title = strip_tags(stripslashes($widget_text['title']));
if ( current_user_can('unfiltered_html') )
	$text = stripslashes( $widget_text['text'] );
else
	$text = stripslashes(wp_filter_post_kses( $widget_text['text'] ));
$options[$widget_number] = compact( 'title', 'text' );

The text widget has two parameters: title and text. The $widget_text variable in this piece of code is used the same way as $widget_many_instance in the example code we have been following: it holds the data posted for a specific instance of the widget.

In the text widget example, you'll also see some interesting security features that you may want to look into a bit more when developing your own widgets. For this tutorial, however, it is enough to see that the contents of the variables $title and $text are read from the array posted and then stored as an array to the widget's options on line 6.

If you are wondering about the compact() function, it's the opposite of extract(), and takes the local variables whose names were passed as parameters and turns them into an array with the names as keys.

Finally, the new $options array is stored as the data for the widget, widget_many in the example code or widget_text in the text widget, using the update_option() function.

The last thing remaining in the widget settings function is drawing the form for this widget instance. This form is not rendered on screen as is, but WordPress converts it into a real form later, using some JavaScript magic. The code below is from the template code, so it uses $something to represent the widget data. In the text widget it is replaced by $title and $text, and in your own widget with anything you need to save.

It's important to notice that although the saving is done for every instance at one go, the form rendered here is for just one instance. This is where we'll use the widget instance id read in at the beginning of the function.

	if ( -1 == $number ) {
		$something = '';
		$number = '%i%';
	} else {
		$something = attribute_escape($options[$number]['something']);
	}

On the above lines, the widget first checks if a valid widget number was given or not. If not, and $number was set to the default value, -1, all data should be set to default values. Otherwise, we will get the data from the $options array and use it to populate the form with current settings. This is a nice thing to do so that the user won't have to always start editing the widget from an empty slate.

And then, the form itself is built so that it can be read into an array when posted to the widget handling code: The form inputs are named following the pattern widget-many[$number][something] where $number is the number of this widget instance and something is the name of the parameter to be saved. WordPress then parses this into the array structure described earlier when the user submits the form.

The form doesn't have a submit button because all the widgets are saved using the same submit button provided by WordPress.

<p>
	<input class="widefat"
		id="widget-many-something-<?php echo $number; ?>"
		name="widget-many[<?php echo $number; ?>][something]"
		type="text" value="<?php echo $data; ?>" />
	<input type="hidden"
		id="widget-many-submit-<?php echo $number; ?>"
		name="widget-many[<?php echo $number; ?>][submit]" value="1" />
</p>

Step 4: Registering the Widget as Multi-Widget

For WordPress to show a widget in the widget list, you need to register it by telling WordPress what functions to use for rendering the widget and its settings form. The way this is done for multi-widgets isn't that different from regular widgets. The only difference is in how the id of the widget is defined: In $control_ops (line 7), we tell WordPress to enable multiple instances and to attach all widget instances with an id starting "many" to this widget type.

function widget_many_register() {
	if ( !$options = get_option('widget_many') )
		$options = array();

	$widget_ops = array('classname' => 'widget_many', 
                'description' => __('Widget which allows multiple instances'));
	$control_ops = array('width' => 400, 'height' => 350, 'id_base' => 'many');
	$name = __('Many');

	$registered = false;
	foreach ( array_keys($options) as $o ) {
		// Old widgets can have null values for some reason
		if ( !isset($options[$o]['something']) ) 
			continue;

		// $id should look like {$id_base}-{$o}
		$id = "many-$o"; // Never never never translate an id
		$registered = true;
		wp_register_sidebar_widget( $id, $name, 'widget_many', 
                        $widget_ops, array( 'number' => $o ) );
		wp_register_widget_control( $id, $name, 'widget_many_control', 
                        $control_ops, array( 'number' => $o ) );
	}

	// If there are none, we register the widget's 
    // existance with a generic template
	if ( !$registered ) {
		wp_register_sidebar_widget( 'many-1', $name, 'widget_many', 
                $widget_ops, array( 'number' => -1 ) );
		wp_register_widget_control( 'many-1', $name, 'widget_many_control', 
                $control_ops, array( 'number' => -1 ) );
	}
}

// This is important
add_action( 'widgets_init', 'widget_many_register' );

In the function above, we first register all the existing widgets of this type, skipping the ones that don't have valid data (lines 11-23), and then if there is no instance available yet, register a default one to make sure the widget is registered and available in the sidebar (lines 27-32).

As the last, crucial step, on line 34, we hook the widget initialization function to the widgets_init event so that WordPress will call it when it is the time to initialize all widgets. Without this function, we wouldn't see any of the functionality of the widget we just analyzed as no part of the code would ever get called.

Step 5: Putting it All Together

In the snippets above, I have deliberately left out some of the code to make things more readable, so you won't be able to just copy the pieces and put them together. Instead, you should go and copy that same code from your WordPress installation.

After that, you just need to put in your own functionality, and there you have it: your own multi-widget.


Advertisement