Settings API – easiest way of validating checkboxes?

I’m using Settings API and I can’t get checkboxes to work, if they’re set to “false” they’re not even being $_POST and that’s the point.

Stephen Harris & Chip Bennett explained it, but I believe their way is not simple enough (especially for me, not PHP-guy at all). Do I REALLY need a few custom PHP functions and custom-named submit buttons for every form just to get this simple thing working?

My settings look much like:

register_setting('XX_theme_settings', 'XX_theme_settings', 'setting_validate' );

add_settings_section('theme_options', 'Theme Options', 'theme_options_generate', 'page1' ); 
add_settings_field( 'XX_Option1', 'Option 1', 'checkbox', 'page1', 'theme_options', 'XX_Option1', $args = array('id' => 'checkbox1', 'type' => 'checkbox') );
add_settings_field( 'XX_Option2', 'Option 2', 'checkbox', 'page1', 'theme_options', 'XX_Option2', $args = array('id' => 'checkbox2', 'type' => 'checkbox') ); 

Then I generate checkboxes with this callback function:

function checkbox($args) {
    $options = get_option('XX_theme_settings');
    echo '<input type="checkbox" name="'. $args['id'] .'" value="true"'; if($options[$args['id']]==true) { echo 'checked="checked'; }; echo '/>';
    echo $args['type'];  
}

Now, everything I need is a good validate callback:

function setting_validate($input) {
   // Firstly it should find all fields of type == "checkbox" 
   // (I'm not sure why should I check which field was just being sent, 
   // is updating all the checkboxes from different sections a big no-no?
   // it is much easier indeed.
   // Now set the ones missing to false/0 and the rest to true/1.
   // Then merge everything like below:

   $options = get_option('XX_theme_settings');
   $merged = array_merge($options, $input);  
   return $merged;  
}

So the question is: how to list all Settings Api checkboxes and then set the right ones to true?

I guess it’d look something like that:

$options = array(
        array(
            'id' => 'checkbox1',
            'type' => 'checkbox',
            'page' => 'page1',
         ),
        array(
            'id' => 'checkbox2',
            'type' => 'checkbox',
            'page' => 'page1',
         ),
);

function setting_validate($input) {
   foreach($options as $option) {
     if($option['type'] == "checkbox") { 
        //set it to 1 if sent
     }
   }

   $options = get_option('XX_theme_settings');
   $merged = array_merge($options, $input);  
   return $merged;  
}

Thanks 🙂

Here’s how Chip Bennett does it exactly, but even with all these great comments I can’t get how it exactly works.

1
1

If the setting in question is $setting, and is a checkbox type, you validate it like so:

<?php
$valid_input[$setting] = ( isset( $input[$setting] ) && true == $input[$setting] ? true : false );
?>

In this case, $input is the form data passed to the validation callback, where $input is an array-of-arrays. The validation method is known as whitelisting, where the current settings array is called (in this case, as $valid_input), and each setting in the settings array is only updated if the value in $input[$setting] passes the validation.

With checkboxes, if the checkbox isn’t set, then the setting itself doesn’t get populated in the $input array. So, the first step to validate it is to determine if it’s been set in $input. The second step is to ensure that the value that is set is true (or 'true', or 'on', or whatever it is to which you set the checkbox value in the settings form). If both those conditions are met, return true; otherwise, return false.

See: it’s quite simple, really. 🙂

Edit

Regarding this:

function setting_validate($input) {
   foreach($options as $option) {
     if($option['type'] == "checkbox") { 
        //set it to 1 if sent
     }
   }

   $options = get_option('XX_theme_settings');
   $merged = array_merge($options, $input);  
   return $merged;  
}

…that’s not exactly the way you want to do it. You want to limit the return value to valid options; that is, you want to return, explicitly, the settings that you know you’ve defined. If, somehow, someone can populate $input with additional data – i.e. malicious code – your method would simply pass that additional data right into the database.

Don’t do an array_merge(). Update each $valid_options[$setting] separately/individually, and only ever return $valid_options.

Here’s the process:

  1. Define the currently valid data:

    $valid_input = get_option( 'XX_theme_settings' );
    
  2. Update the currently valid data with $input only if $input passes your validation/sanitization.
  3. Then return the updated, currently valid data.

If the fields are being generated dynamically, then you should also step through them dynamically, in your validation callback. You have this correct, with foreach( $options as $option ).

Putting it all together might look like this:

<?php
function setting_validate( $input ) {
    // Get current setting values
    $valid_options = get_option( 'XX_theme_options' );
    // Get option parameters array
    // Note: XX_theme_get_option_parameters() function
    // should be defined to return the array you've 
    // defined for $options
    $options = XX_theme_get_option_parameters();
    // Now step through the option parameters,
    // and validate each $input[$option]
    foreach( $options as $option ) {
         // If $option['type] is a checkbox
         if( 'checkbox' == $option['type'] ) { 
             // verify that $input[$option] is set
             // and is set with a valid value;
             // if so, set $valid_input[$option] to 1
             $valid_input[$option] = ( isset( $input[$option] && true == $input[$option] ? 1 : 0 );
         }
     }
     // Return the updated $valid_input array
     return $valid_input;
}
?>

Leave a Comment