You might also like the ColdFusion and Google Analytics: Getting Out What You Put In post.
Updated 08-02-2009: Replaced this line:
<cfset accountXML = callApi("https://www.google.com/analytics/feeds/accounts/#urlEncodedFormat(form.Email)#",loginAuth) />
with this:
<cfset accountXML = callApi("https://www.google.com/analytics/feeds/accounts/default",loginAuth) />
My Hooking into Google Analytics with ColdFusion post received a suggestion from Raymond Camden that authentication and token acquisition could be done via cfhttp for access to Google Analytics information. So, here it is again with authentication built in. I set this up with a email/password form but you could easily hard code the email and password in if you so desired.
1. Prompt for email and password of Google account that has access to Google Analytics data. This is an all-in-one example so the form submits back to its own page and processing continues.
<form action="GAwithCFlogin.cfm" method="post">
Google email <input type="text" name="Email" /><br />
Password <input type="password" name="password" /><br />
<input type="submit" />
</form>
2. Authenticate the credentials with Google.
<cfset loginAuth = googleLogin(form.Email,form.password) />
The googleLogin function sends the email/password combo along with the service and source values as URL parameters required by Google to Google’s client login URL via a post request. The source parameter is a Short string identifying your application, for logging purposes. This string should take the form: “companyName-applicationName-versionID” according to Google. You can put in your company name or whatever name you want followed by the rest of the convention Google suggests. Example: yourcompanyname-analytics-1.0. 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="googleLogin" access="public" returntype="string">
<cfargument name="email" type="string" required="yes" default="">
<cfargument name="password" type="string"required="yes" default="">
<cfargument name="gaLoginUrl" type="string" required="no" default="https://www.google.com/accounts/ClientLogin">
<cfset var loginAuth = "" />
<cfhttp url="#arguments.gaLoginUrl#" method="post">
<cfhttpparam name="accountType" type="url" value="GOOGLE">
<cfhttpparam name="Email" type="url" value="#arguments.email#">
<cfhttpparam name="Passwd" type="url" value="#arguments.password#">
<cfhttpparam name="service" type="url" value="analytics">
<cfhttpparam name="source" type="url" value="yourCompanyName-analytics-1.0">
</cfhttp>
<cfif NOT FindNoCase("Auth=",cfhttp.filecontent)>
<cfset loginAuth = "Authorization Failed" />
<cfelse>
<cfset loginAuth = Mid(cfhttp.filecontent, FindNoCase("Auth=",cfhttp.filecontent) + (Len("Auth=")), Len(cfhttp.filecontent)) />
</cfif>
<cfreturn loginAuth />
</cffunction>
What should come back is a long string of authorization token values. There are several but we only need the Auth token. If it’s not in there, we send back “Authorization Failed.” If it’s in there, we strip it out and return it.
3. Get the accounts associated with the email. There can be more than one set up in Analytics.
<cfset accountXML = callApi("https://www.google.com/analytics/feeds/accounts/default",loginAuth) />
The email account is appended onto the Google URL and a get call is made from the callApi function with the loginAuth token attached as a header value.
<cffunction name="callApi" access="public" returntype="string">
<cfargument name="gaUrl" type="string" required="yes">
<cfargument name="authToken" type="string" required="yes">
<cfset var authSubToken = 'GoogleLogin auth=' & 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>
The header value must be in the form of Authentication: GoogleLogin auth=DF3843483…(it’s kind of long but you get the idea). This authentication is different form AuthSub Proxy authentication used in my previous post.
4. Clean up the XML that returned so we can use XmlSearch() and other handy functions available in ColdFusion. For some reason the atom specification of the XML file bungles those functions up. We need to remove the xmlns nodes from the feed element and remove the dxp prefixes. Do a cfdump on the XML before and after two lines of code used to tidy them up to seed what happens.
<cfdump var="#accountXML#">
Then roll through the accountXML with to grab all the profiles and toss them into an array so we can loop through them later.
<!---remove dxp: prefix from nodes that have it--->
<cfset accountXML = accountXML.ReplaceAll("(</?)(\w+:)","$1") />
<!---Strip xmlns from feed element--->
<cfset accountXML = REReplaceNoCase(accountXML,"<feed[^>]*>","<feed>") />
<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 the profiles, grab the page views and visitors, and tally them up for all profiles.
<cfset totalpageviews = 0 />
<cfset totalvisitors = 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,ga:visitors&start-date=" & startdate & "&end-date=" & enddate />
<cfset statsXML = callApi(reqUrl,loginAuth) />
<!---Tidy up the XML again --->
<cfset statsXML = statsXML.ReplaceAll("(</?)(\w+:)","$1") />
<cfset statsXML = REReplaceNoCase(statsXML,"<feed[^>]*>","<feed>") />
<cfset metric = XmlSearch(statsXML, '//metric/') />
<cfset pageviews = metric[1].XmlAttributes["value"] />
<cfset visitors = metric[2].XmlAttributes["value"] />
<cfset totalpageviews = totalpageviews + pageviews />
<cfset totalvisitors = totalvisitors + visitors />
<p><strong>Profile:</strong> <em>#profileArray[num].title#</em> Pageviews: #NumberFormat(pageviews, ",")# Visitors: #NumberFormat(visitors, ",")# </p>
</cfloop>
<p><strong>Total pageviews:</strong> #NumberFormat(totalpageviews, ",")#</p>
<p><strong>Total visitors:</strong> #NumberFormat(totalvisitors, ",")#</p>
All done. Thank you Raymond Camden and Alex Curelea. Alex got me pointed in the right direction with his post Using the Google Analytics API – getting total number of page views. And, as I mentioned at the get go, Raymond Camden made the suggestion of using the login as some of his examples have done in the past.
Recommended Reading
Download
Information on Google’s Authentication for Installed Application ClientLogin and the Google Analytics Data API ClientLogin Username/Password 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 link to Amazon and look for used copies underneath the books title.