wordpress plugin development demystified
DESCRIPTION
For many WordPress users, even seasoned PHP developers, creating new plugins for WordPress seems like a daunting task. After talking about specific best practices in plugin development last year, this presentation will take a step back to show attendees how simple creating plugins for WordPress from the ground up can be by looking at the architecture of a WordPress plugin, from the basic concepts of registering actions and filters to more advanced concepts such as the creation of admin pages and registering shortcodes.TRANSCRIPT
Yannick Lefebvre (@ylefebvre)Wordpress Plugin Developer
Plugin Development Demystified
Plugin Development Demystified
Topics● Introduction● Plugins Overview● File Structure● Actions and Filter Hooks● Activation, Deactivation
and Removal● Administration Pages
● Meta Boxes● Shortcodes● Publishing your plugin● Recommended
Readings● Questions
Introduction
● Migrated from Blogger to Wordpress in April 2004● Released first plugin in March 2005 (Link Library)● Released 7 Plugins to date
● http://profiles.wordpress.org/users/jackdewey/
Plugins Overview
● Allows developers to extend default Wordpress capabilities
● Open plugin architecture present since very first versions
● Plugin API constantly refined and expanded● Plugin code size and complexity vary widely from
one to another● Functionality stays in place when theme is changed● Can be installed directly from Wordpress admin or
through a manual upload and activation process
Basic Plugin File Structure
● Made from one or more php code file(s)● Can optionally contain other file types (e.g. images,
text files, translation files, etc...)● Located directly in the wp-content\plugins directory
or in a sub-directory within the plugins folder● Entry point is a .php file that contains a specific
plugin header at its top
Plugin File Header
<?php/*Plugin Name: My New Google Analytics PluginPlugin URI: http://yannickcorner.nayanna.bizDescription: New revolutionary GA PluginVersion: 1.0Author: Yannick LefebvreAuthor URI: http://yannickcorner.nayanna.bizLicense: GPL2*/?>
<?php/*Plugin Name: My New Google Analytics PluginPlugin URI: http://yannickcorner.nayanna.bizDescription: New revolutionary GA PluginVersion: 1.0Author: Yannick LefebvreAuthor URI: http://yannickcorner.nayanna.bizLicense: GPL2*/?>
● This information registers the plugin with Wordpress● Most of this data is visible to users in the Plugins
admin section
First plugin sighting
Plugin Evaluation Rules
● Function declared in plugin can be called from theme template file or other plugins
● Function names must be different from Wordpress core functions and other plugins
● Entire content is evaluated each time site is rendered
● A single error will usually bring down the entire site
● Using a local development environment is much safer than developing on live site
Plugin Evaluation Error
Actions and Filter Hooks
● The power of plugins comes from their ability to register custom functions to be called at specific points during the execution of Wordpress
● This process is called hooking● Two types of hooks
● Action hooks allow for code to be executed at a specific point during the page processing loop
● Filter hooks are called during Wordpress data processing to allow plugins to modify, increase or reduce data before it is displayed
Assigning an action hook
add_action ( 'hook_name', 'your_function_name', [priority], [accepted_args] );
Exampleadd_action('wp_head', 'newga_header');
add_action ( 'hook_name', 'your_function_name', [priority], [accepted_args] );
Exampleadd_action('wp_head', 'newga_header');
● Most hook names can be found in Wordpress Codex. Includes description of arguments and hook purpose.
● More complete list is available on third-party sites, but these lack additional information
● A third source is the Wordpress code itself
function wp_head() {do_action('wp_head');
}
function wp_head() {do_action('wp_head');
}
Full action hook implementation
add_action('wp_head', 'newga_header');
function newga_header() { ?><script type="text/javascript">var gaJsHost = (("https:" == document.location.protocol) ?
"https://ssl." : "http://www.");document.write(unescape("%3Cscript src='" + gaJsHost + "google-
analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));</script><script type="text/javascript">try{var pageTracker = _gat._getTracker("UA-xxxxxx-x");pageTracker._trackPageview();} catch(err) {}</script>
<? }
add_action('wp_head', 'newga_header');
function newga_header() { ?><script type="text/javascript">var gaJsHost = (("https:" == document.location.protocol) ?
"https://ssl." : "http://www.");document.write(unescape("%3Cscript src='" + gaJsHost + "google-
analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));</script><script type="text/javascript">try{var pageTracker = _gat._getTracker("UA-xxxxxx-x");pageTracker._trackPageview();} catch(err) {}</script>
<? }
● Prints script code in page header when wp_head() is called in theme template
Typical Action Hooks for WP Page
Assigning a filter hook
add_filter($tag, $function_to_add, [$priority], [$accepted_args]);
Exampleadd_filter( 'the_content', 'newga_content_filter');
add_filter($tag, $function_to_add, [$priority], [$accepted_args]);
Exampleadd_filter( 'the_content', 'newga_content_filter');
● Filters hooks receive data arguments that they can modify within their processing function and must return data, modified or intact
● In WP core, filter function are called with one or more data parameters. The number of parameters needs to be used for accepted_args value
function the_content($more_link_text = null, $stripteaser = 0) {$content = get_the_content($more_link_text, $stripteaser);$content = apply_filters('the_content', $content);$content = str_replace(']]>', ']]>', $content);echo $content;
}
function the_content($more_link_text = null, $stripteaser = 0) {$content = get_the_content($more_link_text, $stripteaser);$content = apply_filters('the_content', $content);$content = str_replace(']]>', ']]>', $content);echo $content;
}
Full filter hook implementation
add_filter( 'the_content', 'newga_content_filter');
function newga_content_filter($the_content) {
// Search through contents for links and add google analytics code after hrefs
// <a href="http://www.example.com" onClick="recordOutboundLink(this, 'Outbound Links', 'example.com');return false;">
return $the_content;
}
add_filter( 'the_content', 'newga_content_filter');
function newga_content_filter($the_content) {
// Search through contents for links and add google analytics code after hrefs
// <a href="http://www.example.com" onClick="recordOutboundLink(this, 'Outbound Links', 'example.com');return false;">
return $the_content;
}
● The result of this code would be extra tags and details around links in post and page content
Database Read Filters
Storing plugin data
● Most plugins have options for user configuration● There are multiple ways to store custom plugin data
● Wordpress Options: get_option / set_option, single options or arrays
● Custom tables in SQL schema● Config files in plugin directory
Storing plugin data using Wordpress Options
● update_option( $option_name, $newvalue );● Creates option if it does not exist. Updates if it does.● Accepts single variable or array as value
● get_option( $show, $default );● Show is name of option● Default is optional value to be returned if option does not exist
● Initial option value is usually created in plugin activation function
Activation / Deactivation
● First step in many plugins is to register functions that will be called on activation and deactivation
register_activation_hook(__FILE__, 'my_new_plugin_activate');
register_deactivation_hook(__FILE__, 'my_new_plugin_deactivate');
register_activation_hook(__FILE__, 'my_new_plugin_activate');
register_deactivation_hook(__FILE__, 'my_new_plugin_deactivate');
● The deactivation function should NOT be used to delete user data as deactivation might be temporary
Activation Example
register_activation_hook(__FILE__, 'my_new_plugin_activate');
function my_new_plugin_activate() {if (get_option('NewGA_Options') === false){
$options['gauser'] = '';update_option('NewGA_Options', $options);
}}
register_activation_hook(__FILE__, 'my_new_plugin_activate');
function my_new_plugin_activate() {if (get_option('NewGA_Options') === false){
$options['gauser'] = '';update_option('NewGA_Options', $options);
}}
● This code first checks if the options already exists and creates new default values if they don't.
Uninstallation Code
● Uninstallation code should remove all traces of the plugin
register_uninstall_hook(__FILE__, 'my_new_plugin_uninstall');register_uninstall_hook(__FILE__, 'my_new_plugin_uninstall');
● Can also be implemented as straight PHP file called uninstall.php in plugin directory that will execute when uninstalled
Administrative Pages
● Admin page allows users to configure plugin options● Register function to get called when the admin
menu is built
add_action('admin_menu', 'my_new_plugin_admin_menu');
function my_new_plugin_admin_menu() {
global $pagehooktop;$pagehooktop = add_menu_page( 'New GA
General Options', 'New GA', 'manage_options', 'new-ga', 'my_new_plugin_show_admin', plugins_url( '/icons/NewGA16.png' , __FILE__ ));
}
add_action('admin_menu', 'my_new_plugin_admin_menu');
function my_new_plugin_admin_menu() {
global $pagehooktop;$pagehooktop = add_menu_page( 'New GA
General Options', 'New GA', 'manage_options', 'new-ga', 'my_new_plugin_show_admin', plugins_url( '/icons/NewGA16.png' , __FILE__ ));
}
Administrative Pages
● Implement admin page rendering function
function my_new_plugin_show_admin() { $options = get_option('NewGA_Options');?><h1>New Google Analytics Plugin</h1>
<form name="newgaform" method="post" action="">
GA User ID: <input type="text" name="gauser" value="<?php echo $options['gauser']; ?>"/><br />
<input type="submit" value="Submit" /></form>
<?php }
function my_new_plugin_show_admin() { $options = get_option('NewGA_Options');?><h1>New Google Analytics Plugin</h1>
<form name="newgaform" method="post" action="">
GA User ID: <input type="text" name="gauser" value="<?php echo $options['gauser']; ?>"/><br />
<input type="submit" value="Submit" /></form>
<?php }
Administrative Pages
● Process Post Data
function my_new_plugin_show_admin() {
if (isset($_POST['gauser'])){
$options['gauser'] = $_POST['gauser'];update_option('NewGA_Options', $options);
}$options = get_option('NewGA_Options');?><h1>New Google Analytics Plugin</h1>
<form name="newgaform" method="post" action="">
GA User ID: <input type="text" name="gauser" value="<?php echo $options['gauser']; ?>"/><br />
<input type="submit" value="Submit" /></form>
<?php }
function my_new_plugin_show_admin() {
if (isset($_POST['gauser'])){
$options['gauser'] = $_POST['gauser'];update_option('NewGA_Options', $options);
}$options = get_option('NewGA_Options');?><h1>New Google Analytics Plugin</h1>
<form name="newgaform" method="post" action="">
GA User ID: <input type="text" name="gauser" value="<?php echo $options['gauser']; ?>"/><br />
<input type="submit" value="Submit" /></form>
<?php }
Administrative Pages
● Add Security
function my_new_plugin_show_admin() {
if (isset($_POST['gauser'])){
check_admin_referer('newga');$options['gauser'] = $_POST['gauser'];update_option('NewGA_Options', $options);
}
$options = get_option('NewGA_Options');?><h1>New Google Analytics Plugin</h1>
<form name="newgaform" method="post" action=""><?php wp_nonce_field('newga'); ?>GA User ID: <input type="text" name="gauser" value="<?php
echo $options['gauser']; ?>"/><br /><input type="submit" value="Submit" /></form>
<?php }
function my_new_plugin_show_admin() {
if (isset($_POST['gauser'])){
check_admin_referer('newga');$options['gauser'] = $_POST['gauser'];update_option('NewGA_Options', $options);
}
$options = get_option('NewGA_Options');?><h1>New Google Analytics Plugin</h1>
<form name="newgaform" method="post" action=""><?php wp_nonce_field('newga'); ?>GA User ID: <input type="text" name="gauser" value="<?php
echo $options['gauser']; ?>"/><br /><input type="submit" value="Submit" /></form>
<?php }
Updated Header Code
function newga_header() {$options = get_option('NewGA_Options');?>
<script type="text/javascript">var gaJsHost = (("https:" == document.location.protocol) ?
"https://ssl." : "http://www.");document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
</script><script type="text/javascript">try{var pageTracker = _gat._getTracker("<?php echo $options['gauser']; ?
>");pageTracker._trackPageview();} catch(err) {}</script>
<?php}
function newga_header() {$options = get_option('NewGA_Options');?>
<script type="text/javascript">var gaJsHost = (("https:" == document.location.protocol) ?
"https://ssl." : "http://www.");document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
</script><script type="text/javascript">try{var pageTracker = _gat._getTracker("<?php echo $options['gauser']; ?
>");pageTracker._trackPageview();} catch(err) {}</script>
<?php}
● Use option data in header code
Meta Boxes
● Meta Boxes are the 'containers' that group together data fields in the default Wordpress admin sections
● Plugins can use meta boxes in their own admin sections or to add custom sections to other parts of Wordpress (e.g. Posts Editor, Links Editor, etc...)
Meta Boxes
function my_new_plugin_admin_menu() {
global $pagehooktop;$pagehooktop = add_menu_page( 'New GA General Options', "New
GA", 'manage_options', 'new-ga', 'my_new_plugin_show_admin', plugins_url( '/icons/NewGA16.png' , __FILE__ ));
add_meta_box('newga_general_meta_box', 'General Settings', 'my_new_plugin_meta_box', $pagehooktop, 'normal', 'high');
wp_enqueue_script('postbox');}
function my_new_plugin_admin_menu() {
global $pagehooktop;$pagehooktop = add_menu_page( 'New GA General Options', "New
GA", 'manage_options', 'new-ga', 'my_new_plugin_show_admin', plugins_url( '/icons/NewGA16.png' , __FILE__ ));
add_meta_box('newga_general_meta_box', 'General Settings', 'my_new_plugin_meta_box', $pagehooktop, 'normal', 'high');
wp_enqueue_script('postbox');}
Meta Boxes Implementation
<form name="newgaform" method="post" action=""><?php wp_nonce_field('newga'); ?>GA User ID: <input type="text" name="gauser" value="<?php echo
$options['gauser']; ?>"/><br /><input type="submit" value="Submit" /></form>
<form name="newgaform" method="post" action=""><?php wp_nonce_field('newga'); ?>GA User ID: <input type="text" name="gauser" value="<?php echo
$options['gauser']; ?>"/><br /><input type="submit" value="Submit" /></form>
● The original simple form code does get more complex when using meta boxes to include all of the right styles
● Original form code:
● New form code on following page...
Meta Boxes Implementation
<form name="newgaform" method="post" action=""><?php wp_nonce_field('newga'); ?><div id="poststuff" class="metabox-holder" style='width: 95%'>
<div id="post-body"><div id="post-body-content">
<?php if ($_GET['page'] == 'new-ga'){
global $pagehooktop;do_meta_boxes($pagehooktop, 'normal', $data);
}?>
</div></div><br class="clear"/>
</div></form><script type="text/javascript">
//<![CDATA[jQuery(document).ready( function($) {
// close postboxes that should be closed$('.if-js-closed').removeClass('if-js-closed').addClass('closed');// postboxes setuppostboxes.add_postbox_toggles('<?php
if ($_GET['page'] == 'new-ga'){
global $pagehooktop;echo $pagehooktop;
}?>');
});//]]>
</script>
<form name="newgaform" method="post" action=""><?php wp_nonce_field('newga'); ?><div id="poststuff" class="metabox-holder" style='width: 95%'>
<div id="post-body"><div id="post-body-content">
<?php if ($_GET['page'] == 'new-ga'){
global $pagehooktop;do_meta_boxes($pagehooktop, 'normal', $data);
}?>
</div></div><br class="clear"/>
</div></form><script type="text/javascript">
//<![CDATA[jQuery(document).ready( function($) {
// close postboxes that should be closed$('.if-js-closed').removeClass('if-js-closed').addClass('closed');// postboxes setuppostboxes.add_postbox_toggles('<?php
if ($_GET['page'] == 'new-ga'){
global $pagehooktop;echo $pagehooktop;
}?>');
});//]]>
</script>Okay, maybe this one is still a bit mystifying :)Okay, maybe this one is still a bit mystifying :)
Meta Boxes Implementation
function my_new_plugin_meta_box() { ?>GA User ID: <input type="text" name="gauser" value="<?php
echo $options['gauser']; ?>"/><br /><input type="submit" value="Submit" />
<?php }
function my_new_plugin_meta_box() { ?>GA User ID: <input type="text" name="gauser" value="<?php
echo $options['gauser']; ?>"/><br /><input type="submit" value="Submit" />
<?php }
● Code to render contents of Meta Box
Adding meta boxes to existing editors
add_meta_box ('linklibrary_meta_box', 'Link Library - Additional Link Parameters', 'll_link_edit_extra', 'link', 'normal', 'high');
add_meta_box ('linklibrary_meta_box', 'Link Library - Additional Link Parameters', 'll_link_edit_extra', 'link', 'normal', 'high');
Saving meta box data added to existing editors
add_action('add_link', 'add_link_field');add_action('edit_link', 'add_link_field');add_action('delete_link', 'delete_link_field');
function add_link_field($link_id) {// Save extra link fields// Can be saved to Wordpress options or custom MySQL tables// $link_id parameter is ID of new or existing link
}
Function delete_link_field($link_id) {// Delete custom link data from custom MySQL tables// or Wordpress options
}
add_action('add_link', 'add_link_field');add_action('edit_link', 'add_link_field');add_action('delete_link', 'delete_link_field');
function add_link_field($link_id) {// Save extra link fields// Can be saved to Wordpress options or custom MySQL tables// $link_id parameter is ID of new or existing link
}
Function delete_link_field($link_id) {// Delete custom link data from custom MySQL tables// or Wordpress options
}
● Action hooks are used to register custom functions to save additional meta box data
Adding a shortcode
● Simple codes used in a post or page to insert content
● [gallery]● [gallery id="123" size="medium"]
● Can also be used to output special code before and after content
● [style1]My text block[/style1]● These are often introduced by themes● Dangerous to use since they will become regular text if you
change to a new theme without these codes
● Consider creating a simple shortcode plugin if you repeatedly insert similar code on site
Shortcode Implementation
● Since shortcodes are found anywhere within posts / pages, they must return their output instead of displaying it directly
add_shortcode('youtubevid', 'youtubevid_func');
function youtubevid_func($atts) { extract(shortcode_atts(array( 'id' ), $atts));
$output = '<iframe width="560" height="349" src="http://www.youtube.com/embed/' . $id . '" frameborder="0" allowfullscreen></iframe>';
return $output;}
add_shortcode('youtubevid', 'youtubevid_func');
function youtubevid_func($atts) { extract(shortcode_atts(array( 'id' ), $atts));
$output = '<iframe width="560" height="349" src="http://www.youtube.com/embed/' . $id . '" frameborder="0" allowfullscreen></iframe>';
return $output;}
[youtubevid id='hDV-lgmNQUE'][youtubevid id='hDV-lgmNQUE']
Publishing your plugin on Wordpress.org
● Any Open Source plugin can be published on Wordpress.org with a few very easy steps:1) Register on Wordpress.org
2) Submit a plugin name and description
3) Receive approval within a few days
4) Create a plugin readme following wordpress.org template
5) Publish plugin to Wordpress subversion repository
TortoiseSVN User Interface
Recommended Readings
● Professional Wordpress Plugin Development by Brad Williams, Ozh Richard and Justin Tadlock, published by WROX Press
● Wordpress Codex (codex.wordpress.com)
● PHP.net● StackOverflow.com Programming
Samples● Today's presentation and code samples
available at:● http://yannickcorner.nayanna.biz/wcmtl2011
Questions?
Thank you for attending this talk on Plugin Development Demystified
Contact: [email protected]: @ylefebvre
Blog : http://yannickcorner.nayanna.bizPlugins: http://profiles.wordpress.org/users/jackdewey/