WordPress Plugin Bootcamp Part 4 – A Real Life Excercise

wpplugins

In the first three parts of this series you learned theory – how the WordPress plugin API works and how to use it. If you’re like me, however, you’ve been longing for a real life application – thinking, “That looks great, but how do I make the jump to using it in real life?”

Today, that’s exactly what you’re going to get. We’re going to delve in deep and tackle a real-life plugin (called WisdomDistilled) designed to allow easy placement and management of clever quotations. You’ll get to use the stuff you’ve learned – adding a widget and a settings page, saving options, and working with API hooks, plus learn some new techniques related to security, plugin structure, and working with the WordPress database. Are you ready?

Let’s Get Coding

As you may remember, the first step is to define the plugin, so we’ll start there. It’s also a standard practice to add license info (for whatever license you’re releasing under) in a comment right after the plugin definition. So, I started by creating a file called wisdom-distiller.php and typing this:

< ?php
/*
Plugin Name: Wisdom Distilled
Plugin URI: http://webitect.net/unknown-page/
Description: A plugin that allows you to create a collection of favorite quotes and then display them in a sidebar widget, at the end of your post, and more.
Version: 1.0
Author: Nick Parsons
Author URI: http://webitect.net/
*/

/*
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see .
*/
?>

Now we’re ready to start programming.

A Little Bit of OOPHP

Using some object-oriented techniques when writing a plugin can be very beneficial. Many successful plugin authors define their plugin as a class rather than a series of functions (prodedural), for 2 reasons:

  • It’s the ‘right’, organized way to do it
  • By doing that, we don’t have to worry about our function names conflicting with other plugins because they’re all private.

So, we’ll start by defining a class to hold the plugin and then creating an instance of it:

class WisdomDistilled {
}

$wisdom_distilled = new WisdomDistilled()

Now that’s ready, we can start writing methods to take care of the work, but first we should get a clear idea of what the plugin has to do. Here’s what I’m thinking:

  • It should provide an admin page that allows the administrator to add/edit/remove quotations
  • We’ll need to create a custom table to hold the quotes
  • We also need a widget to allow easy display of random quotes
  • For non-widgetized themes (or for custom placement), there should be a template tag which outputs the same content as the widget
  • Of course, once we write all the methods to accomplish this we’ll take advantage of a few hooks to get everything rolling the way it should.

We’ll start with that last one, and then write the functions.

Set Up the Hooks

This code is going to go down below the class definition, right after you’ve defined $wisdom_distilled as a new instance. The first hook we’ll set up is one that I haven’t introduced yet – register_activation_hook(). This is very useful for performing tasks that need to be done once when the plugin is activated, but not everytime the plugin is loaded. We’re going to use it to create a table to hold the quotes saved by the plugin:

if ( function_exists('register_activation_hook') ) {
   register_activation_hook(__FILE__, array($wisdom_distilled, 'create_quotes_table'));
}

 Notice that the first parameter it takes is the file to register the activation hook for (I used the PHP contstant __FILE__ to refer to this one). The second parameter is a callback function. The reason we used an array to describe the callback function is because WordPress invokes that function using call_user_func(), a built in PHP function, so we have to follow the rules of that function. You can read more about how that works it here. We’ll be referring to WisdomDistilled methods the same way throughout this hook section.

Next we need to add two actions -one to add a plugin settings page in the menu and another one to save any settings submitted from that page. You already know how this works:

if ( function_exists('add_action')){
  add_action('admin_menu', 'add_dw_options_page');
  function add_dw_options_page(){
  	add_menu_page('Wisdom Distilled Quote Manager', 'WisdomDistilled', 10, basename(__FILE__), array(new WisdomDistilled(),'display_manager'));
  }

  add_action('admin_init', array($wisdom_distilled, 'modify_quotes'));
}

And finally, we’ll register a sidebar widget and a matching control like you learned how to do in part 2: 

if ( function_exists('wp_register_sidebar_widget') ) {
  wp_register_sidebar_widget('wisdom_distilled', "Wisdom Distilled Quotes", array($wisdom_distilled, 'add_widget'), array('description'=>'This widget displays cycling quotes from the Wisdom Distilled plugin.'));
}

if ( function_exists('register_widget_control') ) {
  register_widget_control('wisdom_distilled', array($wisdom_distilled, 'add_widget_control'));
}

OK, everything is harnessed up, now we just need to write those functions that we specified. Before we do, though, take a moment to look at how I made sure to check every single WordPress function before I called it – just to be safe. It’s a good thing to keep in mind.

Function Time

So go back up inside the WisdomDistilled class – that’s where all the functions (methods) are going to be defined. The first function we’ll write is create_quotes_table(). In order to do that, you’ll have to learn about WordPress’ wpdb class. It is an incredibly powerful class that makes it very easy to interact with the WordPress database structure. It allows us to get the current table prefix, easily escape statements to prevent injection attacks, have lots of control over error reporting and in general have easy access to the database. We’ll use the query method (the most versatile one) to execute a standard MySQL insert statement. Notice how we define $wpdb as a global before we use it:

if ( function_exists('register_activation_hook') ) {
   register_activation_hook(__FILE__, array($wisdom_distilled, 'create_quotes_table'));
}

Nice, huh? Now it’s time to write modify_posts(), which is designed to take data sent from the plugin manager/settings page and save it. Again we globalize $wpdb and define the plugin’s table name. Then we check if the plugin settings form (which we haven’t wrote yet) has been submitted. There’s another condition to the if statement, though – check_admin_referer. This is another important security technique which works in conjunction with a hidden nonce field on the plugin settings page to make sure that the $_POST data is not be sent from somewhere else. It’s very important because without it you can start having CSRF problems. Here’s the code:

  function modify_quotes(){
  	global $wpdb;
	$table_name = $wpdb->prefix . "quotes";
	if (isset($_POST['wd_page_submitted']) && check_admin_referer('wd_options_page')){
		if (isset($_POST['new-quote']) && $_POST['new-quote']!='Type a New Quotation Here!'){
			$newSQL = $wpdb->prepare("INSERT INTO " . $table_name . "(quote) VALUES ('%s') ;",$_POST['new-quote']);
			$wpdb->query($newSQL);
		}
		foreach($_POST as $id => $quote){
			if(empty($quote)){
				$wpdb->query("DELETE FROM ".$table_name." WHERE id='".mysql_real_escape_string($id)."';");
			}
			else{
				$updateSQL = $wpdb->prepare("UPDATE ".$table_name." SET quote='%s' WHERE id = %d;",$quote,$id);
				$wpdb->query($updateSQL);
			}
		}
	}
  }

Next up is print_quote – the one that the widget calls and which can be implemented as a template tag. It’s simple enough- just use $wpdb->get_results, which is faster than $wpdb->query, and then grab a random quote out of the result set. If you’ve been studying your PHP this will make sense right away:

  function print_quote(){
	global $wpdb;
	$table_name = $wpdb->prefix . "quotes";
	$quotes = $wpdb->get_results("SELECT id, quote FROM ".$table_name);
	$random = rand(1,count($quotes)) -1;
	echo "

" .$quotes[$random]->quote . "

"; }

The next 2 functions are add_widget() and add_widget_control(). These will be familiar (from part 2), but as with modify_post(), we need to be sure to verify that the data isn’t being submitted from another site. That means using a wp_nonce_field() (which generates a hidden nonce field for us), and then we checking for that field with check_admin_referer() before saving. Both functions accept one parameter, a handle, so just make sure that the handle is the same in both places.

Another new trick you’ll see here is using add_option to set default options for the widget. It’s different from update_option() because it only executes if that option isn’t set yet, making it safe to use without overwriting the user’s saved options. Here’s how I wrote those functions:

  function add_widget($theme_info){
	$default_options = array(
		'title' => 'Wisdom Distilled',
		'class' => 'dw-quote'
	);
	add_option('wisdom_distilled_widget',$default_options);
	$set_options = get_option('wisdom_distilled_widget');

	echo $theme_info['before_widget'];
	echo $theme_info['before_title'] . $set_options['title'] . $theme_info['after_title'];
	echo "
" .$this->print_quote(). "
"; echo $theme_info['after_widget']; } function add_widget_control(){ if(isset($_POST['dw_title']) && check_admin_referer('dw_nonce')){ $new_options = array( 'title' => $_POST['dw_title'], 'class' => $_POST['dw_class'] ); update_option('wisdom_distilled_widget',$new_options); } $options = get_option('wisdom_distilled_widget'); echo "

"; echo "
"; wp_nonce_field('dw_nonce'); }

The very last and biggest function is display_manager, which outputs the data for the quotes manager/settings page. Most of it is HTML. At the top I’ve put a welcome/how-to-use kind of message, and then below I simply iterate through the quotes table and display the data. A neat technique you can learn from this one is using backend classes to style the HTML output and give a uniform appearance. Notice how I’ve used:

  • ‘widefat’, ‘post’ and ‘fixed’ classes to style the table and make it fit the appearance and layout
  • ‘column-title’ and ‘manage-column’ to style the table header and footer
  • ‘primary-button’ to style the submit button

And of course, you’ll see the wp_nonce_field that will be checked in update_posts()  when the plugin is run:

  function display_manager(){
	if(isset($_POST['wd_submitted'])){
		
	}
?>
	
">

How Does it Work?

Welcome to your "Wisdom Distilled" settings page! This plugin is designed to let you collect cool quotes and then easily display and cycle them on your blog. To use it, you can add the Wisdom Distilled widget to your sidebar or you can place <?php new WisdomDistilled->print_quote(); ?>. You can add, edit and remove quotes below. If you'd like to remove a quote, just erase it and the record will be deleted from the database.

Manage Quotes

< ?php global $wpdb; $table_name = $wpdb->prefix . "quotes"; $rows = $wpdb->get_results("SELECT id, quote FROM ".$table_name. ";"); foreach($rows as $row){ echo ""; } ?>
ID Quote
ID Quote
0
" .$row->id. "
< ?php wp_nonce_field('wd_options_page')?>

< ?php }

Wrapping Up

Good job!  I hope you've benefited and are nodding your head saying, "Now I see how it all fits together." Feel free to download the code  from this tutorial and play around with it - that's one of the best ways to really get familiar with coding concepts. And in case you wondered, there are two plugins called Stray Quotes and Quotes Collection which are similar to but more advanced than the plugin we coded today.

Do you have any more tips that others could benefit from, or any questions?

Written By Nick Parsons

Nick is the editor of Webitect and a developer + designer from Houston TX.

6 Comments

  1. Nick Parsons

    January 22nd, 2010 at 12:08 pm

    I should mention I do owe my readers an apology here – sorry this post is a day late! The schedule had to be modified a bit :)

  2. 12jewels

    February 22nd, 2010 at 02:43 pm

    I’m trying to follow your example for modifying the data. But your example only shows if you were only to have 1 field to update. How would you write it in both the html and the updateSQL if you was to have more than 1 field to update?

  3. 12jewels

    February 22nd, 2010 at 02:53 pm

    never mind I figured it out

  4. shakeer

    April 8th, 2011 at 02:20 am

    sadfsa

  5. shakeer

    April 8th, 2011 at 02:21 am

    sada

  6. Takanudo

    November 24th, 2011 at 11:36 am

    Nick,

    Thanks for the bootcamp. I was trying to download the wisdom-distilled.zip file, but the link appears to be bad.

What Do You Think?