14

Let me preface this by saying that I hardly ever work with WordPress - in fact, the last time I did a site in WordPress was back during 2.2. Yesterday I made quite a mess of everything and asked several questions here trying to get a basic menu plugin working.

I now have the plugin fully functional and behaving exactly as I expect, so I decided to make minor changes here and there to add functionality and compatibility - including using the Settings API. However a very short moment into reading tutorials on this API and I became quite confused, then this confusion only deepened as I read on and tried to implement the examples - which was made even more difficult by the fact that my plugin is implemented as a class.

Unless I'm doing something wrong, from what I understand to use the Settings API requires the creation of a new function PER SETTING. This means 3-5 functions for the average plugin, and up to hundreds for more advanced plugins. It just seems ludicrous to write this many functions (and develop a naming system to keep from confusing them) when you could just as easily import all applicable $_POST variables into an array and forego the entire mess.

Perhaps I'm old-fashioned, but unless there's something to gain from it I don't see the reason to triple or quadruple how much code I'm writing. Here's how I managed options before attempting to add the Settings API:

    function __construct() {
        /* constructor stuff */
        $this->options = $this->db_options = get_option( 'de-menu-options' );
        if( $this->options === false ){
            $this->options = $this->defaults;
        }
        if (is_admin()) {
            add_action('admin_menu', array(&$this, 'admin_menu'));
        }   
        /* more stuff */

        // When WordPress shuts down we store changes to options
        add_action('shutdown', array(&$this, 'update'));
    }

    public function admin_menu() {
        add_options_page('DE Menu Options', 'DE Menu', 'manage_options', 'de-menu-options', array(&$this, 'options'));
        add_option('de-menu-options', $this->options);
    }

    public function options() {
        if (!current_user_can('manage_options')) {
            wp_die( __('You do not have sufficient permissions to access this page.') );
        }
        if ( !empty($_POST) && check_admin_referer('de-menu-options') ) {
            // These options are saved to the database at shutdown
            $this->options = array(
                "columns" => $_POST["de-menu-columns"],
                "maintenance" => $_POST["de-menu-maintenance"]
            );
            echo 'DE Menu options saved';
        }
?>

<div class="wrap">
    <h2>DE Menu Plugin</h2>
    <form method="post" action="<?php echo $_SERVER['REQUEST_URI']; ?>">
        <?php settings_fields('de-menu-options'); ?>
        <input type="checkbox" name="de-menu-maintenance" />
        <label for="de-menu-columns">Columns:</label>
        <input type="text" name="de-menu-columns" value="<?php echo $this->options['columns']; ?>" />
        <p class="submit">
        <input type="submit" name="de-menu-submit" value="Update Options »" />
        </p>
    </form>
</div>
<?php
    }

    function update() {
        // By storing all changes at the end we avoid multiple database calls
        $diff = array_diff( $this->options, $this->db_options );
        if( !empty( $diff )  ){
            update_option('de-menu-options', $this->options);
        }
    }

Now with the settings API I have something more like the following:

    function __construct() {
        /* constructor stuff */
        // Do I load options? Will they be loaded for me? Who knows?
        if (is_admin()) {
            add_action('admin_menu', array(&$this, 'admin_menu'));
            add_action('admin_init', array(&$this, 'admin_init'));
        }   
        /* more stuff */
        // Settings API should update options for me... I think
    }

    public function admin_menu() {
        add_options_page('DE Menu Options', 'DE Menu', 'manage_options', 'de-menu-options', array(&$this, 'options'));
        add_option('de-menu-options', $this->options);
    }

    public function admin_init() {
        register_setting('de-menu-options','de-menu-options',array(&$this,'validate'));
        add_settings_section('de-menu-main-options', 'Main Settings', 'options_section', 'de-menu-options');
        add_settings_field('de-menu-maintenance', 'Maintenance Mode', array(&$this,'options_maintenance'), 'de-menu-options', 'de-menu-main-options');
        add_settings_field('de-menu-columns', 'Columns', array(&$this,'options_columns'), 'de-menu-options', 'de-menu-main-options');
    }

    public function options() {
        if (!current_user_can('manage_options')) {
            wp_die( __('You do not have sufficient permissions to access this page.') );
        }
        if ( !empty($_POST) && check_admin_referer('de-menu-options') ) {
            // These options are saved to the database at shutdown
            $this->options = array(
                "columns" => $_POST["de-menu-columns"],
                "maintenance" => $_POST["de-menu-maintenance"]
            );
            echo 'DE Menu options saved';
        }
?>

<div class="wrap">
    <h2>DE Menu Plugin</h2>
    <form method="post" action="<?php echo $_SERVER['REQUEST_URI']; ?>">
        <?php settings_fields('de-menu-options'); ?>
        <?php do_settings_sections('de-menu-options'); ?>
        <p class="submit">
        <input type="submit" name="de-menu-submit" value="Update Options »" />
        </p>
    </form>
</div>
<?php
    }

    public function options_section() {
        echo '<p>' . __('Main description of this section here.','de-menu-lang') . '</p>';
    }

    public function options_maintenance() {
        echo "<input id='de-menu-maintenance' name='options[maintenance]' type='checkbox' />";
    }

    public function options_columns() {
        echo "<input id='de-menu-columns' name='options[columns]' type='checkbox' value=".$this->options['columns']."/>";
    }

    function validate($options) {
        return $options; // I guess?
    }

It's probably painfully obvious from the scrollbars that the code is already longer with just two options. It's like-wise obvious from the comments that I don't entirely understand what I'm doing. Then there's the matter of having 5 new functions (and removing only 1) in order to accomplish all of this.

So just what advantage am I gaining from all of this extra work?

3
  • Do not use them for such cases. I think they are intended for PHP beginners, who need 3-4 options inside their plugin / theme. This is one of the "features" that should have never been implemented... It's basically an API for another API :) Commented Jul 29, 2011 at 14:59
  • 4
    I use the settings API for everything i write, it all depends how you use it, note you can use the API without even using add_settings_section and add_settings_field, those two functions add bloat to your code more than anything, avoid those and you avoid the bloat..
    – t31os
    Commented Jul 29, 2011 at 15:39
  • 1
    I do the same thing as t3los: register the setting itself, then I just code in the forms in HTML on my settings page. If you want to see a really easy way to do this and keep code for later, check out Yoast's WordPress SEO plugin. Commented Jul 29, 2011 at 16:18

3 Answers 3

8

My point of view is that main purpose and benefit of Settings API is structure.

It helps to keep complex settings setups:

  • orderly (logic of registration and sections);
  • secure (nonces, validation callbacks);
  • extensible (hooking into another page or allowing to be hooked into).

As with any such structural overhead it benefits more complex use cases and benefits less simple ones.

So you do can implement anything Settings API does without using it. The question is if you can accomplish that in as reliable, secure and extensible way.

3
  • I finally got my Settings page working with help from this tutorial: alisothegeek.com/2011/01/wordpress-settings-api-tutorial-1 and with the help of the switch statements and helper functions I must say that things are now more orderly in my code (which is nice since I plan on moving from my two test settings to 15-20 total settings).
    – stevendesu
    Commented Jul 30, 2011 at 4:57
  • 1
    @steven_desu yep, the running joke is that everyone who uses Settings API writes a framework for it. :) Couple helper functions are almost inevitable. Also note that Settings API is not considered finalized and there are (vague) plans to improve it in the future (I think it was mentioned in context of 3.3 plans).
    – Rarst
    Commented Jul 30, 2011 at 8:46
  • 1
    I certainly hope it's improved. I honestly see no advantages to the Settings API, but rather every advantage I'm enjoying now is the result of the framework that I borrowed for it. I like that all form elements are now dynamically generated with the same appearance... but that isn't Settings API. I like that default settings and registering settings are handled by the same definitions... but that isn't Settings API. I like that jQuery not only makes the forms pretty, but is progressively enhanced - but I had to manually code the progressive enhancement...
    – stevendesu
    Commented Jul 30, 2011 at 15:50
5

If you use callbacks properly, there's no need for all the redundant code. Here's how I implement the Settings API, in a way that is completely scalable.

Advantages (among other things):

  • The Settings API forces sanitization of untrusted user data.
  • The Settings API forces options to be registered as an options array, resulting in a single wp_options DB entry, rather than discrete DB entries for each option
  • The Settings API facilitates security hardening of the settings form
  • The Settings API facilitates admin UI consistent with core admin UI, resulting in better UX
3
  • So it essentially forces security and aesthetic standards that I was already following without its help? I will read through the tutorial you linked, though. If it makes the Settings API as easy as manually coding the forms (or easier) then I'll accept this answer
    – stevendesu
    Commented Jul 29, 2011 at 16:10
  • Are you aware that the source code you pointed to implements the functions oenology_get_settings_by_tab() and oenology_get_default_options without ever first defining them? I thought it was bad enough at 209 lines of code (after removing comments and blank lines), but once those functions are defined it'll be even longer... For four options?
    – stevendesu
    Commented Jul 29, 2011 at 23:18
  • They're defined elsewhere. The oenology_get_settings_by_tab() isn't really relevant to what you're doing. But you have to define your form-field markup somewhere, just as you have to validate/sanitize user input somehow, so if you're doing it right, you'll have all that same code as well. Commented Jul 29, 2011 at 23:55
1

Thanks for posting this, I was wondering the exact same thing. Lots of functions.

To reduce them you can store your options as arrays. Wordpress serializes the data for you. This saves on code (or functions anyway), but makes worse data. For instance, if you want to sort, hand-edit, export, etc., your tables, they will have these serialized values. On the other hand, your plugin adds fewer entries to the options table and they are easier to clean up.

So here's your code re-done. A few notes:

  • My example demonstrates both simple options (de_w, de_h) and an array option (de_width_height).
  • Always sanitize user input. I used integers in the example because they are easy to sanitize.
  • You do not need $_POST, nonces, check_admin_referer(), update_option(), etc., when using the Settings API.
  • The save happens on the next page load, not at shutdown. Then WP does a redirect to your page. So to debug, print some output and call wp_die() in one of the validation functions.
  • The form action is always "options.php." That is how the Settings API works. Do not use anything else. Well, you can use admin_url('options.php') if you want.
  • WP will print the save message for you.
  • Enhancements not included here: using <label> for accessibility. Using add_settings_error(), settings_error(), which handle messages as well as errors. That's often the only reason to have separate validation functions for each option. You can see below validate_w() and validate_h() could be one function. I looked at trying to abstract out the messaging, but you don't get enough info in the validation callback as I recall. Like what field you're working on.
  • The validation callback functions get a raw $_POST value from the Settings API. I like to name the parameter as such, $raw. For the array option, you get an array, like magic.
  • Edit: $this is better than &$this.

Code:

<?php
$foo= new de_Foo();
class de_Foo {
function __construct() {
    if (is_admin()) {
        add_action('admin_menu', array($this, 'admin_menu'));
        add_action('admin_init', array($this, 'admin_init'));
    } 
}
public function admin_menu() {
    add_options_page(
       'DE Menu Options',
       'DE Menu',
       'manage_options',
       'de-menu-options',
       array($this,'xoxptions')
    );
    // add_option('de-menu-options', $this->options);
}
public function admin_init() {
 register_setting(
      'de-menu-settings-group',
      'de_w',
      array($this, 'validate_w')
 );
 register_setting(
      'de-menu-settings-group',
      'de_h',
      array($this, 'validate_h')
 );
 register_setting(
      'de-menu-settings-group',
      'de_width_height',
      array($this, 'validate_width_height')
 );
 add_settings_section(
      'de-menu-settings-section-size',
      'Size',
      array($this, 'settings_section_size_render'),
      'de-menu-options'
 );
 add_settings_field(
      'de_w',
      'W',
      array($this, 'w_render'),
      'de-menu-options',
      'de-menu-settings-section-size'
 );
 add_settings_field(
      'de_h',
      'H',
      array($this, 'h_render'),
      'de-menu-options',
      'de-menu-settings-section-size'
 );
 add_settings_field(
      'de_width_height',
      'Width / Height',
      array($this, 'width_height_render'),
      'de-menu-options',
      'de-menu-settings-section-size'
 );
}
public function options() {
    if (!current_user_can('manage_options')) {
        wp_die( __('You do not have sufficient permissions to access this page.') );
    }
////////////////////////////
// no no no
////////////////////////////
//         if ( !empty($_POST) && check_admin_referer('de-menu-options') ) {
//             // These options are saved to the database at shutdown
//             $this->options = array(
//                 "columns" => $_POST["de-menu-columns"],
//                 "maintenance" => $_POST["de-menu-maintenance"]
//             );
//             echo 'DE Menu options saved';
//         }
////////////////////////////
?>
<div class="wrap">
<h2>DE Menu Plugin</h2>
<form method="post" action="<?php echo admin_url('options.php'); ?>">
    <?php settings_fields('de-menu-settings-group'); ?>
    <?php do_settings_sections('de-menu-options'); ?>
    <p class="submit">
    <input type="submit" name="de-menu-submit" value="Update Options" />
    </p>
</form>
</div>
<?php
}
public function settings_section_size_render() {
    echo '<p>' . __('Main description of this section here.','de-menu-lang') . '</p>';
}
public function w_render() {
 $w= esc_attr( get_option('de_w') );
 echo "<p><input name='de_w' value='$w'></p>\n";
}
public function h_render() {
 $h= esc_attr( get_option('de_h') );
 echo "<p><input name='de_h' value='$h'></p>\n";
}
public function width_height_render() {
 $width_height= get_option('de_width_height', array());
 $width= esc_attr( @$width_height['width'] );
 $height= esc_attr( @$width_height['height'] );
 echo "<p>Width: <input name='de_width_height[width]' value='$width'></p>\n";
 echo "<p>Height: <input name='de_width_height[height]' value='$height'></p>\n";
}
function validate_w($raw) {
 return (int)$raw;
}
function validate_h($raw) {
 return (int)$raw;
}
function validate_width_height($raw) {
 is_array($raw) or $raw= array();
 $result= array();
 $result['width']= (int)@$raw['width'];
 $result['height']= (int)@$raw['height'];
 return $result;
}
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.