Search code examples
phpwordpresswordpress-hook

Custom filter not affecting gtag script position in WordPress footer


I am using a custom filter to place the gtag script in footer if it is applied in functions.php. The filter code is:

if (apply_filters('myplugin_ga_script_position', $position = 'footer')) {
    add_action('wp_footer', array($this, 'myplugin_add_analytics_code'));
} else {
    add_action('wp_head', array($this, 'myplugin_add_analytics_code'));
}

But when I do not use the following filter in functions.php even then the gtag script is in footer:

add_filter( 'myplugin_ga_script_position', function( $position ) {
    return 'footer';
});

Any fixes?


Solution

  • You want the condition to handle the result of the filter call:

    if('footer' === apply_filters('myplugin_ga_script_position', 'footer')){
        add_action('wp_footer', array($this, 'myplugin_add_analytics_code'));
    }else{
        add_action('wp_head', array($this, 'myplugin_add_analytics_code'));
    }
    

    EDIT

    After further discussion, depending on how both hooks are implemented a race condition could occur. My updated recommendation is to have two hooks which further register the these hooks.

    
    // This code is the theme's main preference for where the hook
    // should be used. We're using the `init` hook which is a
    // common hook for this.
    add_action(
        'init',
        static function () {
            if ('footer' === apply_filters('myplugin_ga_script_position', 'footer')) {
                add_action('wp_footer', array($this, 'myplugin_add_analytics_code'));
            } else {
                add_action('wp_head', array($this, 'myplugin_add_analytics_code'));
            }
        }
    );
    
    // This is where the theme overrides the previous hook. We're
    // using an earlier hook so that it gets registered before the
    // other hook gets invoked.
    add_action(
        'after_setup_theme',
        static function () {
            add_filter(
                'myplugin_ga_script_position',
                function ($position) {
                    return 'header';
                }
            );
        }
    );
    

    EDIT 2

    You absolutely can have custom-named hooks. This is one of WordPress's main ways to extend the system, and it is a great way for plugins to offer defaults for users while still giving advanced users the ability to override them.

    But it is important to know, understand and document the hooks and the order that they are called in.

    I don't know your specific code, but I think I have a pretty good general ideal of what your setup is. Let's take this code and assume it is written in the bootup code for your plugin:

    if (apply_filters('myplugin_ga_script_position', $position = 'footer')) {
        add_action('wp_footer', array($this, 'myplugin_add_analytics_code'));
    } else {
        add_action('wp_head', array($this, 'myplugin_add_analytics_code'));
    }
    

    Actually, I'm going to go one step further and simply it by just doing this:

    $script_position = apply_filters('myplugin_ga_script_position', $position = 'footer');
    

    Imagine that in your index.php (or whatever) of your plugin. Plugins load before themes, so $script_position is already set by the time your theme loads. The apply_filters code was called, however because your theme wasn't loaded yet, it didn't have a chance to register a callback to that hook.

    This is a chicken and egg problem. A plugin cannot ask for a theme's input on something until the theme has started loaded.

    The fix is what's in the first edit. Your plugin says "when the init hook runs, ask anyone if they want to change the default". Your theme then needs to register a response to this, and you must register this response before init is called.

    Also, I'm talking the common pattern of plugin doing one thing and a theme doing another. But the same is true for a plugin customizing its own logic. I would recommend, however, that your plugin doesn't use its own hooks except for exception situations. Personally I would write the plugin code as the following, and only themes (or other plugins) would implement your filter. There's generally no reason for your code (in a single plugin or theme) to implement your filters. That's what your code's logic is for in the first place.

    add_action(
        'init',
        static function () {
    
            // Get the value from the database
            $script_position = get_option('script_position', 'footer');
    
            if ('footer' === apply_filters('myplugin_ga_script_position', $script_position)) {
                add_action('wp_footer', array($this, 'myplugin_add_analytics_code'));
            }
    
            if ('header' === apply_filters('myplugin_ga_script_position', $script_position)) {
                add_action('wp_head', array($this, 'myplugin_add_analytics_code'));
            }
        }
    );