Hi,

We have a Drupal site spanning quite a few domains (http://www.vetspace.org.nz). The content on each site is different, but it's the same Drupal instance powering them.

Your module doesn't seem to support clicking across to other domains while preserving the GA session (http://www.google.com/support/analytics/bin/answer.py?hl=en&answer=55503), so I've provided a patch against the latest development copy of the Drupal 5 version. It should be trivial to port it forward to D6 too if you want.

It works by providing an optional config item to specify a list of domains that are still local, and if this is set, it turns off cross-domain cookie setting and captures link click requests and passed them to the pageTracker object (via _link()) for handling.

This is also closely related to #132650: Ability to track multiple domains that resolve to the same site, a D6 version of this issue that you marked wontfix. I believe that issue was going in a slightly different direction though.

My patch also fixes a bug on your current development copy that prevents the link event handler from being attached, as jQuery.ready() passes an instance of jQuery as the 'context' argument to gaTrackerAttach, which gets used as the context, and doesn't work.

Feedback on how I've accomplished this is welcome.

Thanks

Comments

hass’s picture

Status: Needs review » Needs work

I've already thought about this functionality, but I think it should have no configuration options. I thought this should work as google analytics themself does. If someone adds _setDomainName() to the custom JS field we should make sure this works automatically. If this is the case I see no real need to add another UI config option.

+      $script .= 'pageTracker._setDomainName("none");';
+      $script .= 'pageTracker._setAllowLinker(true);';

Also don't forget that we could have a wildcard domain like:

pageTracker._setDomainName(".example.com");

This seems to be wrong:

 if (Drupal.jsEnabled) {
-  $(document).ready(gaTrackerAttach);
+  $(document).ready(function() {
+    gaTrackerAttach(null);
+  });
 }
neilnz’s picture

The javascript still somehow needs to know a list of domains that are local to call _link() the links for. That's all the UI does. This could also be implemented by offering up a hook in the module so another module can provide a domain list, eg. using the domain module, which is already integrated with singlesignon.

And yes, if you're just using subdomains, something like pageTracker._setDomainName(".example.com") would work nicely, but in my case the domains are .com's, .org's, .org.nz's etc, for different organisations. They just happen to be served up by a single Drupal instance. This patch would also be applicable if you had another site that wasn't powered by Drupal (or was powered by a different Drupal), as you could still list the domains in the settings and it would link out to those other site with the correct tracking code.

I would've just used the custom javascript box if the requirements were as simple as those two lines, but it needs proper support in googleanalytics.js to call _link(), which is the whole point of this patch.

As for patching the ready() function, I'd welcome suggestions on a better way to do it. By passing null to gaTrackerAttach, it defaults to using document, which is correct for the initial load. If you just let jQuery.ready() call it, it gets passed a function reference for jQuery as the context, and no events get bound at all.

hass’s picture

Your code hardcodes the _setDomainName() value to "none". This is incorrect. It needs to be dynamic to be general for the module code. For example, if you add pageTracker._setDomainName("none"); to before code snippet, the googleanalytics.js can see this variables and do appropriate actions, Maybe one action is to add pageTracker._setAllowLinker(true); dynamically - not sure. Otherwise if someone configures pageTracker._setDomainName(".example.com"); the JS code in googleanalytics.js may do other actions, but this needs to be dynamic from what the user configures in "before" snippet textarea. Hope this makes the requirements clear.

The ready() function and how it attaches events is taken from other modules and all looking the same like today. Take a look to project, and others. Aside it works for me - not sure what should be wrong here.

neilnz’s picture

Sure, I agree it makes good sense to set the automatic code before the manual overrides, because someone may want a hybrid approach (subdomains and external domains). I can make that change. It's possible I shouldn't mess with the default 'auto' value at all, since I don't think it would make a different to linking out to other domains. I just put it there because that's what the GA help page said, and because it made sense for my situation of entirely external domains.

The _setAllowLinker(true) is required to accept incoming tracker codes from other domains, so is a bit more important to set. It should possibly be manually configurable on its own though, as there may be cases where a site should accept incoming tracker codes from other domains but will not be making any outgoing tracked links. How about we make it a checkbox "Allow incoming tracking sessions from other domains" and make it automatically select if multiple domains are somehow specified?

hass’s picture

If possible I don't like to add more configurable options to the UI... if not possible without UI - ok, but every UI change adds new strings and needs to be translated and supported and let me say - what you are doing seems not very common for the masses, but otherwise it cannot be wrong to have it implemented and I also need this in near future! For such special requirements we have build the "before" code snippet. Only to note - over 15.000 known installations and you are the first who request this functionality... :-)

I would be happy if this feature would be implemented such easy to configure like google allows you to configure this functionality - with only two static configuration lines. All other stuff should automatically be done - that's great and I think we can also do this based on the two config lines.

neilnz’s picture

I understand that... It is an advanced feature, but I thought that was what the "advanced" section of the UI was for ;)

If you like, this functionality could be provided by an additional "contrib" module (either distributed with or without the googleanalytics module). All that would be required is maybe a hook to add more GA javascript, although that could also be done simply using drupal_add_js() and ensuring the contrib module is weighted after googleanalytics.module (since it requires the pageTracker object to exist)... Using this approach a second javascript file would need to be included that also listens for link clicks and does the _link() call where appropriate.

If it was a separate module, it may not need to have a UI, since installing it would activate it, and it could then use a hook to the domains module to discover the list of domains.

I'm not the first person to request the exact functionality my patch performs though - ArjanLikesDrupal was asking for it over on #132650: Ability to track multiple domains that resolve to the same site just last month. So that's at least 2 out of 15,000 people that want it - 0.01%! I believe that if you have a feature, people will use it, if you don't, not many would bother to ask for it, especially if they can't write patches.

hass’s picture

No, I wouldn't add it into an extra module. That would be totally overkill. Try to integrate it as written above. Make the integration as easy as possible and reuse google analytics api configuration options. I'm not refusing your request... I'd only like to integrate it such EASY as google does this themself. That's all what we should do here. Don't overcomplicate things for users... no idea why you need an UI if google don't need this.

neilnz’s picture

Google don't specify how you decide which domains. From their page:

2. Next, you'll need to add the _link function to any links between the domains. Please note that your analytics tracking code and calls to _gat._getTracker (shown in Step 1) must be placed on the page above the call to _link. If your current links look like:

<a href="https://www.secondsite.com/?login=parameters"> Login Now</a>

change them to:

<a href="https://www.secondsite.com/?login=parameters" onclick="pageTracker._link(this.href); return false;">Login Now</a>

Of course in Drupal with jQuery we have a better way than adding onclick= to every link.

I'd be happy to automate this so it just "discovers" the list of domains and doesn't present a UI at all. The only case where this would cause complications is if they have a non-Drupal site (or a site under a different Drupal) that they would like to persist tracking sessions to. For instance if someone has a Drupal site and a Moodle site, and they want to use the same GA code on both, and they're on different domains, this isn't possible using just domains module integration, as domains module wouldn't know about the Moodle domain. This applies to my situation (I do have a Moodle on a different domain with the same GA code), and my patch allows for that with a manually configurable domains list. If I create this patch to use the domains module, I'll still have to run a custom-patched GA module to keep this functionality. Maybe it should call both the domains module hook and a custom hook, eg. hook_googleanalytics_domains(), so another module could provide additional domains as well?

So in terms of configuring pageTracker, have we decided that it shouldn't apply the _setDomainName() call at all (leave this as an option for the user to paste into the custom JS box), but we should apply _setAllowLinker(true) by default before the custom script is applied (and hence overridable)? Or did you want that as a UI checkbox option to allow external tracking codes? We shouldn't make the user manually paste that into the JS box, as they wouldn't know they need to when using multiple domains...

clint.davis’s picture

Im also interested that some movement goes ahead with this as well.

Subscribing.

hass’s picture

hass’s picture

Marked #376166: Tracking multiple domains as a duplicate.

hass’s picture

Title: Support for tracking internal links on multi-domain sites » Add multi/cross domain tracking
hass’s picture

@neilnz: We should get the remaining bugs in the patch fixed and get this in soon...

neilnz’s picture

I'm not working on this at the moment - I was waiting on some confirmation of how you'd like this done before I changed my patch... I'm happy to spend some time working on this though.

My question again:

So in terms of configuring pageTracker, have we decided that it shouldn't apply the _setDomainName() call at all (leave this as an option for the user to paste into the custom JS box), but we should apply _setAllowLinker(true) by default before the custom script is applied (and hence overridable)? Or did you want that as a UI checkbox option to allow external tracking codes? We shouldn't make the user manually paste that into the JS box, as they wouldn't know they need to when using multiple domains...

Note that approach would require an FAQ on how to enable this kind of behaviour, including instructions on making inter-site links call the _link function (as my jQuery did in the original pach).

hass’s picture

Sorry, but I do not really understand you... :-(.

From my review of the gaAPI docs I think we need to implement the following:

1. Catch click on links and execute _link().
2. Catch form submits and execute _linkByPost().
3. _setDomainName() should allow "auto" | "none" | [domain]. Maybe add a selectbox with "auto", "none" and "domain list" item and the "domain list" makes a input field visible where users are able to add the domain name - or we detect this from current hostname!? I expect that we are not able to solve this detection in 100% of all cases correctly. Some people like to add a full hostname and others a wildcard domain name. So this needs to be a free text field or we add items like "current domain as wildcard (.example.com)" and "current hostname (www.example.com)". For sure this needs a good description text and some basic validation.
4. _setAllowLinker() needs to be implemented.
5. Use as few setting as possible and try to make it nerd save by using good validation :-). I don't like to get any question about this feature in the issue queue... it need to be absolutely self explanatory.

All this needs to be as easy as possible to configure... I'm not yet sure how _setDomainName('auto') works... I need to investigate.

aaron1234nz’s picture

Hi Guys,

Take a look at this thread,

http://www.google.com/support/forum/p/Google+Analytics/thread?tid=2069ea...

this is the behavior experienced using the patch supplied by neilnz. It makes for a lot of pretty awful looking URLS. I'm not too familiar with the gaAPI but I expect that if cookie information is transferred between domains then this behavior cant be avoided.

owen barton’s picture

Version: 5.x-1.x-dev » 6.x-2.x-dev

I think http://code.google.com/apis/analytics/docs/tracking/gaTrackingSite.html has some good examples for how this can be done cleanly.

krlucas’s picture

Note that if you have segmentation enabled, pageTracker._setVar() still runs before "Code Before".

Therefore, if you include pageTracker._setDomainName("none"); in Code Before it will essentially be ignored--the setVar method call apparently initializes the cookie domain for all subsequent pageTracker method calls in that pageView.

To test:
Enable segmentation for user roles.
Add

pageTracker._setDomainName("none");
pageTracker._setAllowLinker(true);

to Code Before
Visit the site as an anonymous user. The setVar call for segmentation won't happen because you're not logged in. The GA cookies are set with the FQDN (www.mydomain.com) -- expected behavior for cross domain tracking.
Log in. A new set of GA cookies is set with the wild card DN (.mydomain.com) -- as if pageTracker._setDomainName("none"); had not been called.

Calling _setVar after the _setDomainName call seems to solve this.

hass’s picture

Why should the domain of your cookies change if you log in? Sounds like your site is not configured properly.

icenogle’s picture

krlucas is right. I just tried his experiment and looked at the page source.

hass’s picture

Someone interested in providing a patch for the next release?

webdrips’s picture

Subscribing

turadg’s picture

+1 subscribe

hass’s picture

sittard’s picture

Subscribing +1

jaxpax’s picture

Subscribing +1

csc4’s picture

Subscribing

jaxpax’s picture

Have somebody done any work on this lately?

bleen’s picture

Subscribing

sanderc’s picture

How is the status on this? I face the same problem: multiple domains using one Drupal installation. The Google Analytics code seems to track only one of the domains. Am I right that tracking of multiple domains is not implemented yet?

bleen’s picture

Version: 7.x-1.x-dev » 6.x-2.x-dev

In case this helps anyone ...

under admin/settings/googleanalytics we added the following under "Advanced Settings"->"Custom JavaScript Code"...

BEFORE

pageTracker._setDomainName("none");
pageTracker._setAllowLinker(true);

AFTER

var domains = new Array("a.com","b.com","c.com");
$('a').each(function(){
  for (domain in domains) {
    if(this.href.match(domain)){
      $(this).click(function(){ pageTracker._link(this.href); });
    }
  }
});

note: I'm actually using a slight variation of this on my site (relying on some site-specific stuff) so test this thoroughly..

IMPORTANT UPDATE: if you use this code AND have any links on your site that load content AJAXically you need to make sure that those links are accounted for. If you dont, the AJAXically loaded content will suddenly take over your whole page. Example: if you use quicktabs then this line:

if(this.href.match(domain)){
will need to be something like this:
if(this.href.match(domain) && !this.href.match('quicktabs')){

ANOTHER UPDATE:
Suggestion from #48 below... you might want the "AFTER" js to read:

var domains = new Array("a.com","b.com","c.com");
$('a').each(function(){
  for (var i=0; i<domains.length; i++) {
    if(this.href.match(domains[i])){
      $(this).click(function(){ pageTracker._link(this.href); });
    }
  }
});
jaxpax’s picture

@Bleen18 You're da bomb! =)

guysaban’s picture

Google now offers the option of multi-domain tracking [which is also useful for i18n websites that use sub-domains for languages (see that option in the i18n module) like en.example.com, de.example.com, etc.].

Google tracker:

<script type="text/javascript">

  var _gaq = _gaq || [];
  _gaq.push(['_setAccount', 'UA-990102-1']);
  _gaq.push(['_setDomainName', '.websitename.com']);
  _gaq.push(['_trackPageview']);

  (function() {
    var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
    ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
  })();

</script>
hass’s picture

Version: 6.x-2.x-dev » 7.x-1.x-dev

@bleen18: Thank you for sharing your example. Hopefully it helps someone. I believe the Ajax stuff can be solved by binding to body. See latest DEV.

Something like this (fully untested)

var domains = new Array("a.com","b.com","c.com");
$(document.body).click(function(event) {
  // Catch only the first parent link of a clicked element.
  $(event.target).parents("a:first,area:first").andSelf().filter("a,area").each(function() {
    for (domain in domains) {
      if(this.href.match(domain)){
        $(this).click(function(){ pageTracker._link(this.href); });
      }
    }
  }
});

For now I'm moving this to D7 as it seems easier to solve as in D6. I guess it will never backported. I wonder that your code works as I thought from the google docs that all tracker code need to be added into the header and we are not able to do this in D6 from hook_footer().

bleen’s picture

@hass

I wonder that your code works as I thought from the google docs that all tracker code need to be added into the header and we are not able to do this in D6 from hook_footer().

can you point me to the google documentation that you're referring to?

hass’s picture

In past the google docs said you can add the normal tracker code to the header and footer (recommended), but if _link() is used you must add it to the header or at least before the first _link() call.

hass’s picture

Found it http://www.google.com/support/googleanalytics/bin/answer.py?hl=en&answer...

If your pages include a call to _trackPageview(), _link(), _trackTrans(), or _linkByPost(), your Analytics tracking code must be placed in your HTML code above any of these calls.

bleen’s picture

@hass .. In #31 my analytics code is above my link() calls. I have my link() calls in the "after" section, meaning "after the analytics code"

right?

Jay Adan’s picture

Would this go in the settings for the top level domain and all sub-domains - or just the top-level domain settings?

bleen’s picture

@jayaden .. all domains

AlexisWilke’s picture

Hi guys,

There is an option in Google Analytics, when you create a new entry for a domain, that says "multiple top-level domains"

When you select that option, you get the following JavaScript:

<script type="text/javascript">

  var _gaq = _gaq || [];
  _gaq.push(['_setAccount', 'UA-4615342-15']);
  _gaq.push(['_setDomainName', 'none']);
  _gaq.push(['_setAllowLinker', true]);
  _gaq.push(['_trackPageview']);

  (function() {
    var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
    ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
    var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
  })();

</script>

So it looks like this is correct as hass mentioned in an earlier post. From what I can tell, that code should never be a problem for users with multiple domains.

Thank you.
Alexis

johnpitcairn’s picture

subscribe

bleen’s picture

re #41 ... take a look at steps 2 & 3 here: http://www.google.com/support/analytics/bin/answer.py?hl=en&answer=55503

... this needs some love too for multi-domain analytics to work correctly

AlexisWilke’s picture

Ah! That would explain why I saw no difference...

I'm testing point 4. of http://www.google.com/support/analytics/bin/answer.py?hl=en&answer=55503

I'll see whether the filter has an effect, so far it changed nothing to the output...

That filtering system is quite powerful!

Thank you.
Alexis

AlexisWilke’s picture

Btw,

I added the following:

pageTracker._setDomainName("none");pageTracker._setAllowLinker(true);

to the Code snippet (before): in the Advanced settings / Custom JavaScript code.

Hope that helps. If you make it work, let us know! 8-)

Thank you.
Alexis

bleen’s picture

AlexisWilke ... check out #31

AlexisWilke’s picture

Hi guys,

Good news! It works. Just did not have time to check back here for a little while.

If you had the old method first, you will still see the old entries and they won't be merged... So that means you will have counts that go a bit funky for a month or so.

My setup on my website is simply to add the following in the Code snippet (before) text area (in field set Advanced settings » Custom JavaScript code)

pageTracker._setDomainName("none");pageTracker._setAllowLinker(true);

Then, as mentioned by bleen18 in #43 and me in #44, I followed step 4 to create the perfect filter on GoogleAnalytics:

http://www.google.com/support/analytics/bin/answer.py?hl=en&answer=55503

That part is rather complicated, to my point of view. You have to go to the main screen and click "edit" on the domain you're interested in. There click on "Add Filter". Give it a name and choose "Advanced".

For Field A, choose Hostname and enter (.*) as the pattern.
For Field B, choose Request URI and enter (.*) as the pattern.
For Output, choose Request URI and enter $A1$B1
Say Yes to everything except case insensitive if you're on Linux.

Wait a few days, then go to your analytics report and enter a domain name in the search below, for me I entered www.m2osw.com and secure.m2osw.com to see each separate (and I'm happy to see I don't get very many hits on the secure side which means I don't use that much processing time to encrypt data.)

Hope this helps.

Thank you.
Alexis Wilke

chrisdfeld’s picture

Version: 6.x-2.x-dev » 7.x-1.x-dev

Thanks for this. One important correction to the JavaScript in comment #31 above:

AFTER

var domains = new Array("a.com","b.com","c.com");
$('a').each(function(){
  for (domain in domains) {
    if (this.href.match(domains[domain])){
      $(this).click(function(){ pageTracker._link(this.href); });
    }
  }
});
hass’s picture

Status: Needs work » Fixed

Committed completely reworked code to D7.

hass’s picture

Version: 7.x-1.x-dev » 6.x-3.x-dev
Status: Fixed » Patch (to be ported)
StatusFileSize
new11.56 KB

Here is the committed D7 patch.

The above shared code for BEFORE and AFTER have some real issues. I strongly suggest you to add at least a break; into the "for" or the loop will not end after a match (waste of time of you have many cross domains) and only after the list has been looped to the end. I guess it is much faster to change to a regex, like I've done in D7. Additionally there is currently no regex escaping in place what will cause matches for "a.com" if the URL have something inside like "aXcom". Every char will match for the dot. It would also be better if there would be a no protocol check in the beginning. Maybe some of you have an URL like "http://example.com?ref=a.com". This will currently cause a false match, as this is not a cross domain of your sites.

This may be some edge cases, but keep this in mind as the code is not stable.

hass’s picture

Status: Patch (to be ported) » Closed (won't fix)

I guess we are not able to backport this D7 patch. As documented in http://www.google.com/support/analytics/bin/answer.py?answer=55503 we cannot do the _link this stuff reliable from footer for the reason of

Please note that your analytics tracking code and calls to _gat._getTracker (shown in Step 1) must be placed on the page above the call to _link

Therefore it get's a WON'T FIX for D6. :-(

hass’s picture

Version: 6.x-3.x-dev » 7.x-1.x-dev
Status: Closed (won't fix) » Fixed

Let's show the D7 issue in queue for the next 2 weeks.

nvanhove’s picture

This method gives a error on every page when clicking on a link

  RegExp.escapeDomains = function(text) {
    return text.replace(/[-[\]{}()*+?.,\\^$#\s]/g, "\\$&");
  }

Error: text is undefined
Source File: http://nsh.cad.drupaldev/sites/default/modules/contrib/google_analytics/...
Line: 11

Fixed the errormsg by using

  RegExp.escapeDomains = function(text) {
    if(text)
      return text.replace(/[-[\]{}()*+?.,\\^$#\s]/g, "\\$&");
  }
nvanhove’s picture

Status: Fixed » Needs work
hass’s picture

Do you know how to repro?

magnusk’s picture

I have the same problem as nvanhove, Firefox reporting that "text is undefined". (Not sure this belongs in this issue?)

I've set the JS to be included in the footer, no header. Single domain tracking. I think all other settings left as they come by default.

hass’s picture

Status: Needs work » Fixed

Changed the line for now to:

return (text) ? text.replace(/[-[\]{}()*+?.,\\^$#\s]/g, "\\$&") : '';

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.