Blurbette Plugin: Admin Control Panel
In this chapter I’ll proceed a bit differently: I’ll make a few changes to WPCX_Blurbette_Def and WPCX_Blurbette_Registry first, then define the new class below.
The sole aim of this admin control panel is to update a list of options. WordPress provides a Settings API that provides output helpers and manages groups of individual settings; but for no particular reason, I prefer to assign them all to a single option as an array.
Here is how the control panel will look:

Notice most of the options are checkbox on/off values. This means the form simply doesn’t submit them if unchecked, and upon processing they won’t show up in our options array. So a simple empty() or isset() serves to check their values. I’ll decide on some key names now, which I can use in building the form and processing the data:
- use_shortcode
- use_widget
- use_copy_metabox
- copied_everywhere (can have 3 values: ‘y’, ‘n’ or undefined)
- clear_on_deactivate
Definition / Registry Updates
I’ll start by adding a new constant to WPCX_Blurbette_Def:
class WPCX_Blurbette_Def {
const POST_TYPE = 'wpcx_blurbette';
const TEXT_DOMAIN = 'wpcx_blurbette';
const OPTION_METAKEY = 'wpcx_blurbette_options';
Then, a new options property in WPCX_Blurbette_Registry:
class WPCX_Blurbette_Registry {
public $objects = array();
public $options = array();
… and a simple addition to its __construct() method:
function __construct() {
spl_autoload_register( array( $this, 'class_autoloader' ) );
$this->options = get_option( WPCX_Blurbette_Def::OPTION_METAKEY );
WPCX_Blurbette_Def::do_all_hooks();
WPCX_Blurbette_Shortcode::do_all_hooks();
add_action( 'widgets_init', array( $this, 'register_widget' ) );
add_action( 'plugins_loaded', array( $this, 'instantiate_the_rest' ) );
}
Now, the Registry fetches and stores all the settings once, and all the classes can look to its options array for values.
While I’m editing WPCX_Blurbette_Registry, I’ll add a few conditionals. First, in the __construct() method:
function __construct() {
spl_autoload_register( array( $this, 'class_autoloader' ) );
$this->options = get_option( WPCX_Blurbette_Def::OPTION_METAKEY );
WPCX_Blurbette_Def::do_all_hooks();
if ( ! empty( $this->options['use_shortcode'] ) ) :
WPCX_Blurbette_Shortcode::do_all_hooks();
endif;
if ( ! empty( $this->options['use_widget'] ) ) :
add_action( 'widgets_init', array( $this, 'register_widget' ) );
endif;
add_action( 'plugins_loaded', array( $this, 'instantiate_the_rest' ) );
}
Now, the shortcode and widget methods are only hooked if the appropriate settings are enabled. This is a clear benefit to using a Registry coding pattern and autonomous classes — it’s easy enable or disable whole sections on a single conditional.
One more change — if shortcodes are disabled, then it doesn’t make sense to provide the TinyMCE button which outputs them:
function instantiate_the_rest() {
if ( is_admin() ) :
if ( ! empty( $this->options['use_shortcode'] ) ) :
$this->register( 'WPCX_Blurbette_MCE_WithDialog', array(
'registry' => $this,
'name' => 'WPCXBlurbette',
'row' => 2,
'js' => plugin_dir_url( __FILE__ ) . 'mce/mce_blurbette.js'
) );
endif;
endif;
}
Immediately after that, I’ll instantiate the new class, which I’ll build below. I’ll place it within the is_admin() conditional too:
function instantiate_the_rest() {
if ( is_admin() ) :
if ( ! empty( $this->options['use_shortcode'] ) ) :
$this->register( 'WPCX_Blurbette_MCE_WithDialog', array(
'registry' => $this,
'name' => 'WPCXBlurbette',
'row' => 2,
'js' => plugin_dir_url( __FILE__ ) . 'mce/mce_blurbette.js'
) );
endif;
$this->register( 'WPCX_Blurbette_AdminPanel', array(
'registry'=>$this
) );
endif;
}
I’ve decided to pass a registry arg, pointing back to this WPCX_Blurbette_Registry instance, for ease of access.
The Admin Panel Class
This class is little more than a standard web form. Taking it in chunks:
<?php
class WPCX_Blurbette_AdminPanel {
private $registry;
const CAPABILITY = 'manage_options';
const NONCE_NAME = 'wpcx_blurbette_admin_nonce';
const NONCE_VALUE = __FILE__;
function __construct( $options ) {
$this->registry = $options['registry'];
add_action( 'admin_menu', array( $this, 'register_submenu' ) );
}
function register_submenu() {
add_submenu_page(
'options-general.php',
__( 'Blurbette Settings', WPCX_Blurbette_Def::TEXT_DOMAIN ),
__( 'Blurbettes', WPCX_Blurbette_Def::TEXT_DOMAIN ),
self::CAPABILITY,
WPCX_Blurbette_Def::POST_TYPE . '_mgmt',
array( $this, 'control_panel' )
);
}
As mentioned above, the $registry property points back to the main WPCX_Blurbette_Registry instance that registered this object. With this property defined, all the settings can be accessed herein as $this->registry->options.
Also, I’ve decided that only users with manage_options capability will have access to this control panel.
Then, I’ve defined a nonce name and value for use below. Notice I’ve set the nonce value to __FILE__; internally WordPress adds the logged-in user id, so this nonce value is completely unique to this class (file) and user.
The add_submenu_page() function (documented here) is purely standard. Next is its referenced callback method, which outputs the page — starting with the first half:
function control_panel() {
if ( isset( $_POST[self::NONCE_NAME] ) ) :
$update_message = $this->save_posted_settings();
endif;
$settings = $this->registry->options;
?><div class="wrap">
<h2><?php echo $GLOBALS['title'] ?></h2>
<?php if ( isset( $update_message ) ) : ?>
<div id="setting-error-settings_updated" class="<?php echo ( stripos( $update_message, 'saved' ) === false )? 'error' : 'updated'; ?> settings-error">
<p><strong><?php echo $update_message ?></strong></p></div>
<?php endif; ?>
<form name="wpcx_blurbette_optsform" method="post" action="<?php
echo add_query_arg( array(
'page' => $GLOBALS['plugin_page']
), admin_url( $GLOBALS['pagenow'] ) );
?>">
<?php wp_nonce_field( self::NONCE_VALUE, self::NONCE_NAME ); ?>
This first checks if there is $_POSTed data by looking for the presence of a defined field (I’ve chosen the nonce field), and if so calls save_posted_settings() (below), which returns a message. If that message is defined, then present a standardized <div> element containing it — HTML classname “saved” if successful, “error” if not.
Then, the form tag, which defines this page as its action. This page’s URL is constructed by adding a ‘page’ query arg (value defined in add_submenu_page(), above) to the global variable $GLOBALS['pagenow']. Followed by the the all-important wp_nonce_field().
The second half of the method is simply the HTML table that presents the information, styled to match other WordPress panels, and the closing tags:
<table class="form-table"> <tr valign="top"> <th scope="row"><?php _e( 'Shortcodes', WPCX_Blurbette_Def::TEXT_DOMAIN ) ?></th> <td><label><input type="checkbox" name="blurbette_opt[use_shortcode]" value="1" <?php checked( $settings['use_shortcode'], 1 ); ?> /> <?php _e( 'Use Shortcodes', WPCX_Blurbette_Def::TEXT_DOMAIN ) ?></label> </td> </tr> <tr valign="top"> <th scope="row"><?php _e( 'Widgets', WPCX_Blurbette_Def::TEXT_DOMAIN ) ?></th> <td><label><input type="checkbox" name="blurbette_opt[use_widget]" value="1" <?php checked( $settings['use_widget'], 1 ); ?> /> <?php _e( 'Use Widget', WPCX_Blurbette_Def::TEXT_DOMAIN ) ?></label> </td> </tr> <tr valign="top"> <th scope="row"><?php _e( 'Copy', WPCX_Blurbette_Def::TEXT_DOMAIN ) ?></th> <td><label><input type="checkbox" name="blurbette_opt[use_copy_metabox]" value="1" <?php checked( $settings['use_copy_metabox'], 1 ); ?> /> <?php _e( 'Enable "Copy" button in post panels', WPCX_Blurbette_Def::TEXT_DOMAIN ) ?></label> <blockquote><?php _e( 'New Blurbettes are available:', WPCX_Blurbette_Def::TEXT_DOMAIN ) ?> <br /> <label><input type="radio" name="blurbette_opt[copied_everywhere]" value="y" <?php checked( $settings['copied_everywhere'], 'y' ); ?> /> <?php _e( 'Everywhere', WPCX_Blurbette_Def::TEXT_DOMAIN ) ?></label> <label><input type="radio" name="blurbette_opt[copied_everywhere]" value="n" <?php checked( $settings['copied_everywhere'], 'n' ); ?> /> <?php _e( 'Nowhere', WPCX_Blurbette_Def::TEXT_DOMAIN ) ?></label> </blockquote> </td> </tr> <tr valign="top"> <th scope="row"><?php _e( 'Deactivation', WPCX_Blurbette_Def::TEXT_DOMAIN ) ?></th> <td><label><input type="checkbox" name="blurbette_opt[clear_on_deactivate]" value="1" <?php checked( $settings['clear_on_deactivate'] , 1 ); ?> /> <?php _e( 'Clear everything when deactivating this plugin', WPCX_Blurbette_Def::TEXT_DOMAIN ) ?></label> </td> </tr> </table> <?php submit_button(); ?> </form> </div> <?php }
Lastly, the save_posted_settings() method, followed by a filter callback:
protected function save_posted_settings() {
if ( ! wp_verify_nonce( $_POST[self::NONCE_NAME], self::NONCE_VALUE ) ) :
return __( 'Sorry, there was an error on this form page.', WPCX_Blurbette_Def::TEXT_DOMAIN );
endif;
if ( ! current_user_can( self::CAPABILITY ) ) :
return __( 'Unauthorized.', WPCX_Blurbette_Def::TEXT_DOMAIN );
endif;
$filtered_input = filter_var(
$_POST['blurbette_opt'],
FILTER_CALLBACK,
array( 'options' => array( $this, 'single_wordchar' ) )
);
$settings_changed = false;
foreach( $filtered_input as $key=>$value ) :
if ( $value != $this->registry->options[$key] ) :
$settings_changed = true;
break;
endif;
endforeach;
$success = update_option( WPCX_Blurbette_Def::OPTION_METAKEY, $filtered_input );
if ( $success || ! $settings_changed ) :
$this->registry->options = $filtered_input;
return __( 'Settings saved.', WPCX_Blurbette_Def::TEXT_DOMAIN );
else:
return __( 'Sorry, settings were not updated.', WPCX_Blurbette_Def::TEXT_DOMAIN );
endif;
}
protected function single_wordchar( $val ) {
$filtered = preg_replace( '/[^\w]/', '', $val );
return $filtered[0];
}
} // end class WPCX_Blurbette_AdminPanel
As with all form-processing routines, input must be checked and filtered. So first this method bails if wp_nonce_verify() fails, and if the user lacks proper capability.
Then, input is filtered. For variety, I’ve chosen the PHP filter_var() function with the callback single_wordchar(). Every input value should be either a 1 or a single letter, so the callback gets to be very strict, stripping out all non-word characters and returning only the first character.
I’ve also defined a variable $settings_changed for a specific reason: if update_option() fails for some reason, it returns false — however it also returns false if the updated option is unchanged (new value matches the database value). By checking if settings have changed, I can combine it with update_options()‘s returned value to determine success or failure.
Recapping the code
Here is the entire WPCX_Blurbette_AdminPanel class:
<?php
class WPCX_Blurbette_AdminPanel {
private $registry;
const CAPABILITY = 'manage_options';
const NONCE_NAME = 'wpcx_blurbette_admin_nonce';
const NONCE_VALUE = __FILE__;
function __construct( $options ) {
$this->registry = $options['registry'];
add_action( 'admin_menu', array( $this, 'register_submenu' ) );
}
function register_submenu() {
add_submenu_page(
'options-general.php',
__( 'Blurbette Settings', WPCX_Blurbette_Def::TEXT_DOMAIN ),
__( 'Blurbettes', WPCX_Blurbette_Def::TEXT_DOMAIN ),
self::CAPABILITY,
WPCX_Blurbette_Def::POST_TYPE . '_mgmt',
array( $this, 'control_panel' )
);
}
function control_panel() {
if ( isset( $_POST[self::NONCE_NAME] ) ) :
$update_message = $this->save_posted_settings();
endif;
$settings = $this->registry->options;
?><div class="wrap">
<h2><?php echo $GLOBALS['title'] ?></h2>
<?php if ( isset( $update_message ) ) : ?>
<div id="setting-error-settings_updated" class="<?php echo ( stripos( $update_message, 'saved' ) === false )? 'error' : 'updated'; ?> settings-error">
<p><strong><?php echo $update_message ?></strong></p></div>
<?php endif; ?>
<form name="wpcx_blurbette_optsform" method="post" action="<?php
echo add_query_arg( array(
'page' => $GLOBALS['plugin_page']
), admin_url( $GLOBALS['pagenow'] ) );
?>">
<?php wp_nonce_field( self::NONCE_VALUE, self::NONCE_NAME ); ?>
<table class="form-table">
<tr valign="top">
<th scope="row"><?php _e( 'Shortcodes', WPCX_Blurbette_Def::TEXT_DOMAIN ) ?></th>
<td><label><input type="checkbox" name="blurbette_opt[use_shortcode]" value="1" <?php
checked( $settings['use_shortcode'], 1 );
?> /> <?php _e( 'Use Shortcodes', WPCX_Blurbette_Def::TEXT_DOMAIN ) ?></label>
</td>
</tr>
<tr valign="top">
<th scope="row"><?php _e( 'Widgets', WPCX_Blurbette_Def::TEXT_DOMAIN ) ?></th>
<td><label><input type="checkbox" name="blurbette_opt[use_widget]" value="1" <?php
checked( $settings['use_widget'], 1 );
?> /> <?php _e( 'Use Widget', WPCX_Blurbette_Def::TEXT_DOMAIN ) ?></label>
</td>
</tr>
<tr valign="top">
<th scope="row"><?php _e( 'Copy', WPCX_Blurbette_Def::TEXT_DOMAIN ) ?></th>
<td><label><input type="checkbox" name="blurbette_opt[use_copy_metabox]" value="1" <?php
checked( $settings['use_copy_metabox'], 1 );
?> /> <?php _e( 'Enable "Copy" button in post panels', WPCX_Blurbette_Def::TEXT_DOMAIN ) ?></label>
<blockquote><?php _e( 'New Blurbettes are available:', WPCX_Blurbette_Def::TEXT_DOMAIN ) ?>
<br />
<label><input type="radio" name="blurbette_opt[copied_everywhere]" value="y" <?php
checked( $settings['copied_everywhere'], 'y' );
?> /> <?php _e( 'Everywhere', WPCX_Blurbette_Def::TEXT_DOMAIN ) ?></label>
<label><input type="radio" name="blurbette_opt[copied_everywhere]" value="n" <?php
checked( $settings['copied_everywhere'], 'n' );
?> /> <?php _e( 'Nowhere', WPCX_Blurbette_Def::TEXT_DOMAIN ) ?></label>
</blockquote>
</td>
</tr>
<tr valign="top">
<th scope="row"><?php _e( 'Deactivation', WPCX_Blurbette_Def::TEXT_DOMAIN ) ?></th>
<td><label><input type="checkbox" name="blurbette_opt[clear_on_deactivate]" value="1" <?php
checked( $settings['clear_on_deactivate'] , 1 );
?> /> <?php _e( 'Clear everything when deactivating this plugin', WPCX_Blurbette_Def::TEXT_DOMAIN ) ?></label>
</td>
</tr>
</table>
<?php submit_button(); ?>
</form>
</div>
<?php
}
protected function save_posted_settings() {
if ( ! wp_verify_nonce( $_POST[self::NONCE_NAME], self::NONCE_VALUE ) ) :
return __( 'Sorry, there was an error on this form page.', WPCX_Blurbette_Def::TEXT_DOMAIN );
endif;
if ( ! current_user_can( self::CAPABILITY ) ) :
return __( 'Unauthorized.', WPCX_Blurbette_Def::TEXT_DOMAIN );
endif;
$filtered_input = filter_var(
$_POST['blurbette_opt'],
FILTER_CALLBACK,
array( 'options' => array( $this, 'single_wordchar' ) )
);
$settings_changed = false;
foreach( $filtered_input as $key=>$value ) :
if ( $value != $this->registry->options[$key] ) :
$settings_changed = true;
break;
endif;
endforeach;
$success = update_option( WPCX_Blurbette_Def::OPTION_METAKEY, $filtered_input );
if ( $success || ! $settings_changed ) :
$this->registry->options = $filtered_input;
return __( 'Settings saved.', WPCX_Blurbette_Def::TEXT_DOMAIN );
else:
return __( 'Sorry, settings were not updated.', WPCX_Blurbette_Def::TEXT_DOMAIN );
endif;
}
protected function single_wordchar( $val ) {
$filtered = preg_replace( '/[^\w]/', '', $val );
return $filtered[0];
}
} // end class WPCX_Blurbette_AdminPanel