Archives

 

Enhance Your SiteCatalyst S_Code Using Server-Side Scripting

I receive a lot of questions from people working on their own SiteCatalyst implementations and I’m always happy to help. One that I got recently is “why is your s_code file a php file”? I figured there were not too many people out there doing it like this or even know about this, so I thought I would help out those that were interested in what the advantages of using server side code to enhance your data collection.

The reason I use a php file to house all of my s_code script is simple. I want to be able to do what is not easily done with using standard JavaScript. Here are a couple of examples.

First things first. How do I get a php file to act like JavaScript? It’s actually pretty easy. First thing you do is in the top of your file add this small bit of code:

<?php header('Content-type: application/javascript'); ?>

The purpose of this line is just to say ‘hey, unless instructed otherwise, treat everything you are going to see here as JavaScript. Next change your file extension from .js to .php. That’s all you need to do to start adding in some php scripting into your file.

Here are some things I am using it for. I like capturing the IP address of my visitors. I like to track this because I have had problems with spammers, scraper bots and general bad visitors in the past, and I just like keeping my eye on things. Here is the code to capture IP address.

s.eVar17="<?php echo $_SERVER['REMOTE_ADDR']?>";

I also have this matched with the get Val Once plug-in.

Another thing I like to capture is User Agent. How many people come to my site from a specific build of IE6? Is Googlebot executing my JavaScript when crawling my site? Here is how I capture User Agent.

s.eVar23="<?php echo $_SERVER['HTTP_USER_AGENT']?>";

Again I match this up with the get Val Once plug-in.

Another thing I like to do is use php to populate the configuration variables of the Time Parting plug-in. The latest version of the plug-in, 2.0, uses specific daylight savings time variables (the 2.0 version is available from the Omniture Help section. The version I host here on the site is the older 1.4 version). The 3 variables that need to be configured for the plug-in are Daylight Savings Time start day for the current year, Daylight Savings Time end day for the current year, and the Current Year. Now all of these can be hard coded, but I’d rather do a little bit of one time coding and never have to worry about it again. Here is how I set those variables using php.

s.dstStart="<?php echo date('m/d/Y', strtotime("Second Sunday March 0"));?>";
s.dstEnd="<?php echo date('m/d/Y', strtotime("First Sunday November 0"));?>";
s.currentYear="<?php echo date('Y');?>";

All of these take advantage of the date() functionality of php. Combine that with a little bit of extra code, and with the fact that I know that daylight savings time always begins the second Sunday of March and ends on the first Sunday of November, I never need to touch those variables again.

The getCartOpen and resetGetCartOpen SiteCatalyst Plugins

In our quest to make our SiteCatalyst implementation more powerful and lighten the load on the developers and programmers (who for some reason always have more important things to do then add more Omniture code to the site), there are a couple of plug-ins I like to use to help to set the scOpen event.

The scOpen event is used to populate the Carts Report and metric with the number of times visitors view a new shopping cart during an eCommerce process. The scAdd event should be used when adding items into a shopping cart. But what if every time you add an item to the shopping cart, you are taken to the shopping cart? Well then you should set both events the first time. But only first time the shopping cart is opened, the scOpen event should be set. If the shopping cart has already been opened, the scOpen event should not be set again during that visit. So how do we make sure that the scOpen event is only set on that very first instance of the cart being viewed?

What we have here are the getCartOpen and resetGetCartOpen SiteCatalyst plug-ins. What the getCartOpen plug-in does is looks for the very first instance of the scAdd event being set and adds the scOpen event at that same time. Each time after that the scAdd event is set, the scOpen event will not be set again. But what if the visitor completes an order and wants to place another during the same visit? That’s where the resetGetCartOpen plug-in comes in. It looks for an instance of the purchase event and then allows the scOpen event to be set again if a new shopping cart is created.

This first line which goes in the calls to plugins section of the s_code.js file, calls the getCartOpen plugin and returns the events string with scOpen added the first time scAdd occurs during a visit.

/*Get Cart Open*/
s.events=s.getCartOpen("s_scOpen");

And here is the getCartOpen plug-in code that should be added to the plug-in code section.

/*
 * Plugin: getCartOpen
 */
s.getCartOpen=new Function("c",""
+"var s=this,t=new Date,e=s.events?s.events:'',i=0;t.setTime(t.getTim"
+"e()+1800000);if(s.c_r(c)||e.indexOf('scOpen')>-1){if(!s.c_w(c,1,t))"
+"{s.c_w(c,1,0)}}else{if(e.indexOf('scAdd')>-1){if(s.c_w(c,1,t)){i=1}"
+"else if(s.c_w(c,1,0)){i=1}}}if(i){e=e+',scOpen'}return e");

This line which goes in the calls to plug-ins section of the s_code.js file, calls the resetGetCartOpen plug-in and resets the scOpen event after a purchase takes place, allowing the scOpen event to be set again if the visitor decides to make another purchase during the same visit.

/*Reset Get Cart Open*/
s.events=s.resetGetCartOpen(); 

And here is the resetGetCartOpen plug-in code that should be added to the plug-in code section.

/*
 * Plugin: resetGetCartOpen
 */
s.resetGetCartOpen=new Function("" 
+"var s=this,t=new Date,e=s.events?s.events:'';t.setTime(t.getTime()+"
+"10000);if(e.indexOf('purchase')>-1){if(s.c_r('s_scOpen')||e.indexOf"
+"('scOpen')>-1){if(!s.c_w('s_scOpen','',t)){s.c_w('s_scOpen','',0);}"
+"}}return e");

For more information on when to use the scOpen and scAdd events, check out the Omniture Knowledge base Answer ID 440.

Enjoy!

Detect Silverlight and Flash with SiteCatalyst Plugin

Tracking your visitor’s Flash version is nothing new. I wrote about it a while ago in the Plugin To Detect Flash in SiteCatalyst post. What I have here is a little more powerful. This is the Rich Internet Application tracking plug-in.

RIA development is becoming more and more popular. Do your Visitors have Flash? What version? How about SilverLight? What version of that? Do they have both? Now you will know. What this SiteCatalyst plug-in does is detects the Silverlight version, and the Flash version. If you want to see it in action, fire up your Omniture JavaScript debugger on this page. You will see s.prop10 and s.prop11 (c10 and c11) populated with the Flash version and Silverlight versions your browser has installed. You will also see the Flash version populated in s.prop9 (c9). This is done using the original flash detection plug-in mentioned above.

Here is how to use it. First in the s_code.js file call the plug-in along with the variables you want to use to store the visitors rich application versions. Here I am using s.prop10 and s.prop11:

s.detectRIA('s_ria','prop10','prop11'); 

Then in the plug-in’s section add the plug-in code:

/*
 * Plugin: detectRIA v0.1 - detect and set Flash, Silverlight versions
 */
s.detectRIA=new Function("cn", "fp", "sp", "mfv", "msv", "sf", ""
+"cn=cn?cn:'s_ria';msv=msv?msv:2;mfv=mfv?mfv:10;var s=this,sv='',fv=-"
+"1,dwi=0,fr='',sr='',w,mt=s.n.mimeTypes,uk=s.c_r(cn),k=s.c_w('s_cc',"
+"'true',0)?'Y':'N';fk=uk.substring(0,uk.indexOf('|'));sk=uk.substrin"
+"g(uk.indexOf('|')+1,uk.length);if(k=='Y'&&s.p_fo('detectRIA')){if(u"
+"k&&!sf){if(fp){s[fp]=fk;}if(sp){s[sp]=sk;}return false;}if(!fk&&fp)"
+"{if(s.pl&&s.pl.length){if(s.pl['Shockwave Flash 2.0'])fv=2;x=s.pl['"
+"Shockwave Flash'];if(x){fv=0;z=x.description;if(z)fv=z.substring(16"
+",z.indexOf('.'));}}else if(navigator.plugins&&navigator.plugins.len"
+"gth){x=navigator.plugins['Shockwave Flash'];if(x){fv=0;z=x.descript"
+"ion;if(z)fv=z.substring(16,z.indexOf('.'));}}else if(mt&&mt.length)"
+"{x=mt['application/x-shockwave-flash'];if(x&&x.enabledPlugin)fv=0;}"
+"if(fv<=0)dwi=1;w=s.u.indexOf('Win')!=-1?1:0;if(dwi&&s.isie&&w&&exec"
+"Script){result=false;for(var i=mfv;i>=3&&result!=true;i--){execScri"
+"pt('on error resume next: result = IsObject(CreateObject(\"Shockwav"
+"eFlash.ShockwaveFlash.'+i+'\"))','VBScript');fv=i;}}fr=fv==-1?'flas"
+"h not detected':fv==0?'flash enabled (no version)':'flash '+fv;}if("
+"!sk&&sp&&s.apv>=4.1){var tc='try{x=new ActiveXObject(\"AgControl.A'"
+"+'gControl\");for(var i=msv;i>0;i--){for(var j=9;j>=0;j--){if(x.is'"
+"+'VersionSupported(i+\".\"+j)){sv=i+\".\"+j;break;}}if(sv){break;}'"
+"+'}}catch(e){try{x=navigator.plugins[\"Silverlight Plug-In\"];sv=x'"
+"+'.description.substring(0,x.description.indexOf(\".\")+2);}catch('"
+"+'e){}}';eval(tc);sr=sv==''?'silverlight not detected':'silverlight"
+" '+sv;}if((fr&&fp)||(sr&&sp)){s.c_w(cn,fr+'|'+sr,0);if(fr)s[fp]=fr;"
+"if(sr)s[sp]=sr;}}");
s.p_fo=new Function("n",""
+"var s=this;if(!s.__fo){s.__fo=new Object;}if(!s.__fo[n]){s.__fo[n]="
+"new Object;return 1;}else {return 0;}");

“We are considering developing some Silverlight applications. What percentage of our visitors have Silverlight, and if so what version?” Now you will have no problems answering that question.

If you would like more information about using SiteCatalyst with Silverlight, check out the white paper Using SiteCatalyst and Silverlight, available in the Omniture Knowledge Base.

Enjoy!

How To Add Page Views And Visits To All SiteCatalyst eVars

Here is a simple way to add Page Views and Visits to all of your eVar reports (see the update below). While Client Care has the ability to turn on a few things, flip some switches here and there to add visits to some reports, I like having full control over it as much as possible myself (and I’m not really in the mood to wait on hold to get it done). To make this happen we are going to have to use 2 events and a pair of plug-ins. Hopefully you should have a couple of events available to use, and you can find the plug-in code on the SiteCatalyst Plugins page. Here’s how to do it.

First lets get Page Views. To do this we are going to use the Append List plug-in. The Append List (or apl) plug-in utility provides a simple mechanism to append a value to any delimited lists, with the option of a case sensitive or case-insensitive check to insure the value doesn’t already exist in the list. The apl plug-in is referenced by several standard plug-ins but can be used directly in a variety of situations. This is an Omniture supported plug-in and you can find more information about it by accessing the Knowledge Base.

s.apl(L,v,d,u)

L = source list, empty list is accepted
v = value to append
d = list delimiter
u (optional, defaults to 0) Unique value check. 0=no unique check, value is always appended. 1=case insensitive check, append only if value isn’t in list. 2=case sensitive check, append only if value isn’t in list.

What we are going to do is in the s_code.js file, set up this plug-in to fire off an event on every single page view of the site, exactly mimicking the standard Page View metric.

/* Set Page View Event */
s.events=s.apl(s.events,'event1',',',1);

Here is the plug-in code:

/*
* Plugin Utility: apl v1.1
*/
s.apl=new Function("L","v","d","u",""
+"var s=this,m=0;if(!L)L='';if(u){var i,n,a=s.split(L,d);for(i=0;i<a."
+"length;i++){n=a[i];m=m||(u==1?(n==v):(n.toLowerCase()==v.toLowerCas"
+"e()));}}if(!m)L=L?L+d+v:v;return L");

UPDATE: In my eagerness to share this with everyone I skipped the testing process I usually do. Upon testing I realized I a critical, basic error in my logic. By doing it this way, you are setting an event prior to any of the eVars, so they would not be associated with each other, and there for show up as “None” in your report. It is such a simple oversight that I am actually embarrassed that I missed. So now I throw it out to you, the Omniture community, how do you guys think we should try to figure this one out? Or is it even possible to do? Let me know what you think!

Just to be super clear, this will work if you want to add visits to some of your Traffic Sources reports. For example if you want visits from the original referring domains, this will work perfectly for that.

Now lets get Visits. I thought a lot about how to get this. One method you could use would be do the exact same thing we did to get the Page Views event. Set it up on a different event, then call up Omniture Client Care to have them set that event to only record once per visit. Again I wanted to try to avoid using Client Care. I also did not want to go that route since I want this to mimic the Visits metric as close as possible, so I want it to be cookie based. Here I decided to use the Get Visit Start plug-in.

What the Get Visit Start plug-in was designed to do is to determine first page of a visit. It returns 1 on the first plug-in call of the visit, otherwise returns nothing. It uses a 30 minute cookie if possible, otherwise reverts to a session cookie. Always returns nothing if 30 minute or session cookie can’t be set, so it functions very much like the standard Visit’s metric. What I have done is set the plug-in up to populate a variable, then use basic JavaScript to determine if there is a value to the variable that was just set, and if so fire off the Append List plug-in. The theory behind this is the Get Visit Start plug-in will only return a value one time per visit, on the first page view, and at that one and only time fire off a single event.

s.getVisitStart(c)

c=cookie name for tracking (“s_visit” is standard)

Here is how I have it set up. I made up a variable s.visEvent to hold the value of the Get Visit Start Plugin, then fire off event6 if it has a value.

	
/* Set Visit Event */
s.visEvent=s.getVisitStart("s_visit");
if (s.visEvent) s.events=s.apl(s.events,'event6',',',1);

Here is the plug-in code:

/*
 * Get Visit Start
 */
s.getVisitStart=new Function("c",""
+"var s=this,v=1,t=new Date;t.setTime(t.getTime()+1800000);if(s.c_r(c"
+")){v=0}if(!s.c_w(c,1,t)){s.c_w(c,1,0)}if(!s.c_r(c)){v=0}return v;");

Pretty simple. Typically I would have just used the Get Val Once plug-in when I want to get a value of a variable only once per visit, but it didn’t want to work well with the s.events variable.

Enjoy!

Omniture Image Request Counter

A little while ago I wrote about how A Robust SiteCatalyst Implementation Could Fail In Internet Explorer. In the article I point out that IE does not like any URL’s that are over 2083 characters. Recently I have had some issues with a few sites where I think they may have been running into this problem again. I would always use Firebug or Tamper Data to look at the actual image request, copy that and drop it into one of the various free ‘character counting’ tools that are pretty abundant all over the web to make sure that the Omniture image request URL is not over 2083 characters. This can get pretty time consuming. I figured there has to be an easier way to do it. Behold the Omniture Image Request Counter.

Omniture Image Request Counter is just a simple JavaScript bookmark, very similar to the Omniture Debugger tool. What this does is takes a look at the image request and counts how many characters are there. Very quick, very simple.
Omniture Image Request Counter

Download a copy of the code here
or
View a copy of the code here

Here is how to use it:
1. Take any web page and create a bookmark (or favorite depending on what you call it).
2. Right-click the favorite you just created.
3. Click Properties.
4. Delete all text from the URL field of the Properties window.
5. Paste the following code into the URL field of the Properties window.

javascript:var%20j=document.styleSheets,i=document.images,r='';for(var%20x=0;x<j.length;x++)if(j[x].imports)for(var%20y=0;y<j[x].imports.length;y++)if(j[x].imports[y].href.toLowerCase().indexOf('/b/ss/')>=0)r+=j[x].imports[y].href+"\n\n";for(var%20x=0;x<i.length;x++)if(i[x].src.toLowerCase().indexOf('/b/ss/')>=0)r+=i[x].src+"\n\n";for(w_m%20in%20window)if(w_m.substring(0,4)=='s_i_'&&window[w_m].src)if(window[w_m].src.indexOf('/b/ss/')>=0)r+=window[w_m].src;void(alert('The%20Omniture%20image%20request%20contains%20'+r.length+'%20characters.'))

6. Click OK

Now navigate to any page that has Omniture SiteCatalyst code and click this new bookmark that you just created. You will get a pop up that tells you how many characters are in the Omniture image request. If you are using Internet Explorer and the image request is over 2083 characters then you need to review your web analytics code and make some changes to your SiteCatalyst implementation.

Track the Number of Your Twitter Followers in SiteCatalyst

I am a huge fan of the Twitter Analytics plug-in from OmnitureTwitterAnalytics.com.
In case you don’t know, the plug-in is a fully configurable feature designed to track brand popularity by capturing comments from Twitter into Omniture SiteCatalyst in real time. It is based on the Twitter Integration concept that was introduced by Adam Greco at Omniture Summit 2009.

I have been using it for many months now, recently upgrading to the latest version, but I still wanted more. Using the plug-in I know how many people are discussing my brand, but how many people are following my brands Twitter profile? How good are we doing at attracting followers and keeping them? I know I can just go right to twitter and see how many followers I have there, but that’s no fun. I want all my data right in SiteCatalyst! How many followers did I have yesterday, or last week? How did that tweet we put out there at 3pm do in attracting new followers? Did we lose any because of it?

One of the sites I work with is ForRent.com. They have a twitter profile, @AptsForRent, and they have been working on building a following. I decided to use them to test out to see if I could automatically record the number of followers they have each hour, every hour, and trend it over time.

Based on the code from OmnitureTwitterAnalytics.com I decided to see what I could do to use the Twitter API to look and see how many followers we have, and the Omniture Data Insertion API to insert that number into SiteCatalyst once an hour. I choose to do it only once an hour, since the smallest time granularity we can get in SiteCatalyst is hourly.

Download the Twitter Follower Tracking Code

First thing you need to do is to set up a new event. Make sure it’s set up as a numeric event, not a counter. Then take the php code and fill in the following variables:
$namespace = “YOUR_NAMESPACE_GOES_HERE”; //Namespace of your Omniture account
$domain = “112.2o7.net”; //This should not change UNLESS you are using first party cookies
$rsid = “REPORT_SUITE_IN_SITECATALYST”; //Name of the Report Suite we are sending data to
$tweetUser = “TWITTER_NAME”; //Twitter user name to track
$successEvent = “EVENT_NUMBER”; //Event you are going to use

Now set up a cron to run the code once an hour. If you set it more than once an hour it will take your follower count and sum it together for that hour. If you do it less than once an hour, then you will be missing data. So remember, once an hour.

OK so what do we get? Pull up the report for the event you chose to use. Be sure to look at it at a hourly granularity. If you look at daily totals, then you are going to see the grand total number of your followers each hour summed up. So if you have 100 followers, never gain any or lose any for an entire day, looking at that report on a daily granularity will say 2400. So remember, view the report hourly.
Twitter Follower Report
By viewing the report you can see how many followers you had each hour. You can go back in time and see how many followers you had at 4pm on Thursday and how may you had at 1am on Sunday. Now you know if that “joke tweet” you did at 2pm cost yourself 8 followers, or gained you 6.

Enjoy!

Reduce the Number of Cookies SiteCatalyst sets with Cookie Combining Plugin

The more advanced we get with our SiteCatalyst implementation, the greater the amount of cookies that can be set. Every time you use a getValOnce a cookie is set. If you are using that a bunch there can easily be over 20 cookies set on a single page ( I have seen as many as 45). If this is a concern for you, then Cookie Combining Utility to the rescue.

This one is pretty easy to implement. Just add both chunks to the Plugin section of your s_code.js file and that’s all there is too it.

/*
 * Function - read combined cookies
 */
s.c_rr=s.c_r;
s.c_r=new Function("k",""
+"var s=this,d=new Date,v=s.c_rr(k),c=s.c_rr('s_pers'),i,m,e;if(v)ret"
+"urn v;k=s.ape(k);i=c.indexOf(' '+k+'=');c=i<0?s.c_rr('s_sess'):c;i="
+"c.indexOf(' '+k+'=');m=i<0?i:c.indexOf('|',i);e=i<0?i:c.indexOf(';'"
+",i);m=m>0?m:e;v=i<0?'':s.epa(c.substring(i+2+k.length,m<0?c.length:"
+"m));if(m>0&&m!=e)if(parseInt(c.substring(m+1,e<0?c.length:e))<d.get"
+"Time()){d.setTime(d.getTime()-60000);s.c_w(s.epa(k),'',d);v='';}ret"
+"urn v;");

/*
 * Function - write combined cookies
 */
s.c_wr=s.c_w;
s.c_w=new Function("k","v","e",""
+"var s=this,d=new Date,ht=0,pn='s_pers',sn='s_sess',pc=0,sc=0,pv,sv,"
+"c,i,t;d.setTime(d.getTime()-60000);if(s.c_rr(k)) s.c_wr(k,'',d);k=s"
+".ape(k);pv=s.c_rr(pn);i=pv.indexOf(' '+k+'=');if(i>-1){pv=pv.substr"
+"ing(0,i)+pv.substring(pv.indexOf(';',i)+1);pc=1;}sv=s.c_rr(sn);i=sv"
+".indexOf(' '+k+'=');if(i>-1){sv=sv.substring(0,i)+sv.substring(sv.i"
+"ndexOf(';',i)+1);sc=1;}d=new Date;if(e){if(e.getTime()>d.getTime())"
+"{pv+=' '+k+'='+s.ape(v)+'|'+e.getTime()+';';pc=1;}}else{sv+=' '+k+'"
+"='+s.ape(v)+';';sc=1;}if(sc) s.c_wr(sn,sv,0);if(pc){t=pv;while(t&&t"
+".indexOf(';')!=-1){var t1=parseInt(t.substring(t.indexOf('|')+1,t.i"
+"ndexOf(';')));t=t.substring(t.indexOf(';')+1);ht=ht<t1?t1:ht;}d.set"
+"Time(ht);s.c_wr(pn,pv,d);}return v==s.c_r(s.epa(k));");

Just add that code and watch the number of cookies being set on each page drop right down.

Using Visitor Intentions To Track Your Web Site

Implementing basic web analytics on your web site should be pretty straight forward. Add the code to every page and your on your way. Once you start getting creative with your site elements is when you really need to look at which elements you are tracking as basic traffic metrics. Just blindly adding tracking code to every single page you have listed on your server can lead to miscounting what is really going on. Let me explain.

The two biggest things that I have seen a problem with is pop-ups and iframes. These elements while both technically can be additional pages to your site, they should not be considered as additional page views. The main thing to consider here is visitor intention. Consider the following example. If you take a look at this page on Boat Trader.com, you will see a form at the bottom of the page. While everything looks nice and seamless, that form is actually a iframed in page. Even though you are viewing two pages here, the visitors intention is not to see two pages but a single page. That iframed form should not be tracked as a page view. This can easily be overlooked if adding code to everything you see. Still looking at that page we see a link near the top of the page that says “Send to Friend”. Clicking this brings up a pop-up form which is also another seperate page, presented as a pop-up. Again, the visitor did not intend to view another page of the site so that pop-up should also not be considered another page view of the site. All of this should be pretty simple to figure out. Nothing really ground breaking yet.

When I talk about this to others the largest debate I come across is tracking outsourced sections of your site. Content syndication has been around for a long time and comes in many different forms. But when it comes to tracking these pages is where it gets a little hairly and we need to rely on visitor intention to help us out.

Lets look at another example. If you visit Walmart’s site, you will see a link on their left nav, for Job Classifieds. Clicking on that link takes you to a Walmart Job Classified page. Now this page is completely outsourced and is actually a subdomain of Oodle. Looking at the page it is branded and styled to look just like a page of walmart.com. Walmart’s intention is to let the user feel like they are still on their own site, and this is just another page. Walmart should count this as another page view of their site. Now lets look at this from Oodle’s side of things. This page is ‘technically’ a part of oodle.com, with all of Oodle’s content. So what do they do? Does Oodle get credit for another visit, visitor or page view? Looking at the visitors intentions, they never intended to visit oodle.com. They intended to visit walmart.com. Walmart did all the legwork to attract that visitor to their site, and should get credit for everything that happens with that visitor. That is Walmarts visit, visitor and page view and should be credited toward the walmart.com report suite.

But that is Oodle’s content. It is Oodle’s advertisers that are getting impressions and the leads. What do they get out of the deal? How does Oodle tell their advertisers that their ad recieved 2 leads with 0 visitors? This page still needs to be tracked by Oodle in some fashion, but in a separate report suite than the main site. To someone in this situation I would recommend that they create a new report suite and call it ‘affiliate network’. Then they have the ability to track all of the leads generated from this page to their advertisers, but without artificially inflating the number of visitors who actually intended to visit www.oodle.com. Then by using the SiteCatalyst Excel client you can bring in both site’s metrics on to a single spread sheet and you have the ability to report on the performance of main site and the affiliate network of sites.

I can see where it would be tempting by the crew over at Oodle to call that another page view of their site to bump up their traffic numbers, but that is misleading. Our sites are built for our visitors, and should be tracked using their intentions.