Building your first WordPress plugin (part 3)

There is no need to ask why anybody would want to write a plugin for WordPress. It’s one of the primary features that makes WordPress so flexible and a good fit for a wide range of projects. In the first part of our series we created the base for a WordPress plugin recognisible by the core. Then, in the second part we learnt how to alter the default functionality of the core. Today we’re going to look at plugin options. This is one of the most common tasks that plugins need to perform.

Commonly you will need to create a set of parameters (options) and give the user the ability to assign appropriate values to them. Values are stored in the database and can be retrieved on request. The plugin will normally perform different actions based on these values, produce different output for example.

What tools does WordPress give us to make this scenario possible? It allows us to register options with the system and retrieve them by assigned ID – The Options API is responsible for that. WordPress also provides a Settings API to create an admin GUI for options dialogues. Apart from that it allows us to add custom items into the admin menu so that the plugin can have its own settings page. Finally, WordPress takes care of plugin security and provides a set of capabilities and cleaning methods to handle user input safely.

Let’s take a detailed look at each part.

Options API

The Options API is a standardized way of storing custom data in the database. All the data is saved in the wp_options table under a given custom name and can be accessed by it from somewhere in the code. The most important functions of the API are:

<?php get_option('opting_name'); ?>
<?php update_option('option_name', $new_value); ?>

The get_option function simply extracts from the database any information stored under a given name and returns it. The function update_option takes an option name and its value and updates the corresponding entry in the database. If there is no such entry it will be created automatically. Both functions can operate with arrays as well as single values. That means you can store array data under a single name in the database and the API will handle serialization and mineralization actions for you. That’s a recommended practice for plugins: store all plugin options as an array under a single name.

Plugin options’ page

You can create a settings page or group of pages for your plugin in the admin menu. If you are creating a group of pages you should add a top level page first:

<?php add_menu_page( $page_title, $menu_title, $capability, $menu_slug, $output_function, $icon_url, $position ); ?>

The parameter values are self-explanatory but you can refer to the source for details. Now you have to add internal pages one by one in the following manner:

<?php add_submenu_page( $parent_slug, $page_title, $menu_title, $capability, $menu_slug, $function ); ?>

As a $parent_slug parameter you have to use the ID of the top level page — in the case of a custom top level page it’s the value you provided as $menu_slug upon registration. If you don’t need several pages you can create a single settings page under one of existing top-level sections — commonly under “Settings” (options-general.php should be used as the $parent_slug). Alternatively there is are shortcut functions for adding subpages under certain admin menu items, in the case of “Settings” it’s add_options_page().

Settings API

The Settings API allows you to create an interface to manage the plugin’s settings; mark a page as a settings page (to process input automatically) and output sections on that page and fields inside each section for accepting user input. To achieve that your first goal is to register settings with the system and create section-fields structure for them:

<?php register_setting($option_group, $option_name, $sanitise_callback); ?>
<?php add_settings_section( $id, $title, $callback, $page ); ?>
<?php add_settings_field( $id, $title, $callback, $page, $section, $args ); ?>

Refer to the Codex for a detailed description of the parameters, but the logic is quite simple: first of all, we register our options name (if there are a lot of options, they could be organised into groups); then we register section(s) with an internal ID and set of fields for each section; the API gives us the ability to specify custom callbacks for input validation and for displaying each field and section.

After registering our options and corresponding fields we have to display them on the settings page — the following functions have to be called inside the <form> tag:

<?php settings_fields($option_group); ?>
<?php do_settings_sections($page); ?>

The settings_fields function takes care of obligatory hidden fields for the native WordPress options mechanism to work. The do_settings_sections actually outputs previously registered sections and fields.

Security considerations

The fundamental security rule when dealing with options is very simple: clean the input, escape the output and take care of capabilities. In other words, if you accept input from a user, you have to check that its format is correct and does not include malicious content (that’s validation), after that you can pass the data for further processing. When displaying data extracted from the database, it should be escaped to output special characters (especially HTML) properly. For both tasks WordPress provides native functions that can be used in different contexts (read more on the subject here).

Another point of concern is the users’ permissions. WordPress has built-in mechanism controlling users’ roles and capabilities that blocks access to certain admin areas for users with insufficient permissions. Only admins are allowed everywhere. When creating options pages you have to assign correct capabilities to them (normally it’s ‘manage_options’) and do not allow users with low privileges to access the page (for more information about WordPress roles and capabilities please refer to the Codex).

Put it all into work

Let’s see the whole scenario in action.

We’ll continue to develop our ‘Hello World’ example (started in the previous parts of the series) that displays guest author’s information under a post with the help of custom taxonomy.

The author’s block markup was previously hard coded into the plugin. Now we are going to give a user the ability to specify a template for that markup using placeholders for author-specific data (name, url and description). Our plugin already has two includable PHP files: core.php (containing the main code) and admin.php (containing admin-related code).

What changes do we need to make?

1. Create a plugin options page (in admin.php)

/* register menu item */
function msp_helloworld_admin_menu_setup(){
add_submenu_page(
'options-general.php',
'Helloworld Settings',
'Helloworld',
'manage_options',
'msp_helloworld',
'msp_helloworld_admin_page_screen'
);
}
add_action('admin_menu', 'msp_helloworld_admin_menu_setup'); //menu setup

/* display page content */
function msp_helloworld_admin_page_screen() {
global $submenu;
// access page settings
$page_data = array();
foreach($submenu['options-general.php'] as $i => $menu_item) {
if($submenu['options-general.php'][$i][2] == 'msp_helloworld')
$page_data = $submenu['options-general.php'][$i];
}

// output
?>
<div class="wrap">
<?php screen_icon();?>
<h2><?php echo $page_data[3];?></h2>
<form id="msp_helloworld_options" action="options.php" method="post">
<?php
settings_fields('msp_helloworld_options');
do_settings_sections('msp_helloworld');
submit_button('Save options', 'primary', 'msp_helloworld_options_submit');
?>
</form>
</div>
<?php
}
/* settings link in plugin management screen */
function msp_helloworld_settings_link($actions, $file) {
if(false !== strpos($file, 'msp-helloworld'))
$actions['settings'] = '<a href="options-general.php?page=msp_helloworld">Settings</a>';
return $actions;
}
add_filter('plugin_action_links', 'msp_helloworld_settings_link', 2, 2);

In this snippet msp_helloworld_admin_menu_setup creates a subpage under the ‘Settings’ menu (it should be executed on the ‘admin_menu’ action hook to work correctly). Then we output the settings form with msp_helloworld_admin_page_screen. It uses the Settings API functions for fields and pre-built WordPress functions for other elements of the interface (like the submit button). Note the action attribute of the <form> tag: it should point to ‘options.php’ to process options correctly. Finally, msp_helloworld_settings_link filter creates a shortcut link to the options page on the plugin management screen.

page link

2. Register plugin options with the system and create fields and rules for them

/* register settings */
function msp_helloworld_settings_init(){

register_setting(
'msp_helloworld_options',
'msp_helloworld_options',
'msp_helloworld_options_validate'
);

add_settings_section(
'msp_helloworld_authorbox',
'Author's box',
'msp_helloworld_authorbox_desc',
'msp_helloworld'
);

add_settings_field(
'msp_helloworld_authorbox_template',
'Template',
'msp_helloworld_authorbox_field',
'msp_helloworld',
'msp_helloworld_authorbox'
);
}

add_action('admin_init', 'msp_helloworld_settings_init');

/* validate input */
function msp_helloworld_options_validate($input){
global $allowedposttags, $allowedrichhtml;
if(isset($input['authorbox_template']))
$input['authorbox_template'] = wp_kses_post($input['authorbox_template']);
return $input;
}

/* description text */
function msp_helloworld_authorbox_desc(){
echo "<p>Enter the template markup for author box using placeholders: [gauthor_name], [gauthor_url], [gauthor_desc] for name, URL and description of author correspondingly.</p>";
}

/* filed output */
function msp_helloworld_authorbox_field() {
$options = get_option('msp_helloworld_options');
$authorbox = (isset($options['authorbox_template'])) ? $options['authorbox_template'] : '';
$authorbox = esc_textarea($authorbox); //sanitise output
?>
<textarea id="authorbox_template" name="msp_helloworld_options[authorbox_template]" cols="50" rows="5" class="large-text code">
<?php echo $authorbox;?>
</textarea>
<?php
}

I should point out that all plugin options should be stored as an array. Despite the fact that we only have one option (authorbox_template), we include it in an array and the corresponding field into the section for demonstration purposes. The registration function msp_helloworld_settings_init should be executed on the ‘admin_init’ hook. The function msp_helloworld_options_validate takes care of user input by cleaning it with the native wp_kses_post filter that relies on the KSES library. The function msp_helloworld_authorbox_desc creates a description for the form’s section and msp_helloworld_authorbox_field outputs a textarea to handle inputted markup. Note that we assign the CSS classes “large-text code” to it so that built-in admin styling is applied.

All this produces the following screen in the WordPress admin panel.

options form

3. Modify the function that outputs author’s box (in core.php)

We do this so that it gets the template from the database and replaces placeholder data ([gauthor_name], [gauthor_url], [gauthor_desc]) with the corresponding values.

/* Create author's box markup */
function msp_helloworld_author_block(){
global $post;

$author_terms = wp_get_object_terms($post->ID, 'gauthor');
if(empty($author_terms))
return;

$name = stripslashes($author_terms[0]->name);
$url = esc_url(get_term_link($author_terms[0]));
$desc = wp_filter_post_kses($author_terms[0]->description);

//get template from option
$options = get_option('msp_helloworld_options');
$out = (isset($options['authorbox_template'])) ? $options['authorbox_template'] : '';

$out = str_replace(
array('[gauthor_url]', '[gauthor_name]', '[gauthor_desc]'),
array($url, $name, $desc),
$out
);

return $out;
}

Finally, our plugin (after applying some styles) produces a nice guest author’s box under post content.

gauthor

Conclusion

Storing and accessing option data is a very common task, that a lot of plugins need to perform. Through the options mechanism you can provide your users with the ability to tune the plugin to their needs (which they will certainly appreciate). Even developing for yourself you may need a way to store details of a particular installation. Relying on native WordPress APIs and functions when solving such tasks is a good way to create maintainable, secure and future-proof code.

What sort of plugins would you like to see available for WordPress? Have you built your own using this series? Let us know in the comments below.

Featured image uses module image via Shutterstock

Anna Ladoshkina

Anna Ladoshkina

Anna Ladoshkina is a freelance web designer and developer who likes to build pretty things with WordPress and write about it. Connect with Anna on Twitter (@foralien) or on her website, www.foralien.com.

Join to our thriving community of like-minded creatives!