Hooking into Google Analytics with ColdFusion

You might also like the ColdFusion and Google Analytics: Getting Out What You Put In post.

Using Alex Curelea’s example of using PHP to hook into Google Analytics through their API, I developed a ColdFusion method. So, same thing as Alex just flavored with CF. The code grabs the profiles and pageviews, then totals up the pageviews for all profiles.

Update: I followed Raymond Camden’s suggestion below and posted another version of this using ClientLogin Authentication.

1. Authenticate user and get token from Google. Basically just a link that sends them off to Google and Google returns them to your page designated in the href with token attached as URL param.

<a href="https://www.google.com/accounts/AuthSubRequest?next=http://www.your-site-here.com/GAwithCF.cfm&scope=https://www.google.com/analytics/feeds/
&secure=0&session=1">Authenticate through Google.</a>

2. Send that token back to Google and get a session token that can be used to retrieve the profile information and data. This is handled in two functions that I originally had in a CFC. I put all the code on one page for this example.

For the session token required for access to the data, the sessionToken function is called with the URL.token passed in.

<cfset authSessionToken = sessionToken(URL.token) />

The sessionToken function makes a get request to https://www.google.com/accounts/AuthSubSessionToken with the token as a header value using the callApi function. The header value must be in the form of Authorization: AuthSub token=”C8b_xxxxxxxxx” with the token equaling the URL.token.
The session token comes back in the form of “Token=CH____88dfdsfsd.” Strip off the “Token=” and you have your session token. The default start and end dates are also set here.

<!---dates for one full year of stats (default)--->
<cfset startdate = DateFormat(DateAdd("d",-365,CreateDate(Year(Now()),Month(Now()),Day(Now()))), "yyyy-mm-dd") />
<cfset enddate = DateFormat(DateAdd("d", -1, Now()),"yyyy-mm-dd") />

<cffunction name="sessionToken" access="public" returntype="string">
	<cfargument name="authToken" type="string" required="yes">

    <cfset var output = callApi("https://www.google.com/accounts/AuthSubSessionToken",arguments.authToken) />
    <cfset var authSubSessionToken = "" />

    <cfset authSubSessionToken = Mid(output, FindNoCase("Token=",output) + (Len("Token=")), Len(output)) />

    <cfreturn authSubSessionToken />
</cffunction>

<cffunction name="callApi" access="public" returntype="string">
    <cfargument name="gaUrl" type="string" required="yes">
    <cfargument name="authToken" type="string" required="yes">

    <cfset var authSubToken = 'AuthSub token="' & arguments.authToken & '"' />
    <cfset var responseOutput = "" />

    <cfhttp url="#arguments.gaUrl#" method="get">
        <cfhttpparam name="Authorization" type="header" value="#authSubToken#">
    </cfhttp>

    <cfset responseOutput = cfhttp.filecontent />

    <cfreturn responseOutput />
</cffunction>

3. Now the callApi function can be used with the authSessionToken to return the account profiles as XML. Unfortunately, it’s an atom feed XML so a little stripping must occur before we can use the handy XMLSearch function in ColdFusion.

<cfset accountXML = callApi("https://www.google.com/analytics/feeds/accounts/default",authSessionToken) />

First we remove the nodes with a dxp: prefix. Then we clear the “xmlns” nodes in the feed element. That’s what bugs up XMLSearch.

<cfset accountXML = accountXML.ReplaceAll("(</?)(\w+:)","$1") />
<cfset accountXML = REReplaceNoCase(accountXML,"<feed[^>]*>","<feed>") />

Anytime you want to take a peek at the XML, you can use cfdump to spit it out for you.

<cfdump var="#XMLParse(accountXML)#">

4. Parse out the XML to put the profiles in an array for easy looping later.

<cfset entryNodes = XmlSearch(accountXML, '//entry/') />

<cfset profileArray = ArrayNew(1) />

<cfloop from="1" to="#ArrayLen(entryNodes)#" index="num">
    <cfset entryStruct = StructNew() />

    <cfset entryStruct.id = entryNodes[num].id.XmlText />
    <cfset entryStruct.title = entryNodes[num].title.XmlText />
    <cfset entryStruct.tableId = entryNodes[num].tableId.XmlText />

    <cfloop from="1" to="#ArrayLen(entryNodes[num].property)#" index="i">
        <cfswitch expression='#entryNodes[num].property[i].XmlAttributes["name"]#'>
            <cfcase value="ga:accountId">
                <cfset entryStruct.accountId = entryNodes[num].property[i].XmlAttributes["value"] />
            </cfcase>
            <cfcase value="ga:accountName">
                <cfset entryStruct.accountName = entryNodes[num].property[i].XmlAttributes["value"] />
            </cfcase>
            <cfcase value="ga:profileId">
                <cfset entryStruct.profileId = entryNodes[num].property[i].XmlAttributes["value"] />
            </cfcase>
            <cfcase value="ga:webPropertyId">
                <cfset entryStruct.webPropertyId = entryNodes[num].property[i].XmlAttributes["value"] />
            </cfcase>
        </cfswitch>
    </cfloop>

    <cfset arrayAppend(profileArray,duplicate(entryStruct)) />

</cfloop>

5. Loop through each profile, grab the metrics you want for each profile from Google, display them on the page, and tally up the total page views count. The XML needs some tidying again but you saw that earlier.

<cfset totalpageviews = 0 />

<cfloop from="1" to="#ArrayLen(profileArray)#" index="num">
    <cfset reqUrl = "https://www.google.com/analytics/feeds/data?ids=" & profileArray[num].tableId & "&metrics=ga:pageviews&start-date=" & startdate & "&end-date=" & enddate />

    <cfset statsXML = callApi(reqUrl,authSessionToken) />
    <!---Tidy up the XML--->
    <cfset statsXML = statsXML.ReplaceAll("(</?)(\w+:)","$1") />
    <cfset statsXML = REReplaceNoCase(statsXML,"<feed[^>]*>","<feed>") />

<cfset metric = XmlSearch(statsXML, '//metric/') />

<cfset pageviews = metric[1].XmlAttributes["value"] />

<cfset totalpageviews = totalpageviews + pageviews />

<p>Profile: #profileArray[num].title# #NumberFormat(pageviews, ",")#</p>

</cfloop>

<p>Total pageviews: #NumberFormat(totalpageviews, ",")#</p>

That’s it. It should at least get you started. Thanks Alex!

Recommended Reading

Download

News of the Google Analytics API

Information on Google AuthSub Proxy Authentication.

Helpful ColdFusion books include the all-in-one ColdFusion MX 7 WACK with tons of good information even if you’ve moved up to CF8. Crazy as it seems, Adobe decided to break up ColdFusion 8 books into three volumes: Volume 1: Getting Started, Volume 2: Application Development, and Volume 3: Advanced Application Development. You may be able to pick up a used copy in good condition. Follow the book’s link above to Amazon and look for used copies underneath the books title.

 

Further Reading:

  1. ColdFusion and Google Analytics: Getting Out What You Put In
  2. Google Analytics API Login Authentication with ColdFusion
  3. Generating Signatures in ColdFusion with RSA-SHA1 for Secure AuthSub in Google Analytics

9 Comments

  • Pingback: Twitted by mjrjweb

  • May 8, 2009 - 2:22 pm | Permalink

    FYI, step 1 should not be necessary. I mean if you want to get your own stats. You can authenticate/gettoken all via CFHTTP (see my Google Calendar, Docs, Contacts wrappers for more info).

  • May 10, 2009 - 6:50 pm | Permalink

    @Raymond Camden

    Thank you. I posted another version using the method you described. The link to it is at the top of this post.

    Version of this suggested by Raymond Camden: Google Analytics API Login Authentication with ColdFusion

  • Will Wilker
    May 13, 2009 - 1:08 pm | Permalink

    Thanks, Jen! I, too, started taking Alex’s code apart and porting it over to Coldfusion. The PHP version just begged to be using cfhttp, and then I ran into the issue with the atom XML and the extra nodes. Your example cleared things up for me. My hat’s off to you! I have only been developing in Coldfusion for about 7 months now, but I really like it.

  • May 13, 2009 - 2:04 pm | Permalink

    @Will
    Awesome! Glad it helped. I tried several ways to get GA to spit out a feed without the xmlns nodes using the methods Google describes in their Data API FAQs: http://code.google.com/apis/gdata/faq.html#alternate_data_formats

    If you like CF, give Model-Glue a whirl. The latest version is in beta. Once I got my head around it, MG proved to be a huge advantage.
    http://www.model-glue.com/blog/index.cfm/2009/5/12/Model-GlueGesture–Beta-Available

  • June 14, 2010 - 7:56 pm | Permalink

    Thank-you, thank-you, thank-you!
    I couldn't figure out why xmlSearch wasn't working on a ATOM feed.
    reReplaceNoCase(httpResult,"\<feed[^>]*\>","<feed>")
    …did the trick. Thanks again!

  • June 14, 2010 - 8:10 pm | Permalink

    @Andrew
    Frustrating issue for sure. You're welcome.

  • September 12, 2010 - 7:45 pm | Permalink

    Awesome. Next time I should read the comments first. Lol, I made the same mistake as Jen. Wish I don't have to code at night anymore.

  • September 27, 2010 - 10:29 am | Permalink

    I'm really just getting started with Cold Fusion, but after reading your post I think I get a lot more about how to implement it than I did before. the code isn't as confusing to me now, and I thank you for that.

  • Leave a Reply

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

    *

    *

    You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>