Handling Options

 

Plugin options (a.k.a. settings) are values that your plugin users can configure in their WP environment. The template code creates a convention for how to deal with options, provides some conveniences for creating and using them easily, and a basic administration page for setting option values. This page will discuss the approach and how different parts work together.

1. Option Prefixing

Part of being a good plugin citizen is avoiding having the same name for an option as any other plugin. Otherwise, they collide in the wp_options table. As a best practice, this plugin template code promotes adding a prefix to all of your option names to avoid collisions. So if your “My Awesome Plugin” has an option like “extra_awesome_mode”, that option should be stored in the DB as “MyAwesomePlugin_extra_awesome_mode”.

Seems a bit verbose. To help, the plugin provides convenience functions to automatically handle prefixing so don’t even see it. To use it, you need to use options handling functions alternatives to the WP stock methods.

WP provides option handling functions: get_option, add_option, update_option, delete_option  functions. Instead of using those, you will use functions (methods) defined on your XXX_Plugin.php class: $this->getOption, $this->addOption, $this->updateOption, $this->deleteOption.

will automatically add a prefix to the option name stored in the DB to avoid collisions. $this->getOption also allows you to return a default value if the option is not set.

2. Defining Options

Options are defined declaratively in the XXX_Plugin.php class, getOptionMetaData() function. The purpose of this function is to return an associative array of information about each option. Other parts of the template code use this to do things like automatically generate an administration page.

When you define an option, you can optionally define a selection of values. On the generated options admin page, this will then appear as a drop-down list of choices. Otherwise it appears as a text input field where the user types in a value.

Consider the following example:

1
2
3
4
5
6
7
8
9
public function getOptionMetaData() {
    return array(
        //'_version' => array('Installed Version'), // Leave this one commented-out. Uncomment to test upgrades.
        'ATextOption' => array(__('A text option', 'my-awesome-plugin')),
        'Donated' => array(__('I have donated to this plugin', 'my-awesome-plugin'), 'false', 'true'),
        'CanSeeSubmitData' => array(__('Can See Submission data', 'my-awesome-plugin'),
                                    'Administrator', 'Editor', 'Author', 'Contributor', 'Subscriber', 'Anyone')
    );
}

Note that all options you define will be automatically prefixed as described above.

The commented-out “_version” line is there for testing. The plugin code tracks which version is installed using this option.  If you uncomment that line, you will be able to manually change the version on the options page. You can use this to test upgrade actions because it allows you to set the version back to an older value then the upgrade action will run again. See Upgrade Actions.

As you can see, the key of the associative array is the name of option. A prefixed form of this options is stored in the database. The value of an option can be retrieved using $this->getOption($optionName) where $optionName is the same key. All the prefixing stuff is transparent.

The key of the array is associated with an array. The first value in the array is a display string that will be shown on the plugin’s options admin page to describe the option. Use the “__()” function with your text domain (‘my-awesome-plugin’ in the above example) to allow for internationalization of the string (i.e. translation to different languages).

If that associated array has only one value, then the plugin’s options admin page will display a text input field where the user can type in a value. Example:

1
'ATextOption' => array(__('A text option', 'my-awesome-plugin')),

But if you want a drop-down list of options instead, add them to the array in the order you wish to see them like:

1
 'Donated' => array(__('I have donated to this plugin', 'my-awesome-plugin'), 'false', 'true'),

Initializing Option Defaults

But when you do this, there is an issue I call the “Defaults Issue“. Although you define these options, none of them actually exists in the DB until someone goes to the plugin’s options admin page and clicks the save button. At that point all options are saved. Image your code calls

1
if ('false' == $this->getOption('Donated'))

before any options are saved. You are expecting the string ‘false’ but you get null and the “if” test fails.

You can choose to initialize all option defaults during install. The empty initOptions() function is called when the plugin is installed. You could choose to implement this function in XXX_Plugin.php similar to this:

1
2
3
4
5
6
7
8
9
10
protected function initOptions() {
	$options = $this->getOptionMetaData();
	if (!empty($options)) {
		foreach ($options as $key => $arr) {
			if (is_array($arr) && count($arr > 1)) {
				$this->addOption($key, $arr[1]);
			}
		}
	}
}

That works just fine if you have all the options defined at install time. But inevitably you will add more options in later versions. Those users who upgrade will not get the default values for new options since the re-install is not re-run. You can set them as part of an upgrade action.

Alternatively, you can skip all this initialization. But then you need to handle cases where the options are not already in the Database. You have a couple options here. You can add the default option as an additional parameter to the “getOption” call. So it then becomes:

1
if ('false' == $this->getOption('Donated', 'false'))

In this case, if there is no value for ‘Donated’ in the DB, ‘false’ is returned (a convenient improvement to the old get_option call). If you do that, then you should ensure that ‘false’ is the first choice listed in that appreciated array. That way, when a person goes to the plugin’s options admin page, ‘false’ will appear first in the drop-down list but since it is first, it looks like it is selected.

Alternately, you might add an empty string option as the first in you list of choices.

Role Options

The template code provides some conveniences around determining whether or not a user has an adequate role to perform an operation. From the example above:

1
2
3
4
'CanSeeSpecialData' =>
    array(__('Can See Submission data', 'my-awesome-plugin'),
              'Administrator', 'Editor', 'Author',
              'Contributor', 'Subscriber', 'Anyone')

These choices correspond to WP roles with the addition of “Anyone” which means a user with no-role (a user who is not logged-in or registered with the WP site). This is a convenient way to provide an option where the plugin administrator can decide what level of user can do an operation.

To enforce this option, you would add code like:

1
2
3
if ($this->canUserDoRoleOption('CanSeeSpecialData')) {
    // do protected operation
}

In your XXX_OptionsManager.php file, look at the following function to learn more and refer to the Enforcing Role Security page.

1
2
3
4
canUserDoRoleOption($optionName)
isUserRoleEqualOrBetterThan($roleName)
getRoleOption($optionName)
roleToCapability($roleName)

i18n of Option Choices

In the above example, we use the “__()” function to internationalize the option description but not the values (like ‘true’ and ‘false’). Do NOT use “__()” around the list of values. You don’t want some installations saving ‘true’ but others saving ‘vrai’, ‘evet’, ‘ja’, etc. because when you go to call $this->getOption you don’t know what will be returned.

So we have to find an alternative path to internationalizing those strings. This is done via the $this->getOptionValueI18nString() function defined in the XXX_OptionManager superclass to XXX_Plugin. Have a look at the implementation of the function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
protected function getOptionValueI18nString($optionValue) {
	switch ($optionValue) {
		case 'true':
			return __('true', 'TEXT_DOMAIN');
		case 'false':
			return __('false', 'TEXT_DOMAIN');
 
		case 'Administrator':
			return __('Administrator', 'TEXT_DOMAIN');
		case 'Editor':
			return __('Editor', 'TEXT_DOMAIN');
		case 'Author':
			return __('Author', 'TEXT_DOMAIN');
		case 'Contributor':
			return __('Contributor', 'TEXT_DOMAIN');
		case 'Subscriber':
			return __('Subscriber', 'TEXT_DOMAIN');
		case 'Anyone':
			return __('Anyone', 'TEXT_DOMAIN');
	}
	return $optionValue;
}

You will want to add another case to the switch for each value you add as an option choice. You simply add them to that method. A better practice from the OOP perspective is to override that method and implement your own in Plugin_XXX.php. You can call parent::getOptionValueI18nString() to pick up the existing ones.

 

  8 Responses to “Handling Options”

  1. Thanks Michael, setting up an options admin page was made easy with your template and your instructions were helpful. There is an unnecessary bracket (right parenthesis) at the end of this code snippet.
    if ('false' == $this->getOption('Donated', 'false')))
    Best wishes, Jon

  2. What is the easy way to get_option(‘XX_option_key’)?

    • Literally, just like that. Options are stored in the format XXX_Plugin_option_key, which might look like MyFancyCodeThing_Plugin_custombackground or the like.

      I had to dump all of the site options in order to figure it out though – check this:

      <?php echo ''.print_r(get_alloptions(), true).''; ?>

  3. It would be nice to have other field options (i.e. textarea, checkbox, radio, etc) in createFormControl(). Right now, it’s either text field or Drop-down list. Thanks for your awesome work Michael!

    • I added an “$aOptionFormat” value to “createFormControl” to do a checkboxes… would be easy to add other values to it to define output for other field types.


      protected function createFormControl($aOptionKey, $aOptionMeta, $savedOptionValue, $aOptionFormat = 'select') {
      if (is_array($aOptionMeta) && count($aOptionMeta) >= 2) {

      switch($aOptionFormat) {
      case 'select':
      // Drop-down list
      $choices = array_slice($aOptionMeta, 1);
      ?>
      <select name="" id="">

      <option value="" >getOptionValueI18nString($aChoice) ?>

      <input type="checkbox" name="[]" value="" >getOptionValueI18nString($aChoice) ?>

      <input type="text" name="" id=""
      value="" size="50"/>
      <?php

      }
      }

      Of course, Jesse is correct below, so the function would have to change accordingly if his patch is included in your code.

  4. It is a best practice to store all options as an array with a single wp_options entry, would you accept a patch to do that?

    Thanks!

 Leave a Reply

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>