Circumventing ACF’s get_field issue in wordpress functions.php

Technical topic ahoy!

But seriously, this issue has been bugging me all day. I’m using ACF Pro so I’m not sure if this issue also applies to the free version or not, but hey, I’m gonna lay it out anyway.

 

THE REQUIREMENT

I’d created a simple little checkbox in the options page of a WordPress installation so that, if checked as True, I would be able to display a second custom options page in the Dashboard menu. It was for a multisite installation where some sub-sites needed a special options page and some did not.

So far so simple? Not quite. Turns out that trying to use Advanced Custom Field’s native function to retrieve field values, ‘get_field()‘, wasn’t going to work in functions.php. At least, not in the way I wanted.

 

THE INITIAL SETUP

To add the options page, I call the following function inside my init class:

public static function add_options_page()
    {
        if( function_exists('acf_add_my_custom_options_page') ) {
            acf_add_options_page(array(
                'page_title' => 'Custom Options',
                'menu_title' => 'Custom Options',
                'menu_slug' => 'custom-options',
                'capability' => 'edit_posts',
                'redirect' => false
            ));
        }
    }

But, to ensure it only comes up for subsites that have the checkbox selected, I have to do an extra check. Initially, I had something that seems legit and like it should work:

if ( get_field('display_custom_options_page', 'options') == 1 ) {
    self::add_options_page();
    //we use self:: to reference the init class it's within
}

Simple. If the field equals true, call the function that adds the options page.

But this didn’t work. Or rather, it did display the new options page, however the checkbox on the first options page disappeared from view every time get_field was used in functions.php.

So wait, that means every time I decide a subsite needs to have this extra options page, my opportunity to reverse the decision is taken away? Bad user design, no thank you!

 

THE REASON

After some hunting around online, it turns out that ACF’s get_field() function doesn’t play too nicely with WordPress’s init() function if you want to use it before admin headers are set. It has to be called after init().

I could add it later on, sure, but while I could now retrieve the field, going on to add the custom options page still needed to be done in init.

 

THE FIRST (MESSY) SOLUTION

At first I just thought, well, can I just include the custom options page as normal, without the checkbox conditional, and then hide it after initialisation?

So I had:

class MyClass {
    public static function init()
    {
        self::add_options_page();
    }

    public static function add_options_page()
    {
        if( function_exists('acf_add_my_custom_options_page') ) {
            acf_add_options_page(array(
                'page_title' => 'Custom Options',
                'menu_title' => 'Custom Options',
                'menu_slug' => 'custom-options',
                'capability' => 'edit_posts',
                'redirect' => false
            ));
        }
    }
}
MyClass::init();

$is_custom = get_field('display_custom_options_page', 'options');

if ( $is_custom != 1 ) { // if is_custom ISN'T checked
    // a quick and cute function to remove the specified menu page
    function remove_menus(){
        remove_menu_page( 'custom-options.php' );
    }
    add_action( 'admin_menu', 'remove_menus', 999 );
}

This works. BUT this is roundabout, first adding the page then employing the get_field after to cross-check and then remove the page again.

 

THE FINAL SOLUTION

At this point I thought why not just talk to the database myself instead of using ACF’s functions? Everything ACF creates for options pages gets saved in the wp_options table in wordpress databases, so I simply ended up using the $wpdb class to write an SQL request, and grab the display_custom_options_page field from this, all within init() itself.

Here’s the final code:

class MyClass {

    public static function init()
    {
        self::add_options_page();
    }

    public static function add_options_page()
    {
        // retrieves ocustom options field
        // uses $wpdb sql query rather than ACF get_field

        //set global wpdb, get all from options table
        global $wpdb;
        $results = $wpdb->get_results( "SELECT * FROM $wpdb->options" );

        foreach ($results as $option) {
            // if option has correct name and value, add custom option page
            if ($option->option_name == 'display_custom_options_page' && $option->option_value == 1) {
                if( function_exists('acf_add_options_page') ) {
                    acf_add_options_page(array(
                        'page_title' => 'Custom Options',
                        'menu_title' => 'Custom Options',
                        'menu_slug' => 'custom-options',
                        'capability' => 'edit_posts',
                        'redirect' => false
                    ));
                }
            }
        }

    }
}
MyClass::init();

This is a very specific case – most custom options fields aren’t created as conditionals that govern the inclusion of other options pages. For the vast majority, it makes more sense to just use get_field(). But for those times when something needs to be initialised earlier, just sql it.

Leave a Reply

Your email address will not be published. Required fields are marked *