Using WordPress Plugin Boilerplate

Chris Wilcoxson

WordCamp Dayton 2015

slushman

Started using WordPress in 2007 with WordPress.com.

Self-hosted WordPress in 2008 and wrote an eBook to help musicians build their own sites.

Started developing for WordPress in 2011.

Published 3 plugins: ArtistDataPress, BuddyPress Profile Widgets, and BuddyBar

DCC Marketing

6 months ago, my family and I moved to my wife's family farm near Decatur, IL

Lead developer

Full-service, boutique marketing agency

Based in Decatur, IL with an office in Chicago

WordPress Plugin Boilerplate

Brief history

Written by Tom McFarlin of Atlanta, GA in 2011

As of March 4, 2015, taken over by Devin Vinson of Tampa Bay, FL

Why Use It?

Like building a house, start with a good foundation:

Organization helps with maintainability

WP Coding standards

WP documentation standards

Uses WP APIs

Starts with a blank .pot to encourage developers to add internationalization to their plugins

What's New?

The entire project was rewritten from scratch using feedback and contributions fro several other developers

In this rewrite, Tom moved away from using the singleton pattern, which is hotly debated practice among developers

The plugin features a new structure (which we'll go over in more detail)

And the biggest new feature is wppb.io, which serves as the homepage for the project and will house all the documentation coming soon

Structure

Comes ready for WP plugin directory with Markdown docs for README and ChangeLog at the root

The assets folder with an example icon, screenshot, and banner image files for the plugin directory.

Inside the trunk folder...

Admin & Public

Both the admin and public classes have identical structures.

Each has a CSS folder for the related styled

A Javascript folder for any scripts

and a Partials folder for HTML views

The main file in each folder contains all the code needed for that section. We'll go into details of each later.

Includes

Holds all the operational files. This is where the magic happens.

But seriously, thee are the core of your future plugin.

Before we got into all the bits and pieces, let me give you some advice:

Use a Generator

https://wppb.me

Seriously, save yourself a ton of time by using this generator. It takes care of renaming everything for you and prevents alot of troubleshooting from typos.>

plugin-name.php

          
            /**
             * @link              https://example.com
             * @since             1.0.0
             * @package           Plugin_Name
             *
             * @wordpress-plugin
             * Plugin Name:       WordPress Plugin Boilerplate
             * Plugin URI:        https://example.com/plugin-name-uri/
             * Description:       This is a short description of what the plugin does.
             * Version:           1.0.0
             * Author:            Your Name or Your Company
             * Author URI:        https://example.com/
             * License:           GPL-2.0+
             * License URI:       https://www.gnu.org/licenses/gpl-2.0.txt
             * Text Domain:       plugin-name
             * Domain Path:       /languages
             */
          
        

Like any WordPress plugin, it starts with comments describing what it does, who authored it, the current version, etc.

Boilerplate also uses the PHPDoc versions of those same properties.

plugin-name.php

          
            if (!defined( 'WPINC')) { die; }

            function activate_plugin_name() {
              require_once plugin_dir_path( __FILE__ ) . 'includes/class-plugin-name-activator.php';
              Plugin_Name_Activator::activate();
            }
            
            function deactivate_plugin_name() {
              require_once plugin_dir_path(__FILE__) . 'includes/class-plugin-name-deactivator.php';
              Plugin_Name_Deactivator::deactivate();
            }
            
            register_activation_hook(__FILE__, 'activate_plugin_name');
            register_deactivation_hook(__FILE__, 'deactivate_plugin_name');
          
        

register_activation & register_deactivation

Both classes are blank

plugin-name.php

          
            require plugin_dir_path(__FILE__) . 'includes/class-plugin-name.php';

            function run_plugin_name() {
              $plugin = new Plugin_Name();
              $plugin->run();
            }
            run_plugin_name();
          
        

At the bottom of this file, We load up the primary plugin class and call the run method.

Plugin Class

See Full Code

The plugin class is the core of the plugin. Here's where everything ties together. Let's take a look.

Plugin Class

Constructor

          
            public function __construct() {
              $this->plugin_name = 'plugin-name';
              $this->version = '1.0.0';
          
              $this->load_dependencies();
              $this->set_locale();
              $this->define_admin_hooks();
              $this->define_public_hooks();
            }
          
        

The constructors sets the class variables, the plugin_name is used for i18n, the version is used for cache busting scripts and stylesheets.

Then calls these four methods:

Plugin Class

Load Dependencies

          
            private function load_dependencies() {
              require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-plugin-name-loader.php';
              require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-plugin-name-i18n.php';
              require_once plugin_dir_path(dirname(__FILE__)) . 'admin/class-plugin-name-admin.php';
              require_once plugin_dir_path(dirname(__FILE__)) . 'public/class-plugin-name-public.php';
            
              $this->loader = new Plugin_Name_Loader();
            }
          
        

Load dependencies brings in all the other classes used by the plugin class. It also instantiates the Loader class.

Plugin Class

i18n

          
            private function set_locale() {
              $plugin_i18n = new Plugin_Name_i18n();
              $plugin_i18n->set_domain($this->get_plugin_name());
            
              $this->loader->add_action('plugins_loaded', $plugin_i18n, 'load_plugin_textdomain');
            }
          
        

The set_locale method uses the WordPress APIs to set the textdomain for the plugin for i18n.

Plugin Class

Hooks

          
            private function define_admin_hooks() {
              $plugin_admin = new Plugin_Name_Admin(
                $this->get_plugin_name(), $this->get_version()
              );
            
              $this->loader->add_action(
                'admin_enqueue_scripts', $plugin_admin, 'enqueue_styles'
              );
            
              $this->loader->add_action(
                'admin_enqueue_scripts', $plugin_admin, 'enqueue_scripts'
              );
            }
          
        

There are two methods where the WordPress hooks and filters are setup. This is the admin hooks method, the public-facing method is exactly the same, just referencing different hooks.

This is one of the more interesting parts of the 3.0 rewrite. It instantiates the Admin class, then you can see it calls the add_action method with the loader class.

The add_action method is really just a wrapper for a standard add_action like anywhere else in WordPress. Basically, its gathering all those calls and running them all at once. This is part of keeping your plugin organized and providing a stable, consistant structure.

Loader Class

Constructor

          
            public function __construct() {
              $this->actions = array();
              $this->filters = array();
            }
          
        

The constructor sets the actions and filters class variables as blank arrays.

Loader Class

Add Action

          
            public function add_action($hook, $component, $callback, $priority = 10, $accepted_args = 1) {
              $this->actions = $this->add(
                $this->actions,
                $hook,
                $component,
                $callback,
                $priority,
                $accepted_args
              );
            }
          
        

They work the same way: they take the arguments from your call in the plugin class and load each one into their respective arrays.

They use a helper method call "Add", which simply adds a new subarray to either the actions array.

There's also an add_filter method, which appears exactly the same way.

Those are processed in the run method.

Loader Class

Run Method

          
            public function run() {
              foreach ($this->actions as $hook) {
                add_action(
                  $hook['hook'],
                  array($hook['component'], $hook['callback']),
                  $hook['priority'],
                  $hook['accepted_args']
                );
              }
            }
          
        

These are simple foreach loops where each subarray uses the standard add_action WordPress function to setup all the various bits and piece of your plugin.

There's also a loop for adding filters, I ran out of room on the slide, but it works exactly the same way.

That's the loader class.

Admin & Public Classes

Constructor

          
            public function __construct($plugin_name, $version) {
              $this->plugin_name = $plugin_name;
              $this->version = $version;
            }
          
        

The admin and public classes are structured the same and have the same methods, but keep the pulic-facing code and admin-facing code separated.

The constructor just sets the i18n and version class variables.

Admin & Public Classes

Enqueue Styles

          
            public function enqueue_styles() {
              wp_enqueue_style(
                $this->plugin_name,
                plugin_dir_url(__FILE__) . 'css/plugin-name-admin.css',
                array(),
                $this->version,
                'all'
              );
            }
          
        

Then they have added two example methods within each class.

The first is enqueue_styles, which enqueues the sample CSS file.

Admin & Public Classes

Enqueue Scripts

          
            public function enqueue_scripts() {
              wp_enqueue_script(
                $this->plugin_name,
                plugin_dir_url(__FILE__) . 'js/plugin-name-admin.js',
                array( 'jquery' ),
                $this->version,
                false
              );
            }
          
        

Enqueue scripts works the same basic way, calling the WordPress function to enqueue a script using the sample script file included with the boilerplate.

Other Files

index.php files - security measure

languages folder & blank pot file

license.txt - copy of GPL2 license

readme - text for the plugin directory

uninstall.php - blank

Examples

I wrote up an example plugin to show how this stuff works in practice. While explaining it might help you, seeing working code helps more.

Custom Post Type

Plugin Class

          
            private function define_admin_hooks() {
              $plugin_admin = new Now_Hiring_Admin(
                $this->get_i18n(),
                $this->get_version()
              );
            
              $this->loader->add_action('init', $plugin_admin, 'new_cpt_jobs');
            }
          
        

In the admin hooks method in the plugin class, we hook the new_cpt_jobs method onto init.

Custom Post Type

Admin Class

          
            public function new_cpt_jobs() {
              $cap_type = 'post';
              $plural = 'Jobs';
              $single = 'Job';
            
              $opts['show_ui'] = TRUE;
              $opts['supports'] = array('title', 'editor', 'thumbnail');
              $opts['capabilities']['edit_post'] = "edit_{$cap_type}";
              $opts['labels']['add_new'] =
                  __("Add New {$single}", $this->i18n);
              ...
            
              register_post_type(strtolower($plural), $opts);
            }
          
        

Then in the admin class, the new_cpt_jobs method registers the custom post type. I can't fit the entire array on the slide, but you can see part of it there before the register_post_type call.

Taxonomy

Plugin Class

          
            private function define_admin_hooks() {
              $plugin_admin = new Now_Hiring_Admin(
                $this->get_i18n(),
                $this->get_version()
              );
            
              $this->loader->add_action('init', $plugin_admin, 'new_tax_type');
            }
          
        

Taxonomies work the same basic way as a custom post type. We hook the new_taxonomy_type method from the admin class on init.

Taxonomy

Admin Class

          
            public function new_taxonomy_type() {
              $plural = 'Types';
              $single = 'Type';
              $tax_name = 'job_type';
            
              $opts['query_var'] = $tax_name;
              $opts['capabilities']['assign_terms'] = 'edit_posts';
              $opts['labels']['add_new_item'] =
                  __("Add New {$single}", $this->i18n);
              ...
            
              register_taxonomy($tax_name, 'jobs', $opts);
            }
          
        

Then in the new_taxonomy_type method, we register the taxonomy. Again, I'm not showing the entire options array, just a snippet.

Plugin Settings

Plugin Class

          
            private function define_admin_hooks() {
              $plugin_admin = new Now_Hiring_Admin(
                $this->get_i18n(),
                $this->get_version()
              );
            
              $this->loader->add_action('admin_menu', $plugin_admin, 'add_menu');
              $this->loader->add_action('admin_init', $plugin_admin, 'register_settings');
            }
          
        

In the admin class, we hook the register_settings method to admin_init and the add_menu method to admin_menu.

Plugin Settings

Admin Class - Register Settings

          
            public function register_settings() {
              register_setting('now_hiring_options', 'now_hiring_options',
                  array($this, 'validate_options'));
            
              add_settings_section('now_hiring_display_options',
                  'Display Options',
                  array($this, 'display_options_section'), 'now-hiring');
            
              add_settings_field('display_salary', 'Display Salary',
                  array($this, 'display_options_field'),
                  'now-hiring', 'now_hiring_display_options');
            }
          
        

You can see, I've registered the setting "now_hiring_options", added a display options section, and added one field called display_salary.

Plugin Settings

Admin Class - Options Page

          
            public function options_page() {
              echo '<h2>Now Hiring Settings';
              echo '<form method="post" action="options.php">';
              settings_fields('now_hiring_options');
              do_settings_sections('now-hiring');
              submit_button('Save Settings');
              echo '</form>';
            }
          
        

Typical options page stuff here.

Plugin Settings

Admin Class - Add Menu

          
            public function add_menu() {
              add_options_page(
                __('Now Hiring Settings', $this->i18n),
                __('Now Hiring', $this->i18n),
                'manage_options',
                'now-hiring',
                array($this, 'options_page')
              );
            }
          
        

Plugin Settings

Admin Class - Display Section

          
            public function display_options_section($params) {
              echo '<p>' . $params['title'] . '</p>';
            }
          
        

Plugin Settings

Admin Class - Display Field

          
            public function display_options_field() {
              $options = get_option('now_hiring_options');
            
              ?><input 
                type="checkbox" 
                id="now_hiring_options[display_salary]" 
                name="now_hiring_options[display_salary]" 
                value="1" <?php checked(1, $options['display_salary'], false); ?>
              /><?php
            }
          
        

Plugin Settings

Admin Class - Validate Options

          
            public function validate_options($input) {
              $display_salary = trim($input['display_salary']);
              $valid['display_salary'] = isset($display_salary) ? 1 : 0;
            
              if ($valid['display_salary'] != $input['display_salary']) {
                add_settings_error(
                  'display_salary',
                  'display_salary_error',
                  'Display salary error.',
                  'error'
                );
              }
            
              return $valid;
            } // validate_options()
          
        

Settings & Row Links

Settings & Row Links

Plugin File

          
            if (!defined('NOW_HIRING_BASENAME')) {
              define('NOW_HIRING_BASENAME', plugin_basename(__FILE__));
            }
          
        

One of the little things I like including in my plugins are settings link and row links.

Settings & Row Links

Plugin Class

          
            private function define_admin_hooks() {
              $plugin_admin = new Now_Hiring_Admin($this->get_i18n(), $this->get_version());
            
              $this->loader->add_action(
                'plugin_action_links_' . NOW_HIRING_BASENAME,
                $plugin_admin,
                'settings_link'
              );
              $this->loader->add_action(
                'plugin_row_meta',
                $plugin_admin,
                'row_links', 10, 2
              );
            }
          
        

Settings & Row Links

Admin Class - Settings Link

          
            public function settings_link($links) {
              $settings_link = sprintf(
                '<a href="%s">%s</a>',
                admin_url('options-general.php?page=now-hiring'),
                __('Settings')
              );
            
              array_unshift($links, $settings_link);
            
              return $links;
            }
          
        

Settings & Row Links

Admin Class - Row Link

          
            public function row_links($links, $file) {
              if ($file == NOW_HIRING_BASENAME) {
                $link = '<a href="https://grumpycats.com/">Grumpy Cat</a>';
                array_push($links, $link);
              }
            
              return $links;
            }
          
        

Metaboxes

Plugin Class

          
            private function define_admin_hooks() {
              $plugin_admin = new Now_Hiring_Admin($this->get_i18n(), $this->get_version());
              $this->loader->add_action('add_meta_boxes', $plugin_admin, 'add_metaboxes');
              $this->loader->add_action('save_post_jobs', $plugin_admin, 'save_meta', 10, 2);
            }
          
        

Metaboxes

Admin Class - Add Metaboxes

          
            public function add_metaboxes() {
              add_meta_box(
                'now_hiring_job_location',
                __('Job Location', $this->i18n),
                array($this, 'callback_metabox_job_location'),
                'jobs',
                'normal',
                'default'
              );
            }
          
        

Metaboxes

Admin Class - Metabox Method

          
            public function callback_metabox_job_location($object, $box) {
              include( 
                plugin_dir_path(__FILE__) . 'partials/now-hiring-admin-display-metabox-job-location.php'
              );
            }
          
        

Metaboxes

Admin Class - Save Meta

          
            public function save_meta($post_id, $object) {
              // check for autosave, post type, capability, & set nonce
              if (!wp_verify_nonce( $_POST['job_location_nonce'], NOW_HIRING_BASENAME)) { 
                return $post_id;
              }
            
              $custom = get_post_custom($post_id);
              $metas = array('job-location');
            
              foreach ($metas as $meta) {
                // sanitize data
                // update meta
              }
            }
          
        

Shortcode

Plugin Class

          
            private function define_public_hooks() {
              $plugin_public = new Now_Hiring_Public(
                $this->get_i18n(),
                $this->get_version()
              );
            
              $this->loader->add_action(
                'init',
                $plugin_public,
                'register_shortcodes'
              );
            }
          
        

Shortcode

Public Class - Register Shortcodes

          
            public function register_shortcodes() {
              add_shortcode( 'nowhiring', array( $this, 'shortcode' ) );
            }
          
        

Shortcode

Public Class - Shortcode Method

          
            public function shortcode($atts) {
              ob_start();
            
              $defaults['order'] = 'date';
              $defaults['quantity'] = -1;
              $args = shortcode_atts($defaults, $atts, 'nowhiring');
              $items = $this->get_job_posts($args);
            
              ...
            
              $output = ob_get_contents();
            
              ob_end_clean();
            
              return $output;
            }
          
        

Displays, Partials, Views, Oh My!

Shortcode Method

          
            if (is_array($items) || is_object($items)) {
              include(plugin_dir_path(__FILE__) . 'partials/now-hiring-public-display.php');
            }
          
        

Displays, Partials, Views, Oh My!

Public Class - Display Loop

          
            foreach ($items->posts as $item) {
              include(plugin_dir_path(__FILE__) . 'now-hiring-public-display-single.php');
            }
          
        

I set these up in separate files so I can use a plugin option to switch out the display, so I keep each display's code separated.

Displays, Partials, Views, Oh My!

Public Class - Display Loop

          
            $plugin_path = plugin_dir_path(__FILE__);
            $layout = esc_attr($options['layout']);
            
            foreach ($items->posts as $item) {
              include( 
                $plugin_path . 'now-hiring-public-display-single-' . $layout . '.php' 
              );
            }
          
        

The separated files allow for the possibility of using a plugin option to determine which single display gets shown on the front-end.

Displays, Partials, Views, Oh My!

Public Class - Display Single

          
            ?><div class="job-wrap">
              <h1 class="job-title">
                <a href="<?php echo get_permalink($item->ID); ?>"><?php 
                  echo esc_attr($item->post_title);
                ?></a>
                </h1>
                <div class="job-content"><?php echo $item->post_content; ?></h1>
            </div>
          
        

This is the display I've created for the example. I could see having a plugin option for choosing a different single job posting display. In that case, I'd put the option logic in the loop display file, which would then load one of the single displays based on the plugin option.

Widgets

Where?

I found two ways of incorporating a widget into the plugin.

Both options use these two methods, just in different places.

Plugin Class

Load Dependencies

          
            private function load_dependencies() {
              ...
              require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-now-hiring-widget.php';
            }
          
        

In the plugin class, add your widget class file in the load_dependencies method.

If you have more than one widget, load each file as a dependency here.

Init Widgets

          
            public function widgets_init() {
              register_widget('now_hiring_widget');
            }
          
        

Next is the widget registration method, which I do similarly to register metaboxes where I do them all at once.

Flush Widget Cache

          
            public function flush_widget_cache($post_id) {
              if (wp_is_post_revision($post_id)) { return; }
              $post = get_post($post_id);
              if ($post->post_type == 'jobs') {
                  wp_cache_delete($this->i18n, 'widget');
              }
            }
          
        

Lastly, based on the Tom McFarlin's widget boilerplate, I also include a flush_widget_cache, which is triggered when changes within WordPress affect the output of a widget.

Method One

Plugin Class

Constructor

          
            public function __construct($plugin_name, $version) {
              ...
              $this->define_widget_hooks();
            }
          
        

Option 1 is putting all the widget methods in the plugin class. To kick it off, add a method call in the contructor, I called mine define_widget_hooks.

Plugin Class

Define Widget Hooks

          
            private function define_widget_hooks() {
              $this->loader->add_action('widgets_init', $this, 'widgets_init');
              $this->loader->add_action('save_post_jobs', $this, 'flush_widget_cache');
              $this->loader->add_action('deleted_post', $this, 'flush_widget_cache');
              $this->loader->add_action('switch_theme', $this, 'flush_widget_cache');
            }
          
        

Option 1 is putting all the widget methods in the plugin class. To kick it off, add a method call in the contructor, I called mine define_widget_hooks. You can see, we hook the widgets_init method so the widgets will be recognized by WordPress and we flush the widget cache when posts are saved, which would end up changing the output of our widget.

Method Two

Shared Class

Constructor

          
            public function __construct($plugin_name, $version) {
              ...
              $this->define_shared_hooks();
            }
          
        

The second option is to create a shared class, where the widgets_init, flush_widget_cache methods are located. In the constructor, we declare a different hooks method.

Shared Class

Load Dependencies

          
            private function load_dependencies() {
              ...
              require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-now-hiring-shared.php';
            }
          
        

Since we're creating a new class, we'll need to load its file as a dependency.

Shared Class

Define Widget Hooks

          
            private function define_shared_hooks() {
              $plugin_shared = new Now_Hiring_Shared($this->get_i18n(), $this->get_version());
            
              $this->loader->add_action('widgets_init', $plugin_shared, 'widgets_init');
              $this->loader->add_action('save_post_jobs', $plugin_shared, 'flush_widget_cache');
              $this->loader->add_action('deleted_post', $plugin_shared, 'flush_widget_cache');
              $this->loader->add_action('switch_theme', $plugin_shared, 'flush_widget_cache');
            }
          
        

This operates the same way as the admin and public hooks methods, but refers to our new shared class. From there, the shared class looks just like the admin and public classes, so we don't need to review it.

Review

This plugin is on github and I'm hoping to keep developing it and make it ready or the WordPress plugin directory. These are basic examples, but I think you'll get a good idea about how to work with the boilerplate.

Links