Tutorial On WordPress Plugin Programming

This is a tutorial I’ve been developing for a presentation I will give in December. The sample plugin described here is available for download here: demo.zip. About 90% of what I’ve learned about WordPress plugin development comes from WordPress Plugin Development – Beginner’s Guide by Vladimir Prelovac. The other 90% has come from trial and error, searching the WordPress documentation, and seeking out other code examples around the web.

  • Part 1
    • Introduction, API Basics, Custom Admin Menus
  • Part 2
    • Handling form posts, setting and retreiving options, database queries and updates
  • Part 3
    • Shortcodes, contact form example, adding JavaScript

The sample plugin described below is loaded on this site, for purposes of example: webliberationnow.com (a domain I haven’t gotten around to developing otherwise). I’ll point to specific examples of how it is incorporated there as I go along.

By the way, Prelovac’s book does a great job of stepping through techniques in the context of practical examples. My examples are generally less practical, designed instead to give a quick tour through various techniques I have found useful.

In WordPress, a plugin is simply a PHP file loaded under the wp-content/plugins/ directory under the web root directory. Usually, each plugin has it’s own directory, so on my demo site the full path is /home/weblib/public_html/wp-content/plugins/demo/

Inside that directory is a file called demo.php that looks like this.


<?php

/*
Plugin Name: Demo by carrcommunications.com
Plugin URI: http://www.carrcommunications.com
Description: Demo of plugin programming techniques.
Author: David F. Carr
Version: 1.0
Author URI: http://carrcommunications.com/
*/

include WP_CONTENT_DIR."/plugins/demo/dashboard.php";
include WP_CONTENT_DIR."/plugins/demo/menu.php";
include WP_CONTENT_DIR."/plugins/demo/shortcode.php";
include WP_CONTENT_DIR."/plugins/demo/contact_form.php";
include WP_CONTENT_DIR."/plugins/demo/quote_post.php";
include WP_CONTENT_DIR."/plugins/demo/filter.php";
include WP_CONTENT_DIR."/plugins/demo/options.php";
include WP_CONTENT_DIR."/plugins/demo/load-jquery.php";

add_action('wp_footer', 'your_function');

function your_function() {
$content = '<p>This is a wp_footer action call.</p>';

echo $content;
}
?>

The comments at the top of the file are what makes it act as a plugin. The format is explained in the WordPress.org article on Writing a Plugin. As I understand it, the “Plugin Name:” comment is the only one that’s absolutely required. The rest of the tags inside the comment become more important if you are preparing your plugin for publication.

WordPress scans the plugin directory for php files containing this markup and makes them available on the Plugins administration page. Only the plugins that have been activated by an administrator are actually loaded at runtime.

I have this plugin set up to load a series of other files that illustrate various functions and effects. The only active function on display here is the one at the bottom labeled “your function” – an example taken straight from the WordPress documentation on how to use a function hooked to the wp_footer action call.

The WordPress application programming interface is built around a series of these hooks that allow you to trigger your own custom functions in response to the loading of pages or the posting of data.

The two main types of hooks are described in the Filter Reference and Action Reference sections of the WordPress API documentation. Essentially, the distinction is that filters are associated with modifying specific bits of content, whereas actions are associated with events, including events that occur before the page even loads.

If you write a function to act on the filter known as ‘the_content,’ for example, your function will take in the existing content of a post, act on it in some way, and then return it. A filter could translate the content of a post into Pig Latin, if that were your goal. There are similar filters for the post, date, title, and other associated content. Other filters intercept content on its way into the database when an administrator posts an update. Yet others alter database operations. If you wanted to change the sort order of posts on your blog page, you could use a ‘posts_orderby’ filter to modify the SQL query used to retrieve posts.

Another way your plugin can alter the behavior of WordPress is by detecting the occurrence of actions, which are events that take place as WordPress loads and displays a page.

For example, the ‘init’ action comes after the system has loaded all the plugins and created the $wpdb database object (which you will need for any queries or updates) but before the process of displaying a particular page. I often use ‘init’ to intercept a form post and then redirect the user to a different URL depending on whether or not the form submission validates properly. There is also an ‘admin_init’ function that only occurs on administration pages.

The ‘wp_head’ and ‘wp_footer’ actions are triggered by function calls embedded in a template file included with the current WordPress theme. This assumes that the theme follows the standard conventions for the placement of these functions; otherwise, the action will never get called.

In the example above, add_action('wp_footer', 'your_function'); is used to detect the wp_footer action and call a function that outputs a snippet of code.

Modifying the WordPress Administration Screens

The first thing you see when you log in to WordPress is an administrator’s dashboard, with the menus for editing posts, pages, links and so forth on the left. You can modify just about everything you’re seeing here if you know where to hook in your plugin.

I often modify the dashboard, particularly when customizing a website for clients who are too easily distracted by options that are irrelevant to them. For example, WordPress presents several panels worth of information on new plugins and community news that’s really only of interest to techies. Sometimes I have additional information I would like to feature instead. For example, on the website for my son’s cub scout pack, I have assigned logins to the cubmaster and den masters, and I particularly want to present them with options for posting news items and events to the site (which uses my RSVPMaker plugin for event management).

dashboard
Cub Scouts Pack Dashboard

The dashboard.php example file leaves all the default dashboard widgets in place but creates a new one and moves it to the top of the list. This one doesn’t do anything terribly useful, for anyone other than plugin developers. It simply outputs the array contained in the $wp_meta_boxes variable, which you can manipulate to customize the dashboard.

dashboard.php :

<?php

function demo_dashboard_widget_function() {
// Display whatever it is you want to show
global $wp_meta_boxes;
?>

You can remove the default dashboard widgets by removing an item from this array in your dashboard setup routine. Example:

<code>unset($wp_meta_boxes['dashboard']['side']['core']['dashboard_primary']);</code>

The listing below is the $wp_meta_boxes global.
<pre>&lt;?php print_r($wp_meta_boxes);?&gt;</pre>
&lt;?php
}

// Create the function use in the action hook
//force to top
function demo_add_dashboard_widgets() {
wp_add_dashboard_widget('demo_dashboard_widget', 'Demo Dashboard', 'demo_dashboard_widget_function');

// Globalize the metaboxes array, this holds all the widgets for wp-admin

global $wp_meta_boxes;

// Get the regular dashboard widgets array
// (which has our new widget already but at the end)

$normal_dashboard = $wp_meta_boxes['dashboard']['normal']['core'];

// Backup and delete our new dashbaord widget from the end of the array

$demo_widget_backup = array('demo_dashboard_widget' =&gt;
$normal_dashboard['demo_dashboard_widget']);
unset($normal_dashboard['demo_dashboard_widget']);

// Merge the two arrays together so our widget is at the beginning

$sorted_dashboard = array_merge($demo_widget_backup, $normal_dashboard);

// Save the sorted array back into the original metaboxes

$wp_meta_boxes['dashboard']['normal']['core'] = $sorted_dashboard;
}

// Hoook into the 'wp_dashboard_setup' action to register our other functions

add_action('wp_dashboard_setup', 'demo_add_dashboard_widgets' );

?&gt;

The resulting array looks like this:

   [dashboard] => Array
        (
            [normal] => Array
                (
                    [core] => Array
                        (
                            [demo_dashboard_widget] => Array
                                (
                                    [id] => demo_dashboard_widget
                                    [title] => Demo Dashboard
                                    [callback] => demo_dashboard_widget_function
                                    [args] =>
                                )

                            [dashboard_right_now] => Array
                                (
                                    [id] => dashboard_right_now
                                    [title] => Right Now
                                    [callback] => wp_dashboard_right_now
                                    [args] =>
                                )

                            [dashboard_recent_comments] => Array
                                (
                                    [id] => dashboard_recent_comments
                                    [title] => Recent Comments
                                    [callback] => wp_dashboard_recent_comments
                                    [args] =>
                                )

                            [dashboard_incoming_links] => Array
                                (
                                    [id] => dashboard_incoming_links
                                    [title] => Incoming Links
                                    [callback] => wp_dashboard_incoming_links
                                    [args] =>
                                )

                            [dashboard_plugins] => Array
                                (
                                    [id] => dashboard_plugins
                                    [title] => Plugins
                                    [callback] => wp_dashboard_plugins
                                    [args] =>
                                )

                        )

                )

            [side] => Array
                (
                    [core] => Array
                        (
                            [dashboard_quick_press] => Array
                                (
                                    [id] => dashboard_quick_press
                                    [title] => QuickPress
                                    [callback] => wp_dashboard_quick_press
                                    [args] =>
                                )

                            [dashboard_recent_drafts] => Array
                                (
                                    [id] => dashboard_recent_drafts
                                    [title] => Recent Drafts
                                    [callback] => wp_dashboard_recent_drafts
                                    [args] =>
                                )

                            [dashboard_primary] => Array
                                (
                                    [id] => dashboard_primary
                                    [title] => WordPress Development Blog
                                    [callback] => wp_dashboard_primary
                                    [args] =>
                                )

                            [dashboard_secondary] => Array
                                (
                                    [id] => dashboard_secondary
                                    [title] => Other WordPress News
                                    [callback] => wp_dashboard_secondary
                                    [args] =>
                                )

                        )

                )

        )

You can remove some of those default widgets with a command like unset($wp_meta_boxes['dashboard']['side']['core']['dashboard_primary']);

Add Administration Menus

Next, we would be able to add some of our own menu items and data entry or reporting screens to the administration system. We do this by hooking into the ‘admin_menu’ action, registering our menus and submenus, and then creating a function that will display our form or report whenever that menu item is accessed. WordPress provides a variety of functions for adding our own arbitrary menus and submenus, as well as functions specific to adding menus to Options section for setting configuration options. This is all explained exhaustively in the Adding Administrations Menu at WordPress.org.

Here is my example.

menu.php :

&lt;?php

add_action('admin_menu', 'my_demo_menu');

function my_demo_menu() {

$page_title = &quot;My Demo Menu&quot;;
$menu_title = &quot;My Demo Menu&quot;;
$capability = &quot;edit_posts&quot;;
$menu_slug = &quot;demo_menu&quot;;
$function = &quot;demo_menu_function&quot;;
$icon_url = &quot;&quot;;
$position = &quot;&quot;;

add_menu_page( $page_title, $menu_title, $capability, $menu_slug, $function, $icon_url, $position );

$parent_slug = &quot;edit.php&quot;;
$page_title = &quot;Demo Submenu&quot;;
$menu_title = &quot;Demo Submenu&quot;;
$capability = &quot;edit_posts&quot;;
$menu_slug = &quot;demo_post_submenu&quot;;
$function = &quot;demo_post_submenu_function&quot;;

add_submenu_page( $parent_slug, $page_title, $menu_title, $capability, $menu_slug, $function);

}

function demo_post_submenu_function () {

if($_POST)
	{
	print_r($_POST);
	if(wp_verify_nonce($_POST[&quot;qnonce&quot;], &quot;qday&quot;) )
		echo &quot;&lt;p&gt;Verified&lt;/p&gt;&quot;;
	else
		echo &quot;&lt;p&gt;NOT Verified&lt;/p&gt;&quot;;	

	}

	if($id = $_GET[&quot;success&quot;])
		echo &quot;&lt;p&gt;Inserted quote as Post # $id &lt;/p&gt;&quot;;

?&gt;
&lt;div class=&quot;wrap&quot;&gt;
&lt;div id=&quot;icon-edit&quot; class=&quot;icon32&quot;&gt;&lt;br /&gt;&lt;/div&gt;
&lt;h2&gt;Quote of the Day&lt;/h2&gt;
&lt;form name=&quot;form1&quot; method=&quot;post&quot; action=&quot;&lt;?=admin_url()?&gt;edit.php?page=demo_post_submenu&quot;&gt;
&lt;p&gt;Quote&lt;br /&gt;
&lt;textarea name=&quot;quote&quot; id=&quot;quote&quot; cols=&quot;45&quot; rows=&quot;5&quot;&gt;&lt;/textarea&gt;
&lt;/p&gt;
  &lt;p&gt;Author: 
    &lt;input type=&quot;text&quot; name=&quot;author&quot; id=&quot;author&quot;&gt;
  &lt;/p&gt;
  &lt;p&gt;Source: 
    &lt;input type=&quot;text&quot; name=&quot;source&quot; id=&quot;source&quot;&gt;
&lt;/p&gt;
  &lt;p&gt;
    &lt;input name=&quot;live&quot; type=&quot;radio&quot; id=&quot;live&quot; value=&quot;0&quot; checked=&quot;checked&quot; /&gt;
  Test 
  &lt;input type=&quot;radio&quot; name=&quot;live&quot; id=&quot;live2&quot; value=&quot;1&quot; /&gt;
  Live&lt;/p&gt;
  &lt;p&gt;
&lt;?php
wp_nonce_field(&quot;qday&quot;,&quot;qnonce&quot;);
?&gt;
&lt;/p&gt;
&lt;input type=&quot;submit&quot; name=&quot;button&quot; id=&quot;button&quot; value=&quot;Submit&quot; /&gt;
&lt;/form&gt;
&lt;/div&gt;
&lt;?php
}

function demo_menu_function () {
global $current_user;
global $wpdb;

echo &quot;&lt;h2&gt;Current user's posts&lt;/h2&gt;\n&quot;;

$results = $wpdb-&gt;get_results(&quot;SELECT * FROM $wpdb-&gt;posts WHERE post_author=&quot;.$current_user-&gt;ID, ARRAY_A);

if($results)
foreach($results as $row)
{
echo $row[&quot;post_title&quot;].&quot;: &quot;.$row[&quot;post_status&quot;].&quot;&lt;br /&gt;&quot;;
}

}

?&gt;

One of these is more serious than the other. The first demo menu merely creates a screen that dumps out some raw system data:

demo
Demo Menu

The second example is a submenu inserted under Posts, meant to be used to post a quote of the day. I’ve used something like this where it was important for a post to be entered with a very specific formatting that’s easier to make uniform when scripted.

demo submenu
Quote of the Day submenu

From the code shown here, you can see how it is set up to echo back the contents of the $_POST array upon submission. I’ve also included some code for displaying a success or error message.

In the next installment, I will show how to process this input and create a specially formatted post that will be recorded in the blog.

Next: Processing form submissions, setting and retrieving options, and database programming in WordPress