Building a Reusable Google Analytics Feature with Flynt

With WordPress, it can be easy to cram all of the custom logic of your theme into one functions.php file. In Flynt, we avoid this by splitting each piece of custom functionality into small, self-contained features. In this tutorial we’ll take a look at how these features work.

We're going to learn how Flynt features work by breaking down the Google Analytics feature, which is one of many features that come with the Flynt Starter Theme.

Why Features are Awesome

Features share the same set of advantages as components. They are small chunks of self-contained, reusable code that can be dropped into any Flynt project. In most cases, a feature is used to add WordPress hooks (actions and filters) that effect the project on a global level.

Functionality that is needed in multiple components can be built as a feature and then used in each component as required. When reusing a component in another Flynt project, this has the advantage that it is also clear which additional features the component needs.

Using the Google Analytics Feature

All features are found in the Features folder of the Flynt Starter Theme. At a minimum, a feature must contain a functions.php file (for the logic behind the feature), and a simple README.md (for basic usage instructions).

Open Features/GoogleAnalytics/README.md to see a basic overview of what this feature does:

Enable the Google Analytics tracking configuration in the WordPress back end. If a valid Google Analytics ID is provided, the tracking code will automatically be added to all pages. If the ID is invalid, a notice is triggered in the WordPress back end.

As with all features, the Google Analytics feature is initialised in the lib/Init.php file in the initTheme function.

<?php

namespace Flynt\Init;

function initTheme()
{
    add_theme_support('flynt-google-analytics');
}

add_action('after_setup_theme', __NAMESPACE__ . '\\initTheme');

Here we convert the name to a kebab-case string and prefix it with flynt. This is then passed to the Wordpress function add_theme_support which loads the functions.php of the feature.

Everything inside the initTheme function is then run on the after_theme_setup WordPress action (this hook is called during each page load, after the theme is initialised).

That’s everything we need to know about the basics of using features! Now we'll dig into the code and see how the Google Analytics feature really works.

Creating the Functions File

Open Features/GoogleAnalytics/functions.php and you’ll find the following code:

<?php

// 1. Define the namespace for this feature
namespace Flynt\Features\GoogleAnalytics;

// 2. Require the GoogleAnalytics class
require_once __DIR__ . '/GoogleAnalytics.php';

// 3. Include other Flynt features and utility functions
use Flynt\Features\GoogleAnalytics\GoogleAnalytics;
use Flynt\Utils\Feature;
use Flynt\Features\Acf\OptionPages;

// 4. Run the 'init' function on the WordPress 'init' action.
add_action('init', 'Flynt\Features\GoogleAnalytics\init');

// 5. Define the code to run when the feature is initialised.
function init()
{
    $googleAnalyticsOptions = OptionPages::get('globalOptions', 'feature', 'GoogleAnalytics');
    if ($googleAnalyticsOptions) {
        new GoogleAnalytics($googleAnalyticsOptions);
    }
}

Here we run through a simple series of steps that can be applied to any feature you create:

  1. Define the component namespace. This is required for all Flynt features. This namespace is always Flynt\Features\<FeatureFolderName>.
  2. Require the GoogleAnalytics class that has been split into a separate file for better organisation.
  3. Include any other Flynt features or classes we want to use (this enables us to build new features on top of existing ones).
  4. Register the init function to run when the WordPress init action fires (after WordPress has finished loading but before any headers are sent).
  5. Define the init function itself. This function grabs the Advanced Custom Fields (ACF) data for the GoogleAnalytics field group and passes the field data to a new instance of the GoogleAnalytics class.

Wait, what field data?

Just as with components, Flynt makes it easy to add ACF fields from within features so that content editors can quickly change settings directly from the back end of WordPress, and developers can easily reuse features between projects.

Using Advanced Custom Fields with Features

Since we're creating a feature, the options we want to present to our user apply to the entire website, not a single page. For this reason, we will output the fields on an ACF Options page.

Options pages display as new, extra admin pages in the sidebar of the WordPress back end. By default, the Flynt Starter Theme supports using two key options pages. These are:

  1. Global Options: for fields that should keep the same value in all languages.
  2. Translatable Options: for fields that need to be translated for each language.

The ACF fields are defined inside each feature folder in fields.json.

Open Features/GoogleAnalytics/fields.json and you will see the following:

{
  "globalOptions": [
    {
      "name": "gaId",
      "label": "Google Analytics ID",
      "type": "text",
      "maxlength": 20,
      "placeholder": "XX-XXXXXXXX-X",
      "instructions": "You can enter 'debug' to activate debug mode. It will only log to console and overwrite all other settings."
    },
    {
      "name": "anonymizeIp",
      "label": "Anonymize IP",
      "type": "true_false",
      "ui": "no"
    },
    {
      "name": "skippedUserRoles",
      "label": "Skipped User Roles",
      "type": "checkbox",
      "choices": {
        "administrator" :"Administrator",
        "editor": "Editor",
        "author": "Author",
        "contributor": "Contributor",
        "subscriber": "Subscriber"
      },
      "toggle": "All",
      "allow_custom": 0,
      "save_custom": 0,
      "layout": "vertical"
    },
    {
      "name": "skippedIps",
      "label": "Skipped IPs",
      "type": "textarea",
      "maxlength": 500,
      "placeholder" : "Separate IP addresses with commas"
    }
  ]
}

For the Google Analytics feature, we want to register all of our fields on the “Global Options” page. This is because we want to use the same tracking options regardless of the front-end site language set by plugins such as WPML.

We do this by registering the fields into the globalOptions object inside fields.json. Flynt automatically detects the fields within this object and registers a new sub-page under the “Global Options” admin page inside WordPress, using the name of the feature as the page name.

  • Flynt now registers a new sub-page under “Global Options” for our Google Analytics feature. (Full size)

Great! We now have ACF fields registered to our Google Analytics feature and displayed in the back end of WordPress.

Let’s revisit Features/GoogleAnalytics/functions.php and remind ourselves how this field data is passed to our feature.

add_action('init', 'Flynt\Features\GoogleAnalytics\init');
function init()
{
    $googleAnalyticsOptions = OptionPages::get('globalOptions', 'feature', 'GoogleAnalytics');
    if ($googleAnalyticsOptions) {
        new GoogleAnalytics($googleAnalyticsOptions);
    }
}

When the WordPress init action fires, we use the OptionPages class of the Flynt Acf feature to get the values of all of the fields we registered above in the globalOptions object inside fields.json.

We then pass this field data to a new instance of the GoogleAnalytics class.

Splitting Feature Functionality into Classes

Now we’ve reached the guts of our feature - the GoogleAnalytics class itself. Open Features/GoogleAnalytics/GoogleAnalytics.php and you will see the core logic behind the Google Analytics feature.

<?php
// 1. Add the namespace for the `GoogleAnalytics` feature
namespace Flynt\Features\GoogleAnalytics;

// 2. Use other Flynt features and utility functions
use Flynt\Features\AdminNotices\AdminNoticeManager;
use Timber\Timber;

// 3. Define the GoogleAnalytics class
class GoogleAnalytics
{
    private $gaId;
    private $anonymizeIp;
    private $skippedUserRoles;
    private $skippedIps;

    public function __construct($options)
    {
        $this->gaId = $options['gaId'];
        $this->anonymizeIp = $options['anonymizeIp'];
        $this->skippedUserRoles = $options['skippedUserRoles'];
        $this->skippedIps = $options['skippedIps'];

        // 4. Skip any IPs set by the user in the `skippedIps` field
        if ($this->skippedIps) {
            $skippedIps = explode(',', $this->skippedIps);
            $this->skippedIps = array_map('trim', $skippedIps);
        }

        // 5. Throw an error if the Google Analytics ID is invalid
        if ($this->gaId && $this->isValidId($this->gaId)) {
            // 6. Call `addScript` on `wp_footer`
            add_action('wp_footer', [$this, 'addScript'], 20, 1);
        } else if ($this->gaId != '' && !isset($_POST['acf'])) {
            $manager = AdminNoticeManager::getInstance();
            $message = ["Invalid Google Analytics Id: {$this->gaId}"];
            $options = [
                'type' => 'error',
                'title' => 'Google Analytics Error',
                'dismissible' => true,
                'filenames' => 'functions.php'
            ];
            $manager->addNotice($message, $options);
        }
    }

    // 7. Pass the Google Analytics feature data to (and render) `script.twig`
    public function addScript()
    {
        $user = wp_get_current_user();
        $trackingEnabled = !(
            $this->gaId === 'debug' // debug mode enabled
            || $this->skippedUserRoles && array_intersect($this->skippedUserRoles, $user->roles) // current user role should be skipped
            || is_array($this->skippedIps) && in_array($_SERVER['REMOTE_ADDR'], $this->skippedIps) // current ip should be skipped
        );
        Timber::render('script.twig', [
            'gaId' => $this->gaId,
            'trackingEnabled' => $trackingEnabled,
            'anonymizeIp' => $this->anonymizeIp
        ]);
    }

    private function isValidId($gaId)
    {
        if ($gaId === 'debug') {
            return true;
        } else {
            return preg_match('/^ua-\d{4,10}-\d{1,4}$/i', (string) $gaId);
        }
    }
}

Again, let's break this down into the key steps of this class:

  1. Add the namespace for the GoogleAnalytics feature (the same one that we defined in Features/GoogleAnalytics/functions.php).
  2. Include any other Flynt features or classes that we want to use — again, just as we did in Features/GoogleAnalytics/functions.php.
  3. Define the GoogleAnalytics class.
  4. Skip any IPs set by the user in the skippedIps field.
  5. Throw an error if the Google Analytics ID is invalid.
  6. Call the addScript function on the WordPress action wp_footer, so that the script outputs before the closing body tag of our website.
  7. Pass the Google Analytics feature data to (and then render) script.twig.

The final piece of our feature is the script.twig, which contains the template for our tracking script. The GoogleAnalytics class passes in all of the field data from our feature and then renders this template in the footer of our website.

<script>
  {% if trackingEnabled %}
    (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
    (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
    m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
    })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
  {% else %}
    function ga() {
      console.log('GoogleAnalytics: ' + [].slice.call(arguments));
    }
  {% endif %}
  var gaId = {{ gaId|json_encode|raw }};
  ga('create',gaId,'auto');ga('send','pageview');
  {% if anonymizeIp == 1 %}
    ga('set', 'anonymizeIp', true);
  {% endif %}
</script>

If the user activates "debug" mode, the Google Analytics script will not be output. Instead, the details will be logged to the JavaScript console.

Wrapping Up

We are done! We now have a simple, reusable feature that can be dropped into any Flynt project. At the same time, we've provided our content editors with a new page in the WordPress back end with all of the content fields they need for quickly setting up their Google Analytics tracking.

Features in Flynt are a powerful tool. While providing a common structure for your additional functionality, they also leave great freedom to do whatever you want with them. We are excited to see with what kind of awesome features you will come up. Let the hacking begin!

Download the Google Analytics Feature

Next Steps

  1. See an overview of all features that ship with the Flynt Starter Theme.
  2. Learn more about creating your own feature and how to hook into existing features.
  3. Use the Flynt Yeoman Generator to quickly and easily scaffold new features.
  4. Discover more advantages of component driven development.
Michael Carruthers

Michael is a British front end web developer working at bleech in Berlin, with a passion for creating user-friendly web experiences (and drinking tea, of course).