RS Base Plugin – a really simple class based plugin framework

Description

On its own this plugin does nothing.

The class \ReneSeindal\PluginBase is meant as a base class for plugin classes. It is a kind of very simple plugin framework, in reality little more than a class constructor and a handful of helper methods to avoid repetitive coding.

Filters, actions and shortcodes are defined in derived classes simply by making appropriately named class methods. The PluginBase class then automatically adds all the filters, actions and shortcodes.

Additional functionality is defined as PHP traits, which can be used in any combination with the base class.

This class is for making KISS plugins, really simple to program and use, but not flashy. Simplicity is the goal, not looks.

Setup

A minimal plugin looks like this:

add_action( 'plugins_loaded', function() {
    class PluginTemplate extends PluginBase {
        protected $plugin_file = __FILE__;

        // use PluginBaseSettings;

        // Methods galore
    }

    new PluginTemplate();
} );

The class is defined within the plugins_loaded hook to make sure the PluginBase class is loaded.

All derived plugin classes must set the property plugin_file to __FILE__, which is needed for several parts of the base plugin.

Singleton interface

The PluginBase class also implements a static method instance() which creates a singleton interface. This is useful if the plugin object is needed elsewhere, e.g., for a WP-CLI interface.

The last line in the example above could therefore also be written as:

PluginTemplate::instance();

Actions and filters

Most plugins interact with the WordPress core through action and filter hooks.

Actions and filters are defined in derived classes simply by making appropriately named class methods.

Hooks

Hooks for actions and filters are simply correctly named class methods:

  • do_ACTION_action()‘ defines a hook for ACTION;
  • do_ACTION_action_EXTRA()‘ defines the same hook for ACTION as above. The EXTRA part can be descriptive or allow several callbacks for the same ACTION, which can make sense to keep related code together and readable;
  • do_FILTER_filter() defines a hook for FILTER;
  • or ‘do_FILTER_filter_EXTRA()‘ is the same hook for FILTER, just as for actions above.

This cannot work in all cases, as WordPress in some cases uses hook names which do not translate to legal PHP identifiers.

In such cases the correct name of the hook can be defined by a protected class property named $ACTION_callback_hook or $FILTER_callback_hook.

The common case of having the plugin basename in name of a hook can be handled by putting the string __PLUGIN_BASENAME__ in its place, which the base plugin will substitute automatically. Please note that this often leads to three underscores in a row in a method name.

Priority

The priority of an action or filter hook is set by a protected class property named $ACTION_EXTRA_callback_priority or $FILTER_EXTRA_callback_priority. If not defined the WordPress default of 10 is used.

Shortcodes

Shortcodes are likewise defined in derived classes simply by making appropriately named class methods.

  • do_SHORTCODE_shortcode()‘ defines a shortcode named SHORTCODE.

Ajax end-points

To be done: AJAX call backs

Options

Each plugin object has at least these three options/settings related properties:

  • options_page — if not set, it is set to the base-name of $plugin_file with the extension .php removed. This is the default option page URL. See section Settings below.
  • options_base — if not set, it is set to $options_page with all hyphens changed to underscores.
  • options_name — if not set, it is set to $options_base with _options added.

The method get_options() will retrieve option $option_name which is usually an array of all the plugin’s options.

The method get_option( $name, $default = NULL ) will retrieve the named option from the array of plugin options.

Settings

The trait PluginBaseSettings defines a set of methods for making simple option pages.

A link to the Settings page is added automatically to the plugin’s entry on the Plugin admin page.

Plugin requirements

The derived class muse use the trait above to activate the settings functionality of base plugin.

use PluginBaseSettings

Adding the settings page to the menu

The derived class must define a hook for the admin_menu action to generate a menu link to the settings page. In most cases the method settings_add_menu() will be sufficient:

function do_admin_menu_action() {
    $this->settings_add_menu(
        __( 'Settings page title', 'text-domain' ),
        __( 'Menu heading', 'text-domain' )
    );
}

Generating the settings page

A settings page is usually made of one or more sections, each containing one or more related fields. Each section and field has some explanatory text.

If the derived class has a settings_define_sections_and_fields() method, that is called automatically to define, wait for it, sections and fields.

A section is defined with PluginBase::settions_add_section():

$section = $this->settings_add_section(
        'section_id',
        __( 'Display name', 'text-domain' ),
        __( 'Explanatory text', 'text-domain' )
);

The return value is needed for adding fields to the section.

A field is created and added to a section by PluginBase::settings_add_field(), in this way:

$this->settings_add_field(
    'field_id', $section,
    __( 'Field name', 'text-domain' ),
    'settings_field_XXX_html',
    $args
);

The field_id is a unique identifier for the field — which can be used with PluginBase::get_option() to retrieve the value — and the translated string is for display.

The field is rendered by plugin method, in the example above indicated as settings_field_XXX_html with additional arguments $args, an associative array.

Field types

The PluginBase class defines field renderer methods for the most common input types.

Single line text input fields can use settings_field_input_html which takes these arguments:

  • type — input type (optional, defaults to ‘text’)
  • default — default value if no value has ever been saved

Multiline text input, or textareas, can use settings_field_textarea_html:

  • default — default value if no value has ever been saved

Checkboxes can use settings_field_checkbox_html:

  • default — default value if no value has ever been saved

Menus with single or multiple selection can use settings_field_select_html:

  • multiple — whether to allow multiple sections (optional, boolean, default false)
  • size — how many rows to show for multiple selections (default as many as are needed)
  • values — an associative array of the allowed selection; the array key is the value of the option, the array value is the text to display
  • default — default value if no value has ever been saved

Post types and taxonomy menus

The cases of creating multiple selection menus for post types or taxonomies have some extra helpers.

$this->settings_build_post_types_menu(
    $field, $section, $label,
    $query, $default, $hooks
);

Here $query is the argument to get_post_types() 🔗, while $default is the defaults selection. The last $hooks (string|array) are filters through which the list of post types to display is run before rendering the menu.

There’s a similar settings_build_taxonomies_menu().

Minimal settings page example

An minimal example of a settings page with just a post type selection menu (from RS Word Count):

function do_admin_menu_action() {
    $this->settings_add_menu(
        __( 'Word count', 'rs-word-count' ),
        __( 'RS Word count', 'rs-word-count' )
    );
}

function settings_define_sections_and_fields() {
    $section = $this->settings_add_section(
        'section_post_types',
        __( 'Post-types', 'rs-word-count' ),
        __( 'Select the post-types you want to count words in', 'rs-word-count' )
    );

    $this->settings_build_post_types_menu(
        'post_types', $section,
        __( 'Post-types', 'rs-word-count' ),
        [ 'public' => true ],
        $this->default_post_types,
        [
            'rs_word_count_post_types',
            'rs_custom_post_types',
        ]
    );
}

Other traits

The trait PluginBaseCustomLoginPage puts the site logo on the login page instead of the default WordPress logo.

The trait PluginBaseYoastSeo removes the Yoast SEO filter menus from the admin interface.

Installation

The plugin can be installed as any other plugin.

Other plugins using the framework should be defined in the plugins_loaded hook, to ensure the PluginBase class is defined first. If derived classes are loaded earlier, WordPress might report errors.

As a must-use plugin

This can also be resolved by copying the main plugin file to the wp-content/mu-plugins/ folder, where must-use plugins reside. They’re always loaded before the normal plugins.

This should never be necessary if the plugins with derived classes behave as explained above.

A WP-CLI command ‘rsbp’ can install or remove this must-use plugin. Install the must-use plugin with wp rsbp on, remove it with wp rsbp off and check the status with wp rsbp status.

Currently the must-used plugin is created as a symlink to the normal plugin, so updates take effect automatically when the normal plugin is updated. It doesn’t have to be activated in that case.

Resolving errors

If WP-CLI reports errors, use the –skip-plugins to avoid loading the offending plugins until the situation is resolved.

Alternatively, on the URL of the WordPress login screen, add the query string ?action=entered_recovery_mode at the end, and WordPress will not load plugins.

Changelog

1.15

  • Now uses use ReflectionMethod to find argument count for hooks.

1.0

  • First version.

Download


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *