Conditionally disable Pages Children in WordPress

This post is more than 12 years old.

Whilst WordPress might be a very easy tool for managing website content, and is making great inroads into the CMS market, it isn’t very convenient for editing large numbers of pages on a very large website. That’s where the Pages Children plugin comes to the rescue, breaking the page administration lists down into a nice, manageable hierarchy. But it doesn’t play nice with another great WordPress plugin, Role Scoper, which is also likely to be installed on large websites. Hooks to the rescue!

The problem, in brief, is that we want to use Pages Children to cut through the mass of pages on a website, but limit the pages some users can edit. However, if a user has access to edit only subpages, they’ll never see their pages to be able to edit them because Pages Children won’t list the parent pages. This all makes sense really; it just isn’t very helpful.

The simple answer (or at least, SWMBO’s simple answer this morning over coffee) is to disable Pages Children when a limited-access user is browsing pages to edit. Really, only users with full page_edit access need the page hierarchy of Pages Children (on the website we’re working on today, at least!) so we can dispense with it for limited-access users, thus letting them see their pages.

Looking at the source code for Pages Children, there are a few hooks that it creates for hooking into the WordPress admin and doing its thing. We just need to remove those hooks at the appropriate moment. WordPress even supplies a function for that, remove_filter. Just one little wrinkle: it wants a function name, and Pages Children, being nicely OO, is hooked in via object method callbacks.

add_action( 'init',             array(&$this, 'init' ));
add_action( 'admin_init',       array(&$this, 'admin_init' ));
add_action( 'admin_head',       array(&$this, 'admin_head' ));

add_filter( 'request',          array(&$this, 'request_filter' ), 999);
add_filter( 'get_terms_args',   array($this, 'get_terms_args_filter'), 10, 2 );

However, we can manage that. In PHP, callbacks are either strings, representing function names, or arrays, with the first element being the class name or object reference and the second element being the method name. What we need to do is find the hooks that are object callbacks on the PageChildren class, and remove them. There’s no WordPress API function for that, but it isn’t hard to write one.

So, here it is: how to conditionally disable Pages Children for users that don’t have page_edit access.

add_action('plugins_loaded', 'actionPluginsLoaded');

/**
* handle the plugins_loaded action to remove PagesChildren hooks when required
*/
function actionPluginsLoaded() {
    // detect Pages Children and stop it running when not an admin or editor
    if (class_exists('PagesChildren') && !current_user_can('edit_pages')) {
        foreach (array('init', 'admin_init', 'admin_head', 'request', 'get_terms_args') as $filterName) {
            removeObjectFilters($filterName, 'PagesChildren');
        }
    }
}

/**
* remove filters that are methods of an object of some class
* @param string $filterName name of action or filter hook
* @param string $className name of class for object method
*/
function removeObjectFilters($filterName, $className) {
    global $wp_filter;

    // update: must take a variable to iterate over array of filters,
    // else a subtle reference bug messes up the original array!
    $filters = $wp_filter[$filterName];

    foreach ($filters as $priority => $hooks) {
        foreach ($hooks as $idx => $filter) {
            // check for function being a method on a $className object
            if (is_array($filter['function']) && is_a($filter['function'][0], $className)) {
                remove_filter($filterName, $idx, $priority);
                break;
            }
        }
    }
}

Job is done, by hook or by crook.