Writing a custom WordPress multi-instance widget

While working on a soon-to-be finished WordPress theme for a client, I ran into a situation where it would be useful to create a custom widget that they could use to organize content on their site. Basically, they wanted to be able to select a random post or page and display some associated meta data. Essentially a custom image and content teaser.

They were going to be doing this several times throughout the site, but in slightly different configurations. A post here, a page there. It seemed silly to hard-code these features. Using a widget would allow them to swap them out for a Twitter stream, or an RSS news feed in the future.

Making multi-instance widgets in WordPress 2.8+ couldn’t be easier. Here is a good example to start with from the WordPress codex.

  1. <?php
  2. class My_Widget extends WP_Widget {
  3. 	function My_Widget() {
  4. 		// widget actual processes
  5. 	}
  6.  
  7. 	function widget($args, $instance) {
  8. 		// outputs the content of the widget
  9. 	}
  10.  
  11. 	function update($new_instance, $old_instance) {
  12. 		// processes widget options to be saved
  13. 	}
  14.  
  15. 	function form($instance) {
  16. 		// outputs the options form on admin
  17. 	}
  18. }
  19. register_widget('My_Widget');
  20. ?>

And here is my finished widget (evolved from the above example).

  1. <?php
  2. /*
  3.  * Custom mini-post widget
  4.  */
  5. class FGX_MiniPost_Widget extends WP_Widget {
  6. 	function FGX_MiniPost_Widget() {
  7. 		// widget actual processes
  8. 		parent::WP_Widget(false, $name = 'Floragenex MiniPost', array(
  9. 			'description' => 'Display a teaser for a post or a page.'
  10. 		));
  11. 	}
  12.  
  13. 	function widget($args, $instance) {
  14. 		// outputs the content of the widget
  15. 			global $post;
  16.  
  17. 			extract( $args );
  18. 			$type = $instance['type'];
  19. 			$include = (!empty($instance['include']) ? explode(',', $instance['include']) : '');
  20. 			$category = (is_numeric($instance['category']) ? (int)$instance['category'] : '');
  21.  
  22.                         // Set up query for posts with the provided filters
  23. 			query_posts(array(
  24. 				'post_type' => $type,
  25. 				'post__in' => $include,
  26. 				'post__not_in' => array($post->ID),
  27. 				'cat' => $category,
  28. 				'post_status' => 'publish',
  29. 				'meta_key' => 'teaser_value',
  30. 				'meta_value' => '',
  31. 				'meta_compare' => '!=',
  32. 				'orderby' => 'rand',
  33. 				'posts_per_page' => '1'
  34. 			));
  35.  
  36. 			echo $before_widget;
  37.  
  38.                         // Output widget, if a post exists that matches our query
  39. 			if ( have_posts() ) :
  40. 				while ( have_posts() ) : the_post();
  41. 					$post_meta = get_post_custom($post->ID);
  42. 					echo (!empty($post_meta['image_value'][0]) ? '<a href="' . get_page_link() . '">' .
  43. 							 '<img alt="image" src="' . get_bloginfo('template_url') . '/scripts/timthumb.php?src=' . htmlentities($post_meta['image_value'][0]) . '&h=62&w=180&zc=1" />' .
  44. 							 '</a>' : '') .
  45. 							 '<h3><a href="' . get_page_link() . '">' . get_the_title() . '</a></h3>' .
  46. 							 '<p>' . htmlentities($post_meta['teaser_value'][0]) . '</p>' .
  47. 							 '<p><a class="learn-more" href="' . get_page_link() . '">learn more</a></p>';
  48. 				endwhile;
  49. 			else:
  50. 				echo '<p>No posts found.</p>';
  51. 			endif;
  52.  
  53.                         // Very important to reset the query here.
  54. 			wp_reset_query();
  55.  
  56. 			echo $after_widget;
  57. 	}
  58.  
  59. 	function update($new_instance, $old_instance) {
  60. 		// processes widget options to be saved
  61. 		return $new_instance;
  62. 	}
  63.  
  64. 	function form($instance) {
  65. 		// outputs the options form on admin
  66. 		$type = esc_attr($instance['type']);
  67. 		$include = esc_attr($instance['include']);
  68. 		$category = esc_attr($instance['category']);
  69.  
  70.                 // Get the existing categories and build a simple select dropdown for the user.
  71. 		$categories = get_categories();
  72.  
  73. 		$cat_options = array();
  74. 		$cat_options[] = '<option value="BLANK">Select one...</option>';
  75.  
  76. 		foreach ($categories as $cat) {
  77. 			$selected = $category === $cat->cat_ID ? ' selected="selected"' : '';
  78. 			$cat_options[] = '<option value="' . $cat->cat_ID .'"' . $selected . '>' . $cat->name . '</option>';
  79. 		}
  80.  
  81. 		?>
  82. 			<p>
  83. 				<label for="<?php echo $this->get_field_id('type'); ?>">
  84. 					<?php _e('Content type:'); ?>
  85. 				</label>
  86. 				<select id="<?php echo $this->get_field_id('type'); ?>" class="widefat" name="<?php echo $this->get_field_name('type'); ?>">
  87. 					<option value="post"<?php echo ($type === 'post' ? ' selected="selected"' : ''); ?>>Post</option>
  88. 					<option value="page"<?php echo ($type === 'page' ? ' selected="selected"' : ''); ?>>Page</option>
  89. 				</select>
  90. 			</p>
  91. 			<p>
  92. 				<label for="<?php echo $this->get_field_id('include'); ?>">
  93. 					<?php _e('Include post IDs (optional):'); ?>
  94. 				</label>
  95. 				<input id="<?php echo $this->get_field_id('include'); ?>" class="widefat" type="text" name="<?php echo $this->get_field_name('include'); ?>" value="<?php echo $include; ?>" />
  96. 			</p>
  97. 			<p>
  98. 				<label for="<?php echo $this->get_field_id('category'); ?>">
  99. 					<?php _e('Include category (optional):'); ?>
  100. 				</label>
  101. 				<select id="<?php echo $this->get_field_id('category'); ?>" class="widefat" name="<?php echo $this->get_field_name('category'); ?>">
  102. 					<?php echo implode('', $cat_options); ?>
  103. 				</select>
  104. 			</p>
  105. 		<?php
  106. 	}
  107. }
  108.  
  109. // register widget
  110. register_widget('FGX_MiniPost_Widget');
  111. ?>

Just paste this code in your theme’s functions.php file and the widget should appear under your available widgets.

This was a quick braindump post, so please feel free to post with any specific questions.

New skins – EMU Marketing

I decided it was time to freshen up the EMU Marketing blog a bit, so after a little hunting, I swapped out the old theme for the fantastic (and free) deStyle WordPress theme – from ThemeShift.

I was pleasantly surprised by the quality of the deStyle theme. It features a custom theme options page; integrated Twitter and Flickr modules; ad-support; author bios; and really nice looking markup.

The whole theme is just nicely polished.

ThemeShift has two other premium themes, deGusto and deCasa.

Woo! WordPress 2.8.

wordpress-logo-hoz-rgb

WordPress 2.8 is out! If you haven’t already you should go download it now.

As of this post WordPress 2.8 has been downloaded 1,522,482 times, that’s pretty amazing.

I’m actually blown away every time I install a new version of WordPress. It seems like they iron out a few more rough spots each release and the whole experience just gets that much better. As far as open source projects go, the WordPress team definitely has their shit together.

In other news. I’ve been doing some contract work for a client, part of which includes getting them moved from WordPress.com to a self-hosted WordPress blog.

After getting WordPress 2.8 up and running it was time to do some skinning. We decided that the Puretype Theme from Elegant Themes would be a good place to get started. After that, it only took a few hours to modify Puretype a bit to match the client’s website. It helps speed things up immensely when you have some pre-existing graphics to work with.

You can see the final result at blog.goodtugo.com.