In this article, I’m going to show you how to write a simple plugin that adds a new widget to WordPress. We’ll be using the new WP_Widget class, which is the newest method but means that the widget will only work in WordPress 2.8+. I know that 2.8 isn’t actually out yet, but it will be soon and there’s no sense in learning the old method.
The widget we’ll be creating will display upcoming posts (scheduled posts). A lot of sites schedule posts to automatically publish at a specific time, helping them keep a steady flow of articles. I know that I use this trick on Web Developer News and Attackr, and I’ll use it on this site as soon as I get some more articles written. Since the articles are already there and ready to be posted, why not tease them and give your readers something to look forward to? That’s exactly what this widget will do.
To start, you need to create a class for your widget that that extends WP_Widget. We’ll be using WP_Widget_Upcoming_Posts. Then you should hook into the widgets_init action and use register_widget() to register your new widget. The bare bones skeleton looks like this (and does nothing but throw a warning):
/** * Upcoming_Posts widget class */ class WP_Widget_Upcoming_Posts extends WP_Widget { // Our Widget methods will go here } function registerUpcomingPostsWidget() { register_widget('WP_Widget_Upcoming_Posts'); } add_action('widgets_init', 'registerUpcomingPostsWidget');
Now, there are a few methods that you need to create. The first needs to be named the same as the class (WP_Widget_Upcoming_Posts in this case), which will be used to instantiate the widget. Do not use the PHP5 __construct, as the WP_Widget class uses that and you end up with an infinite loop if you do. Second, you must create a widget method, which is used to actually manage and display your widget. In our case we’ll also be overloading the update method to handle updates to widget settings and the form method which is used to display the setting form on the widget admin page. I’m going to go through each method, explain the code, and then put it all together.
First, the WP_Widget_Upcoming_Posts method:
function WP_Widget_Upcoming_Posts() { $widget_ops = array('classname' => 'widget_upcoming_entries', 'description' => __( "List scheduled/upcoming posts", 'upcoming_posts_widget') ); $this->WP_Widget('upcoming-posts', __('Upcoming Posts', 'upcoming_posts_widget'), $widget_ops); }
First, we set up the options for the widget. We set the classname to widget_upcoming_posts. This will be added to the li tag of the widget when it’s displayed. We also set the description, which is displayed when the widget box is expanded in the available widgets section of the new widgets admin page. These options are passed to WP_Widget along with the id base (upcoming-posts) and the widget name (Upcoming Posts).
function widget($args, $instance) { extract($args); $title = empty($instance['title']) ? __('Upcoming Posts', 'upcoming_posts_widget') : apply_filters('widget_title', $instance['title']); if ( !$number = (int) $instance['number'] ) $number = 10; else if ( $number < 1 ) $number = 1; else if ( $number > 15 ) $number = 15; $queryArgs = array( 'showposts' => $number, 'what_to_show' => 'posts', 'nopaging' => 0, 'post_status' => 'future', 'caller_get_posts' => 1, 'order' => 'ASC' ); $r = new WP_Query($queryArgs); if ($r->have_posts()) : ?> <?php echo $before_widget; ?> <?php echo $before_title . $title . $after_title; ?> <ul> <?php while ($r->have_posts()) : $r->the_post(); ?> <li><?php if ( get_the_title() ) the_title(); else the_ID(); ?><?php edit_post_link('e',' (',')'); ?></li> <?php endwhile; ?> </ul> <?php echo $after_widget; ?> <?php endif; wp_reset_query(); // Restore global post data stomped by the_post(). }
The first thing we do is extract $args, which gives you $name, $id, $before_widget, $after_widget, $before_title, $after_title, $widget_id, and $widget_name. We’ll use those throughout the rest of the function. Next we set $title. If it was specified in the settings for the widget, then use use it and apply the widget_title filter to it. If a title wasn’t specified, we use “Upcoming Posts”.
Next, we deal with the number of posts to display. If nothing was specified in the widget settings, we set $number to 10. If the user did specify the number of posts to display, we do some sanity checks to make sure that the number is at least 0 and no more than 15. This isn’t absolutely necessary, but it’s nice to set some limits to things don’t get out of hand.
Now we need to prepare to actually query the database and retrieve the posts. In order to get the posts we want, we set the following:
- showposts
- This sets the number of posts to display.
- what_to_show
- This specifies what items to retrieve from the database. We want posts, not pages, attachments, revisions, etc.
- nopaging
- If the blog is set up to show fewer posts per page than we’re trying to get, paging would interfere. We turn it off just to be safe.
- post_status
- Since this plugin displays scheduled posts, we need to make sure that the post status is future, not published, draft, etc.
- caller_get_posts
- This is to make sure that sticky posts aren’t re-ordered to the top.
- order
- The order defaults to descending, which means it displays post with the highest (most futuristic) date first. We want to display the next post (oldest scheduled post) first, so we set this to ASC for ascending.
Now that all the options are set, we run the query. We want to make sure that all output is inside the have_posts loop, so that if no posts are returned the widget doesn’t display (rather than showing the title with no posts). In order to fit within all themes, you want to make sure to always echo your output in this order: $before_widget, $before_title, $title, $after_title, your widget content, $after_widget. A theme can customize each of those before and after items to create the proper markup for their design, so it’s important to use them. Our widget content is simply an unordered list of posts. The only little extra I added is the edit_post_link(), which will display a link to edit the post if you are logged in with sufficient permissions. The last thing we do is reset the query, so that our widget query doesn’t mess up other queries.
The next method we need to set up is the update method. Technically this isn’t actually required, but it gives us a chance to sanitize input.
function update( $new_instance, $old_instance ) { $instance = $old_instance; $instance['title'] = strip_tags($new_instance['title']); $instance['number'] = (int) $new_instance['number']; return $instance; }
As you can see, we strip tags from the title and make sure that number is and integer. You’ll notice that we take the old instance and update it with info from the new instance. This makes sure that no one tries to sneak in extra data by modifying the form on the widget admin page. It’s not necessary, but it’s a little more secure.
The last thing we need to do is set up the form method, which actually displays the settings on the widget admin page.
function form( $instance ) { $title = attribute_escape($instance['title']); if ( !$number = (int) $instance['number'] ) $number = 5; ?> <p><label for="<?php echo $this->get_field_id('title'); ?>"> <?php _e('Title:'); ?> <input class="widefat" id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" type="text" value="<?php echo $title; ?>" /></label></p> <p><label for="<?php echo $this->get_field_id('number'); ?>"> <?php _e('Number of posts to show:'); ?> <input id="<?php echo $this->get_field_id('number'); ?>" name="<?php echo $this->get_field_name('number'); ?>" type="text" value="<?php echo $number; ?>" /></label> <br /><small><?php _e('(at most 15)'); ?></small></p> <?php }
Since we will be putting the title into the value attribute of an input element, we want to make sure it’s safe to do so by using attribute_escape() first. Then we make sure that number is an integer and set it to 5 if it does not exist (this doesn’t actually set the widget settings to 5, it just displays a 5 in the form field rather than leaving it blank, giving the user and idea of what’s expected). Then we break out of PHP and actually display the form. The format I use is the default and will make your widget blend with the ones included with WordPress. It goes like this (with and without “hint text”):
<p> <label> Label Text <input> </label> </p> <p> <label> Label Text <input> </label> <br /> <small> Hint Text </small> </p>
That’s it! Your widget is done, it matches the existing widgets, and can be dropped into any widget-ready section of a WordPress 2.8+ blog! Download a slightly more advanced version of the Upcoming Posts Widget that includes caching, and try it out! you can also see the widget completed below:
/** * Upcoming_Posts widget class */ class WP_Widget_Upcoming_Posts extends WP_Widget { function WP_Widget_Upcoming_Posts() { $widget_ops = array('classname' => 'widget_upcoming_entries', 'description' => __( "List scheduled/upcoming posts", 'upcoming_posts_widget') ); $this->WP_Widget('upcoming-posts', __('Upcoming Posts', 'upcoming_posts_widget'), $widget_ops); } function widget($args, $instance) { extract($args); $title = empty($instance['title']) ? __('Upcoming Posts', 'upcoming_posts_widget') : apply_filters('widget_title', $instance['title']); if ( !$number = (int) $instance['number'] ) $number = 10; else if ( $number < 1 ) $number = 1; else if ( $number > 15 ) $number = 15; $queryArgs = array( 'showposts' => $number, 'what_to_show' => 'posts', 'nopaging' => 0, 'post_status' => 'future', 'caller_get_posts' => 1, 'order' => 'ASC' ); $r = new WP_Query($queryArgs); if ($r->have_posts()) : ?> <?php echo $before_widget; ?> <?php echo $before_title . $title . $after_title; ?> <ul> <?php while ($r->have_posts()) : $r->the_post(); ?> <li><?php if ( get_the_title() ) the_title(); else the_ID(); ?><?php edit_post_link('e',' (',')'); ?></li> <?php endwhile; ?> </ul> <?php echo $after_widget; ?> <?php endif; wp_reset_query(); // Restore global post data stomped by the_post(). } function update( $new_instance, $old_instance ) { $instance = $old_instance; $instance['title'] = strip_tags($new_instance['title']); $instance['number'] = (int) $new_instance['number']; return $instance; } function form( $instance ) { $title = attribute_escape($instance['title']); if ( !$number = (int) $instance['number'] ) $number = 5; ?> <p><label for="<?php echo $this->get_field_id('title'); ?>"> <?php _e('Title:'); ?> <input class="widefat" id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" type="text" value="<?php echo $title; ?>" /></label></p> <p><label for="<?php echo $this->get_field_id('number'); ?>"> <?php _e('Number of posts to show:'); ?> <input id="<?php echo $this->get_field_id('number'); ?>" name="<?php echo $this->get_field_name('number'); ?>" type="text" value="<?php echo $number; ?>" /></label> <br /><small><?php _e('(at most 15)'); ?></small></p> <?php } } function registerUpcomingPostsWidget() { register_widget('WP_Widget_Upcoming_Posts'); } add_action('widgets_init', 'registerUpcomingPostsWidget');