A better way to exclude users from Tracking in Concrete5

How to include a tracking code for non-admins only in Concrete5

I recently stumbled across some bad advice for only displaying tracking codes on a c5 site when the user is not an Admin. The article is old, but it still shows up high in the SERPs when searching for information on the subject. I feel the need to refute it.  In the article, the author suggests overriding concrete5's default behavior by hard-coding the logic into your template.  This is a bad idea for several reasons:

  1. Concrete5 is specifically designed to allow easily overriding default logic for cases exactly like this.
  2. Putting tracking logic in your template files does not properly address separation of concerns.
  3. In the long term, having non-template logic within your templates will just create more work for you when it comes time to use a new template.  The DRY philosophy evolved with good reason.

So what's the "better" way to do it?

Note: keep in mind this isn't the best possible solution. The best solution would be to extend concrete5 and give groups an attribute that allows you to disable tracking for any collection of groups. I'm not going to do that here as it's beyond the scope and intent of this guide.  However, this method can be adapted for a variety of purposes and any developer that uses c5 should learn the concepts presented.

Override the core files that insert the tracking codes.

In legacy (<5.7) versions of c5, we did file overrides by copying the originals from the /concrete/ folder to the webroot. In modern versions (>=5.7) we do this by copying files from the /concrete/ directory to the /application/ directory. In order to modify the tracking behavior we'll need to do this for two files:

Copy concrete/elements/footer_required.php and /concrete/elements/header_required.php to the application/elements/ directory. We will be editing both of these files. Then create a new file /application/elements/tracking.php.

Open these three new files in your favorite editor.  The following code is from version 5.7.3.1.

/application/elements/footer_required.php

You're going to want to find the following lines of code.  In 5.7.3.1, You'll find it near line 9.

$_trackingCodePosition = Config::get('concrete.seo.tracking.code_position');
if (empty($disableTrackingCode) && (empty($_trackingCodePosition) || $_trackingCodePosition === 'bottom')) {
    echo Config::get('concrete.seo.tracking.code');
}

Replace "echo Config::get('concrete.seo.tracking.code');" with "Loader::element('tracking');".  This will allow you to put your new logic within the file tracking.php.  It should now look like this:

$_trackingCodePosition = Config::get('concrete.seo.tracking.code_position');
if (empty($disableTrackingCode) && (empty($_trackingCodePosition) || $_trackingCodePosition === 'bottom')) {
    Loader::element('tracking'); // custom logic for including tracking code
}

/application/elements/header_required.php

Repeat the same process with header_required.php. In version 5.7.3.1, you'll find the code near line 173. Simply use "Loader::element('tracking');" in place of the identical echo and you'll be ready to edit the third file.

/application/elements/tracking.php

By putting our tracking logic in a single place, it prevents us from having to edit two separate files when changes become necessary. Here's the code for my tracking.php. I'll explain each component afterword.

<?php
/*##############################################################################
#
# custom element to block tracking for super users and members of admin
# group (group 3).  Use this code for anything you like - attribution is not
# required. (Renic Gunderson 2015)
# 
##############################################################################*/
defined('C5_EXECUTE') or die("Access Denied.");

$u = new User();
if(
    $u->superUser != 1
    && in_array(3,$u->uGroups,true) === false
) {
    echo Config::get('concrete.seo.tracking.code');
}

Code Sections of Note

defined()...

The "defined or die" line is commonly found throughout concrete5's source code.  It prevents the script from running when it is called without using c5's dispatcher.  It's not technically needed here, since the script will terminate with an error if called directly; however, I've included it out of habit and recommend using it in general.

$u = new User()...

the User() object is instantiated to gain access to a variety of variables associated to the page visitor.  if we were to use print_r() to see the details for the default superuser, we'd see the following information:

Concrete\Core\User\User Object
(
    [uID] => 1
    [uName] => admin
    [uGroups] => Array
        (
            [2] => 2
            [1] => 1
        )

    [superUser] => 1
    [uTimezone] => 
    [uDefaultLanguage:protected] => 
    [accessEntities:protected] => Array
        (
        )

    [hasher:protected] => 
    [uLastPasswordChange:protected] => 
    [error] => 
)

User::superUser ...

$u->superUser will be equal to one if the current user is *the* admin; however, we also have to deal with other user accounts we may have placed into an admin group, so we place a an additional evaluation in our if statement.

&& in_array(...

We also want to make sure that the user doesn't belong to the Admin group.  By default, that group has a gID (found in mysql Groups.gID) of 3.  if you've shuffled your groups around, or created a different admin group from the default, you will want to look up the appropriate value for group ID.  If we find the admin group's gID within the array $u->uGroups, then we know we don't want to output the tracking codes for that user.

Conclusion

I hope you've found this guide straight forward and useful.  This method has the added bonus of being adaptable - you can now easily code for advanced tracking conditions in a single file without having to bypass c5's built in functionality.