1

Question

Is it possible to give a custom capability the ability to manage the options of specific plugins?

Background

I am developing a suite of plugins, a main one with some additional modules. The main one adds a custom role and capability so that this role is an editor plus can manage and edit the rest of the plugins in the suite.

// In main plugin, on activation hook
$permissions = get_role( 'editor' );
$permissions = $permissions->capabilities;
$permissions[ 'custom_capability' ] = 1;

add_role( 'custom_role', __( 'Custom Role' ), $permissions );

To give access to the plugin pages, I've changed the capability when registering the options page of the plugin components to custom_capability, which has worked to make the plugins visible to those with the Custom Role role.

add_submenu_page( 'custom-plugins', 'Custom Plugins Addon', 'Addon', 'custom_capability', 'custom-plugins-addon', 'add_custom_addon_options_page' );

However, when this role tries to update the plugin settings, it is denied access.

I've tried using the option_page_capability filter, but haven't had any luck.

// In additional modules, when registering options page
function give_custom_role_access() {
    return 'custom_capability';
}
apply_filters( 'option_page_capability_custom_addon_options_page', 'give_custom_role_access' ); 

Apparently there is some confusion about what should go in {$option_page} when using the option_page_capability_{$option_page} filter, so I have tried both the option page slug and the settings group slug, but neither have worked.

0

If you use the Settings API, that $option_page is the value of a hidden <input> field generated by settings_fields(). For example:

<input type="hidden" name="option_page" value="{Option group name}">

So if you have settings_fields( 'my-group' ) in your code, then the $option_page would be my-group. See the (as of writing, 5th) note here.

Code sample: (tried and tested working)

// Register custom option group.
add_action( 'admin_init', function(){
    register_setting( 'my-group', 'test_option' );
} );

// Allows custom role to save options in the above group (my-group).
add_filter( 'option_page_capability_my-group', function( $capability ){
    return 'custom_capability';
} );

// Render the "Custom Plugins Addon" admin page.
function add_custom_addon_options_page() {
    ?>
        <div class="wrap">
            <h1>Custom Plugins Addon</h1>
            <?php settings_errors(); ?>
            <form method="post" action="options.php">
                <p>
                    <label>Text field:</label>
                    <input name="test_option" value="<?php echo esc_attr( get_option( 'test_option' ) ); ?>">
                </p>
                <?php settings_fields( 'my-group' ); ?>
                <?php submit_button(); ?>
            </form>
        </div>
    <?php
}

Full code here, but note that in the example, I intentionally did not use add_settings_section() and add_settings_field().

UPDATE

(Sorry, I actually didn't really notice this part in the question)

so I have tried both the option page slug and the settings group slug, but neither have worked

The option page slug is not supposed to work (assuming it is different from the settings group slug); however, the settings group slug (or option group name) should work just as in the example I've given.

So maybe you're missing the settings_fields( 'my-group' ) in your code?..

Try the example plugin and see where/what you may have done wrong.. if any.

UPDATE #2

(Revised on Mar 18 2019 UTC)

Figured it out... I was using apply_filters() instead of add_filter()

I'm glad you figured it out. And that's a good thing to learn from.. :)

Could you elaborate on when to use apply_filters() vs. add_filter()?

Not a complete elaboration, but I hope these help you.

  1. If you have a function or code which returns something, and you want to allow plugins or custom code to filter/change that something output, you can use apply_filters() which registers a filter hook and applies/runs callback functions hooked to that filter:

    // This function has a hook for a filter named my_filter.
    function my_function() {
        // Allow plugins or custom code to change the output.
        return apply_filters( 'my_filter', 'default output' );
    }
    
    // This function does **not** have any hooks.
    function my_function2() {
        return 'something good..';
    }
    
  2. When the output of a function or code is not what you want/prefer — and that the function/code has a filter hook, you can use add_filter() (which registers a filter callback) to customize the output like so: (the function code could be combined, but I separated them so that you know you can hook as many callbacks as you want to a filter)

    function filter_my_function_output( $output ) {
        return 'custom output';
    }
    add_filter( 'my_filter', 'filter_my_function_output' );
    
    function filter_my_function_output2( $output ) {
        return is_user_logged_in() ? 'You are logged-in...' : $output;
    }
    add_filter( 'my_filter', 'filter_my_function_output2' );
    

    So the final output could be the one returned by filter_my_function_output() or filter_my_function_output2(). Or vice-versa, depending on the callback priority:

    // no priority set explicitly; so defaults to 10
    add_filter( 'my_filter', 'filter_my_function_output' ); // priority = 10
    
    // using custom priority, which is 1; so this runs before the above
    add_filter( 'my_filter', 'filter_my_function_output', 1 ); // priority = 1
    

So in summary to the points #1 and #2 above:

  • my_filter is the name of a filter

  • my_function() contains a hook/place where the filter my_filter is being applied

  • filter_my_function_output() and filter_my_function_output2() are callback functions which run when the my_filter filter is applied

  • Figured it out... I was using apply_filters() instead of add_filters() because on the page for option_page_capability it is shown with apply_filters(). Could you elaborate on when to use apply_filters() vs. add_filters()? – bicarlsen Mar 17 at 18:31
  • I have added an update.. I hope it helps. – Sally CJ Mar 17 at 20:10
  • Great explanation, thanks :) – bicarlsen Mar 18 at 6:34
  • You're welcome! And btw, I +1 the question because it's quite useful (to me), and once you can, I hope you'd upvote my answer.. :) – Sally CJ Mar 18 at 9:05
  • I know! I already tried, to my great disappointment :P – bicarlsen Mar 19 at 10:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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