4

The Settings API is a convenient way to add administrative options. Tutorials on the settings API hook register_setting to admin_init. But what if you also want to programmatically change the same options outside of the Settings API?

For example, use the Settings API to maintain a base value for option X. In addition, each new post automatically increments option X via a direct update_option function call. Unfortunately, if option X is registered, update_option triggers a call to the validation function associated with the registered setting. The validation fails because the update_option argument isn't passed because I screw up the different validation required from the different sources of options change.

Where should register_setting for option X be hooked to allow option X also to be updated outside of the Settings API? Alternatively, where should unregister_setting for option X be hooked to allow the direct update_option call to work without the Settings API sanitization/validation function call?

Update: t31os below helpfully provided some illustrative code. Here is a small addition to his code:

    function __construct() {
        add_action( 'admin_init', array( $this, 'admin_init' ) );
        add_action( 'admin_menu', array( $this, 'admin_menu' ) );
        add_action('add_meta_boxes', array( &$this, 'add_regtest_box' )); //added hook
        }   

//added functions
    function add_regtest_box() {
            global $post;
            add_meta_box( 'regtest_box', 'Registered Setting Test', array( &$this, 'regtest_box_contents'), 'post' , 'side' , 'high' );
        }   
    function regtest_box_contents() {
            update_option( 'test-option', array('fieldone'=>-11,'fieldtwo'=>8) );
            $new_vals = get_option( 'test-option' );
            print_r( $new_vals );
        }

If you "add new post" from the dashboard, you'll notice that the metabox on the top right shows fieldone with a value of 11, not -11. That's because the update_option function call in regtest_box_contents is putting the option values through the sanitizing/validating function that was registered (register_setting) to the option. In this case, that's t31os's update_option function, which applies absint() to the option value passed through. The question is how to avoid that function call, e.g. just have the option updated to -11.

3 Answers 3

0

Now i've thought about it(Mark is me, i use another account when i'm at other locations) all registering a setting does is prepare options.php to accept your incoming form data when a valid nonce is present and also provides a means to hook a sanitization method to the submission of that data. It's really a convenience thing for plugins, and such..

From the front end of the site it should be perfectly fine to just call update_option and be done with it. It doesn't need to registered on the front end, because you're not submitting form data to options.php as you would be for an administration page.

I tried to replicate your problem, but i've been unable to, here's my ugly test code to create a simple registered page that can save options.

class RegisteredSettingsTest {
    private $page;
    private $name = 'test-option';
    private $options;
    function __construct() {
        add_action( 'admin_init', array( $this, 'admin_init' ) );
        add_action( 'admin_menu', array( $this, 'admin_menu' ) );
    }
    function admin_init() {
        register_setting( 'setting_ref', $this->name, array( $this, 'update_option' ) );
        $this->options = get_option( $this->name );
    }
    function admin_menu() {
        $this->page = add_dashboard_page( 'Test','Test','manage_options','translatable_demo', array( &$this , 'load' ) );
    }
    function update_option( $data ) {
        $clean = array_map( 'absint', $data );
        unset($data);
        return $clean;
    }
    function load() {
        ?>
        <div class="wrap">
            <?php screen_icon('index'); ?>
            <h2>Test</h2>
            <form action="options.php" method="post">
                <?php settings_fields( 'setting_ref' ); ?>
                <p><label for="<?php $this->field_name( 'fieldone') ?>">Field one <input value="<?php $this->get_option( 'fieldone') ?>" name="<?php $this->field_name( 'fieldone') ?>" type="text" /></label></p>
                <p><label for="<?php $this->field_name( 'fieldtwo') ?>">Field two <input value="<?php $this->get_option( 'fieldtwo') ?>" name="<?php $this->field_name( 'fieldtwo') ?>" type="text" /></label></p>
                <p class="submit"><input type="submit" name="Submit" class="button-secondary action" value="Save" /></p>
            </form>
            <!-- Output options data, so we can see how it currently looks -->
            <pre><?php print_r( $this->options ) ?></pre>
        </div>
        <?php
    }
    function field_name( $name, $echo = true ) {
        $name = "{$this->name}[$name]";
        if( $echo )
            echo $name;
        else
            return $name;
    }
    function get_option( $name, $echo = true ) {
        $val = '';
        if( is_array( $this->options ) && isset( $this->options[$name] ) )
            $val = $this->options[$name];

        if( $echo )
            echo $val;
        else
            return $val;
    }
}
$r = new RegisteredSettingsTest;

I loaded the page up, entered the values 1 and 2 into the fields and hit Save(which of course works fine and i see the values 1 and 2). I'm using numeric values, because it's just a simple test.

The most simple front end test i could come up with was to just call update_option inside my single.php(though i could have used any other template file).

update_option( 'test-option', array('fieldone'=>3,'fieldtwo'=>4) );

If there were an issue, i should still see the values 1 and 2 when i load up my test admin page. Unfortunately that's not the case, the new values, 3 and 4 are shown.

Can you post up the code that you're using to update the option from the front end? If it's inside a callback, post the whole function, plus the action/filter it's hooked onto, please.. :)

UPDATE:

Remove the sanitization callback before updating the option.

remove_all_filters( 'sanitize_option_test-option', 10 );

And your values won't get butchered by the callback function any longer, because it'll be unhooked.. :)

5
  • Sorry for not responding sooner. Got bogged down in some other stuff. Thanks for moving the ball forward with some code. I've updated the question with additional code that, in conjunction with your code, makes clear the question.
    – BigToe
    Feb 22, 2011 at 17:26
  • thanks again for your contribution. I've updated my question to incorporate your code.
    – BigToe
    Feb 22, 2011 at 17:54
  • Updated my answer to address the problem(i know it works to, i tested it alongside the new sample code you provided)... :)
    – t31os
    Feb 22, 2011 at 18:01
  • Great unhook! Nary a mention of it in the codex. Thanks a lot.
    – BigToe
    Feb 22, 2011 at 18:33
  • You're welcome and for ref: codex.wordpress.org/Function_Reference/… ... :)
    – t31os
    Feb 22, 2011 at 18:38
0

Could you not just fire your action on init instead?

The requirement to update options is still the same(only admins can manage_options), so it should work in theory, have you tried it, was their a problem?

Not at my main PC right now, else i'd give it a shot myself to see if it works.

What did you mean here..

The validation fails because the update_option argument isn't passed.

I'd have asked in a comment, but i've not hit the rep requirement on this account yet.

10
  • Here's the form of register_setting: register_setting( $option_group, $option_name, $sanitize_callback ); The $sanitize_callback function gets the option and returns the sanitized (validated) value. The update_option call on $option_name is calling $sanitize_callback with a null value. Very strange.
    – BigToe
    Feb 18, 2011 at 21:53
  • Ok, i follow you(i think), but are you saying the sanitization callback is failing to fire when it's a non-admin page? In theory it should work in either scenario as long as the current user meets capability requirements(manage_options).
    – t31os
    Feb 18, 2011 at 22:00
  • (Rethink) : Were you saying the callback is not receiving the expected data when it fires for non-admin pages?
    – t31os
    Feb 18, 2011 at 22:01
  • The callback is not receiving the expected data when it fires for non-admin pages.
    – BigToe
    Feb 18, 2011 at 22:03
  • Should the sanitization callback be firing from a direct update_option call?
    – BigToe
    Feb 18, 2011 at 22:05
0

On a sidenote...wouldn't it be better to count the relevant posts and then add the base value from your options to get the total? (If that's really what you need it for.) Unless you want to build a settings-interface available from the front-end, this sounds like it points that you should do it in a different way. I think it might be confusing to the user if their 'settings' change on their own?

2
  • good point, but I think there are some valid use cases. My specific application has a date-time setting and auto incrementing of that setting for serial posts. The WP publication time is somewhat similar, in that it automatically rolls forward with the current time, but the editor can also change it.
    – BigToe
    Feb 22, 2011 at 17:59
  • @BigToe: So if i get you right, you only use the option to store a temporary date value that you then always update to the last one + a certain diff. Which sounds like you can achieve the same effect by fetching the most recent 'serial post' reading the date and adding the diff to that...whenever you create your next serial post (instead of reading the option value).
    – wyrfel
    Feb 22, 2011 at 18:23

Your Answer

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

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