How to update selective options on plugin settings page

I write a wordpress plugin. My plugin has options. All options are in one group, ‘pluginname-settings’. All option names are uniform and look like “pluginname-settings[‘option-name’]”. Some code to illustrate my case:

register_setting( 'pluginname_options', 'pluginname-settings', 'pluginname_validate' );
add_settings_section( 'section-one', 'Name of Section One', 'section_one_callback', 'pluginname_options-sectionname' );
add_settings_field( 'optionname', 'First Option', 'my_text_input', 'pluginname_options-sectionname', 'section-button', array(
    'name' => 'pluginname[optionname]',
    'value' => '42')
    )

For blog admins to tweak them, I created a settings page and grouped options in tabs. Each tab shows a subset of options (one section) and an “Update button”, as follows:

Read More
echo "<form action="options.php" method="POST">";
settings_fields( 'pluginname_options' );
do_settings_sections( 'pluginname_options-sectionname' ); 
submit_button('Update', 'primary',  'submit-form', false);
echo "</form>";

The problem is that when I press “Update” on one tab, the options from other tabs get cleared.

To my understanding, this is because all options represent an array of one setting.

Question: is it possible to save one section without clearing others under this setup? What is a good practice in such cases — register separate settings, use validation function to fill in the blanks or something else? Or is it just me doing it all wrong?

Thank you in advance.

P.S.: Per request in comments, here is a minimal working example:

<?php
/*
Plugin Name: Plugin Name
Plugin URI:
Description: Description
Version: 0.0.1
Author: Author Name
Author URI:
License: GPL3+
*/

add_action( 'admin_menu', 'pluginname_menu' );

function pluginname_menu()
    {
    add_options_page( 'PluginName Options', 'pluginname', 'manage_options', 'pluginname_options_page', 'pluginname_options_page' );
    }

add_action( 'admin_init', 'pluginname_admin_init' );

function pluginname_admin_init()
    {
    register_setting( 'pluginname_options', 'pluginname-settings' );
    add_settings_section( 'section-one', 'Section One', 'pluginname_section_callback', 'pluginname_options-section-one' );
    $options_array = get_option('pluginname-settings');
    add_settings_field( 'optionname-one', 'First Option', 'pluginname_field_callback', 'pluginname_options-section-one', 'section-one', array(
    'name' => 'pluginname-settings[optionname-one]',
    'value' => isset($options_array['optionname-one']) ? $options_array['optionname-one'] : '42')
    );
    add_settings_field( 'optionname-two', 'Second Option', 'pluginname_field_callback', 'pluginname_options-section-one', 'section-one', array(
    'name' => 'pluginname-settings[optionname-two]',
    'value' => isset($options_array['optionname-two']) ? $options_array['optionname-two'] : '42')
    );
    add_settings_section( 'section-two', 'Section Two', 'pluginname_section_callback', 'pluginname_options-section-two' );
    add_settings_field( 'optionname-three', 'Third Option', 'pluginname_field_callback', 'pluginname_options-section-two', 'section-two', array(
    'name' => 'pluginname-settings[optionname-three]',
    'value' => isset($options_array['optionname-three']) ? $options_array['optionname-three'] : '42')
    );
    add_settings_field( 'optionname-four', 'Fourth Option', 'pluginname_field_callback', 'pluginname_options-section-two', 'section-two', array(
    'name' => 'pluginname-settings[optionname-four]',
    'value' => isset($options_array['optionname-four']) ? $options_array['optionname-four'] : '42')
    );
    }

function pluginname_validate($input)
    {
    return $input;
    }

function pluginname_section_callback()
    {
    echo 'Feel free to change parameters below.';
    }

function pluginname_field_callback( $args )
    {
    $name = esc_attr( $args['name'] );
    $value = esc_attr( $args['value'] );
    echo "<input type='text' name='$name' value='$value' /> ";
    }

function pluginname_tab1()
    {
    echo "<form action="options.php" method="POST">";
    settings_fields( 'pluginname_options' );
    do_settings_sections( 'pluginname_options-section-one' ); 
    submit_button('Update', 'primary',  'submit-form', false);
    echo "</form>";
    }

function pluginname_tab2()
    {
    echo "<form action="options.php" method="POST">";
    settings_fields( 'pluginname_options' );
    do_settings_sections( 'pluginname_options-section-two' ); 
    submit_button('Update', 'primary',  'submit-form', false);
    echo "</form>";
    }

function pluginname_options_page()
    {
    ?>
    <div class="wrap">
        <h2>Options</h2>
        <h2 class="nav-tab-wrapper">
        <a href="?page=pluginname_options_page&tab=1" class="nav-tab <? if ( @( $_GET['tab'] == '1' ) || !isset($_GET['tab'])) echo "nav-tab-active"; ?>">Section One Options</a>
        <a href="?page=pluginname_options_page&tab=2" class="nav-tab <? if ( $_GET['tab'] == '2' ) echo "nav-tab-active"; ?>">Section Two Options</a>
        </h2>
        <?php $current_tab = $_GET['tab'];
        switch ($current_tab)
            {
            case '2' : pluginname_tab2(); break;
            default: pluginname_tab1();
            } ?>
    </div>
    <?php
}
?>

minimal working example.

Related posts

3 comments

  1. First kick in your validation callback by changing the register_setting() to

    register_setting( 'pluginname_options', 'pluginname-settings', 'pluginname_validate' );
    

    And then update your validation function to actually do something. Below it gets the current state of the options, and then only updates the pieces of the array that are submitted. When you are on a tab and click “Update” only the info in the tab is posted. Therefore the array (as written) only has 2 keys, and the info from the other tab is purged.

    function pluginname_validate($input)
        {
        $options_array = get_option('pluginname-settings');
    
        if( isset( $input['optionname-one'] ) )
             $options_array['optionname-one'] = sanitize_text_field( $input['optionname-one'] );
    
        if( isset( $input['optionname-two'] ) )
             $options_array['optionname-two'] = sanitize_text_field( $input['optionname-two'] );
    
        if( isset( $input['optionname-three'] ) )
             $options_array['optionname-three'] = sanitize_text_field( $input['optionname-three'] );
    
        if( isset( $input['optionname-four'] ) )
             $options_array['optionname-four'] = sanitize_text_field( $input['optionname-four'] );
    
        return $options_array;
        }
    

    I threw in a little sanitization for fun, the type of data santization depends on what your real options are, but you should always do some kind of sanitization.

  2. There are two ways:

    1. Pass the tab ID as hidden field with your form and update only the fields which belong to that tab.

    2. Separate the field names for each tab, and combine them in your save callback.

  3. So, let’s assume that you’ve defined a parameter to add each option to its own tab. Here’s how I do it, for example:

    'display_social_icons' => array(
        'name' => 'display_social_icons',
        'title' => __( 'Display Social Icons', 'oenology' ),
        'type' => 'checkbox',
        'description' => __( 'Display social icons in sidebar', 'oenology' ),
        'section' => 'social',
        'tab' => 'general',
        'since' => '1.2',
        'default' => true
    ),
    

    Then, when you output your settings page, you determine the current tab:

    $currenttab = oenology_get_current_tab();
    $settings_section = 'oenology_' . $currenttab . '_tab';
    // Defining $currenttab left out for brevity
    

    …then output each tab separately:

    do_settings_sections( $settings_section );
    

    Then, you use tab-specific submit/reset buttons:

    <?php submit_button( __( 'Save Settings', 'oenology' ), 'primary', 'theme_oenology_options[submit-' . $currenttab . ']', false ); ?>
    <?php submit_button( __( 'Reset Defaults', 'oenology' ), 'secondary', 'theme_oenology_options[reset-' . $currenttab . ']', false ); ?>
    

    Now, inside your validation callback, you can determine which submit/reset tab is selected:

        $tabs = oenology_get_settings_page_tabs();
    
        // Determine what type of submit was input
        $submittype = 'submit';        
        foreach ( $tabs as $tab ) {
                $resetname = 'reset-' . $tab['name'];
                if ( ! empty( $input[$resetname] ) ) {
                        $submittype = 'reset';
                }
        }
    
        // Determine what tab was input
        $submittab = 'varietals';        
        foreach ( $tabs as $tab ) {
                $submitname = 'submit-' . $tab['name'];
                $resetname = 'reset-' . $tab['name'];
                if ( ! empty( $input[$submitname] ) || ! empty($input[$resetname] ) ) {
                        $submittab = $tab['name'];
                }
        }
    

    …and then only act on that subset of options, as you step through your whitelist.

    (I’ve left a lot of code out here, as this is just proof-of-concept. If you would like to see a working example, check here.

Comments are closed.