Quintic
WordPress Plugin Development Notes

Introduction

Plugins are intended for functionality that extends WordPress. Where Themes are intended to change the presentation of the inforamtion, Plugins are intended to change the way WordPress actually functions.

Note: The core WordPress code should never be changed. Changes that are made to the core will be lost on the next upgrade, and also risk destabalizing the actual functionality, with the potential to cause unrecoverable loss of information. If you want or need to modify the way WordPress works, do it as a Plugin. its what they were designed for.

Table Of Contents Plugin

If you look at any of the pages on the site you will see that they have a TOC in the left hand sidebar. In my initial implementation I imbedded this functionality in the Q5 Theme. As an implementation of a Proof Of Concept that was fine, but if I intended on publishing the Theme and/or the TOC functionality, then it needed to be extracted out into its own Plugin. Plus I had no mechanism for customising the TOC (Number of levels, HTML Elements to use as for the TOC entries). So it was time to pull it out of the Theme and create a fully fledge Plugin, which I could then publish to the WordPress universe.

What constitutes a Plugin

As with Themes, WordPress Plugins can be very simple, almost nothing affairs. See the Hello Dolly example, and WordPress provides a detailed guide to developing Plugins – The Plugin Handbook. I have no desire to simply re-interate what is in that handbook, this blog is intended to be what I actually did to convert the TOC code from a piece of imbedded php in the Q5 Theme to a stand-alone Plugin.

Steps:

  1. Create a TOC Plugin entity
  2. Decide on State needed to be retained in database (Options table)
  3. Construct initial TOC code with hooks for Activation / Deactivation / Uninstall
  4. Decide what Configuration menus are required.
  5. TOC specific: Design how code will integrate with whatever Theme is selected
  6. Extract code from Theme and encapsulate so Plugin is agnostic to Theme.

Create a TOC Plugin entity

This is really simple. It consists of defining a unique name, that is descriptive, but which is, as best a scan be guaranteed to be unique. Inline with the Q5 naming theme I decided on Q5 TOC.

You then create a folder within wp-content/plugins with said name, and within that folder add a php file, which contains a comment line

Plugin Name: Q5 TOC.

In its minamilistic sense that is it. Fire up your site and the Plugin will be recognised. If, however, you are wanting to publish your Plugin then you will need more documentation than that line. The following is what I have for Q5 TOC

/**
 * Table of Contents Plugin
 * ========================
 *
 * Plugin Name:		Q5 TOC
 * Plugin URI:  	https://quintic.co.uk/plugins/q5-page-content/
 * Description: 	Inserts a Table of Contents (normally into a side bar).  
 *              	Additionally, links to peer pages and associated topics can be included after the TOC.
 *              	Both TOC and Topic links remain fixed when page scrolls.
 * Version:     	1.0
 * Author:      	Quintic
 * Author URI:  	https://www.quintic.co.uk/
 * Requires at least:5.2
 * Requires PHP: 	7.2
 * License:     	GPLv2 or later
 * License URI:	 	http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * Text Domain: 	page-content
 * Domain Path: 	./languages
 *
 * Q5 TOC 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 2 of the 
 * License, or any later version.
 *
 * Q5 TOC 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.
 */

The above comments are pretty self explanatory, and I basically took a copy from another plugin I am using.. All I would add is that text regarding licensing is legal blurb and it is worth ensuring that you have the latest recommended wording.

Decide on State needed to be retained in database

State in relation to Plugins refers to any information that needs to be retained. For the TOC I needed at least to retain the depth to which the User requires that TOC to trace and the HTML elements that will be used to identify the TOC elements at each level. I could have hard coded these items, but that would have been too restrictive for what I hope will be a general purpose Plugin.

State (or options) such as these are generally retained in the Options table (wp-options). They do not have to be held in the Options table, but WordPress provides a number of handles to make it straightforward to retain state in this table.

I will place a limit of 6 on the depth of the TOC. So I will need to define an entry for Max Depth, and for each depth/level (up to a max of 6) an HTML element, which, pretty obviously, must be unique for each level.

That is it for this step. it is a desgin step, so no code.

Initial TOC Activation / Deactivation / Uninstall code

I like to use Classes where possible (OOO background). Hence I defined a class, with three static methods for:

  • Activation
  • Deactivation
  • Uninstall

Why static methods? Because the register_uninstall_hook can only accept a static method. I could have used non-static methods for the other registration actions, but consistency is nice.

The state for the Plugin should be retained in wp_options table. Depending on how the Plugin is implemented will depend on how and when the entries should be added. For Q5_TOC I intend to add a sub-menu to Settings and use the Settings API to manage the Options/State.

The Settings API provides a range of boiler plate code, in particular it constructs and creates the entry in the wp_options table and provides the

/*
 * q5_toc_registration
 * ===================
 * Defines the plugin Activation / Deactivation and Uninstall functions
 * The functions are declared 'STATIC' because it is a requirement that
 * uninstall class functions are always static. For consistency all three
 * functions have been defined as static.
 *
 * Version: 1.0.0
 *
 * Since:   5.2
 */
 if ( ! defined( 'ABSPATH' ) ) {
	die( 'Invalid request.' );
}
 
 class q5_toc_registration {

	// Plugin Acivation / Deactivation functions.
	public static function q5_toc_activation ()
	{
		add_option('q5_toc_depth', 3);
		add_option('q5_toc_elements', ['h1', 'h2', 'h3']);
	}

	public static function q5_toc_deactivation ()
	{
	}

	public static function q5_toc_uninstall ()
	{
		delete_option('q5_toc_depth');
		delete_option('q5_toc_elements');	
	}
}

Placed the code in its own file called q5_toc_registration.php

Next step was to add the activation hook calls to the initial q5_toc.php file.

if ( ! defined( 'ABSPATH' ) ) {
	die( 'Invalid request.' );
}

require_once('q5_toc_registration.php');

register_activation_hook   ( __FILE__ , 'q5_toc_registration::q5_toc_activation');
register_deactivation_hook ( __FILE__ , 'q5_toc_registration::q5_toc_deactivation');
register_uninstall_hook    ( __FILE__ , 'q5_toc_registration::q5_toc_uninstall');

You will notice the the first couple of lines of code in both files is: if ( ! defined( ‘ABSPATH’ ) ) This is a general purpose security check to ensure that the php has been accessed via the WordPress index.php and not via some other means (i.e.by directly tagging on the files path name to the sites url). It isn’t foolproof, but is good practice.

Notice that the first argument to the registration function calls points to the main plugin file (in this case itself). This is vitally important as this is how the registration function knows which plugin is being registered. If the callback functions are located in a separate file (as in my case they are) then you may need a ‘require_once’ call to load them prior to referencing them.

Fired up the site and there was Q5 TOC listed in the plugins.

Q5 TOC
Q5 TOC

and once activated the options appeared in the wp_options table.

wp_options table
wp_options table

Did run through a check that Uninstall did delete all code files, the folder, and the wp_options entries. Note: Before you do this, do retain a copy of your code as it will be deleted!!

Configuration Menus

Plugin Configuration menus in WordPress are termed Administration menus, because the menus are added, or at least accessed via the Adminstration Menu.

You can add menus for your Plugin, either as an additional top level entry or as a sub-menu under an already existing entry such as Settings or Tools. The recommendation from WordPress is that if you only have a single page for configuration then add it as a sub-menu.

Q5 TOC will only require a couple of configuration menus so I opted to add them to to the Settings menu page. Again my prefernece was to create a class q5_toc_admin_menu and included a method to add the options to the Settings menu and a very simple / minimal page to display a Settings button.

<?php
/*
 * q5_toc_admin_menu
 * =================
 * Defines the admin menus for the q5_toc plugin
 * The menus are displayed as a sub-entry to the settings menu.
 *
 * Version: 1.0.0
 *
 * Since:   5.2
 */
if ( ! defined( 'ABSPATH' ) ) {
	die( 'Invalid request.' );
}

class q5_toc_admin_menu
{
	public function q5_toc_options_page()
	{
		add_options_page(
			'Q5 TOC Options',
			'Q5 TOC',
			'manage_options',
			'q5_toc',
			array ($this, 'q5_toc_options_page_html'));
	}
	
	public function q5_toc_options_page_html()
	{
    // check user capabilities
    if (!current_user_can('manage_options')) {
        return;
    }
    ?>
    <div class="wrap">
        <h1><?= esc_html(get_admin_page_title()); ?></h1>
        <form action="options.php" method="post">
            <?php
            // output security fields for the registered setting "wporg_options"
            settings_fields('q5_toc');
            // output setting sections and their fields
            // (sections are registered for "wporg", each field is registered to a specific section)
            do_settings_sections('q5_toc');
            // output save settings button
            submit_button('Save Settings');
            ?>
        </form>
    </div>
    <?php
	}
}
?>

The result:

Settings Menu
Settings Menu
Minimal TOC menu page
Minimal TOC menu page

A couple of points about the code. The documented method for adding sub_menus in WordPress is add_submenu_page. This method takes seven parameters, with the seventh being optional:

  1. string $parent_slug or filename,
  2. string $page_title,
  3. string $menu_title,
  4. string $capability,
  5. string $menu_slug,
  6. callable $function = ”,
  7. int $position = null

Helpfully there are specific functions available for adding menus to the dashboard menus. They are all detailed in the sub-menu page of the handbook

  • add_options_page – adds a sub-menu to the Settings menu
  • add_management_page – adds a sub-menu to the Tools menu.
  • add_theme_page – adds a sub-menu to the Appearance menu.

The functions are all defined in wp-admin/includes/plugin.php,

Now, to me, the use of a filename as an identifier (See above) seems like bad practice. It only needs for someone to rename the file to foobar everyone’s installation. Slugs are there as unique long-lived identifiers, and personally I cannot see any reason not to use those over filenames. That said the use of filesnames to determine state seems to be firmly embedded in the WordPress coding world so maybe it is safe.

One reason why the use of filenames as identifiers maybe so prevalent is that slugs so hard to find. A published list would be good. (As an FYI – The slug for the Tools menu is ‘tools‘ with the filename ‘tools.php‘). For the Settings menu, I assume that, at some point in the past, it was called the General Options menu. Then to make the options easier to manage and navigate they were subsequently sub-divided in to sub-menu pasges. However, to ensure compatibility with existing plugins, the slug for the newly named Settings menu could not be changed. That’s fine, I understand that, just document it somewhere.

Note: In the function q5_toc_options_page_html which will display the fields, the html is enclosed in a <div></div> element. This is as recommended by WordPress to isolate the html from the rest of the dashboard.

Implementing the actual menus.

Next step is to implement the actual menus that I need. What I need are:

  • Max depth option (Positive Integer <= 6)
  • Array of unique HTML elements, one for each depth.

WordPress includes the Settings API to aid in the defintion of menu items. There is an excellent article on how to implement Plugin menus using the Settings API here. The article is a response to question about using arrays in the options, something I need for the HTML Elements.

The steps involved in defining menus via the Setting API are as follows:

  • Create and Register a new Settings menu
  • Create one or more Sections on the Settings menu page
  • Add the menu fields to the Section(s)

I have to admit that I found this aspect quite difficult to follow. Problems:

  1. It is not clear if the Setings API is only for the Settings admin menu, or whether it will work for all Admin menus. It transpires that it is only for the Settings sub-menus.
  2. The Settings API will automatically generate the appropriate entries in the wp_options table for the fields that you add. However you do not discover this until after you have read (and possibly coded up) the menu registration functions, which includes adding your fields to the wp_options table.
  3. The field callback functions are documented as ” Function that fills the field with the desired form inputs. The function should echo its output. “. In fact the callback is the HTML that displays the field.
  4. Finally there is a bug whereby when you save via the options.php function, unless the option_group === option_slug then saving fails with the message “Error: Options Page Not Found