Add GA tracking for Gold pages

This commit is contained in:
jo-asakura
2015-03-12 09:45:57 -07:00
parent d60b1fc6bf
commit f3147ee639
7 changed files with 251 additions and 141 deletions

View File

@@ -134,6 +134,10 @@ googleanalytics =
# google analytics events sampling rate. Valid values are 1-100.
# See https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiBasicConfiguration#_gat.GA_Tracker_._setSampleRate
googleanalytics_sample_rate = 50
# google analytics token for gold
googleanalytics_gold =
# google analytics events sampling rate for gold. Valid values are 1-100.
googleanalytics_sample_rate_gold = 100
# secret used for signing information on the above tracking pixels
tracking_secret = abcdefghijklmnopqrstuvwxyz0123456789

View File

@@ -35,8 +35,9 @@ from r2.controllers.reddit_base import (
from r2 import config
from r2.models import *
from r2.models.recommend import ExploreSettings
from r2.config import feature
from r2.config.extensions import is_api
from r2.lib import hooks, recommender, embeds
from r2.lib import hooks, recommender, embeds, pages
from r2.lib.pages import *
from r2.lib.pages.things import hot_links_by_url_listing
from r2.lib.pages import trafficpages
@@ -262,6 +263,16 @@ class FrontController(RedditController):
if embed_key:
embeds.set_up_embed(embed_key, sr, comment, showedits=showedits)
# Temporary hook until IAMA app "OP filter" is moved from partners
# Not to be open-sourced
page = hooks.get_hook("comments_page.override").call_until_return(
controller=self,
article=article,
limit=limit,
)
if page:
return page
# If there is a focal comment, communicate down to
# comment_skeleton.html who that will be. Also, skip
# comment_visits check
@@ -365,11 +376,16 @@ class FrontController(RedditController):
if c.site.allows_referrers:
c.referrer_policy = "always"
suggested_sort_active = False
suggested_sort = article.sort_if_suggested() if feature.is_enabled('default_sort') else None
if article.contest_mode:
if c.user_is_loggedin and sr.is_moderator(c.user):
sort = "top"
else:
sort = "random"
elif suggested_sort and 'sort' not in request.params:
sort = suggested_sort
suggested_sort_active = True
# finally add the comment listing
displayPane.append(CommentPane(article, CommentSortMenu.operator(sort),
@@ -396,13 +412,24 @@ class FrontController(RedditController):
self._add_show_comments_link(subtitle_buttons, article, num,
g.max_comments_gold, gold=True)
sort_menu = CommentSortMenu(
default=sort,
css_class='suggested' if suggested_sort_active else '',
suggested_sort=suggested_sort,
)
link_settings = LinkCommentsSettings(
article,
sort=sort,
suggested_sort=suggested_sort,
)
res = LinkInfoPage(link=article, comment=comment,
content=displayPane,
page_classes=['comments-page'],
subtitle=subtitle,
subtitle_buttons=subtitle_buttons,
nav_menus=[CommentSortMenu(default=sort),
LinkCommentsSettings(article)],
nav_menus=[sort_menu, link_settings],
infotext=infotext).render()
return res
@@ -1206,7 +1233,9 @@ class FrontController(RedditController):
return BoringPage(_("thanks"), show_sidebar=False,
content=GoldThanks(claim_msg=claim_msg,
vendor_url=vendor_url,
lounge_md=lounge_md)).render()
lounge_md=lounge_md),
page_classes=["gold-page-ga-tracking"]
).render()
@validate(VUser(),
token=VOneTimeToken(AwardClaimToken, "code"))
@@ -1385,7 +1414,11 @@ class FormsController(RedditController):
content = None
infotext = None
if not location or location == 'options':
content = PrefOptions(done=request.GET.get('done'))
content = PrefOptions(
done=request.GET.get('done'),
error_style_override=request.GET.get('error_style_override'),
generic_error=request.GET.get('generic_error'),
)
elif location == 'update':
if verified:
infotext = strings.email_verified
@@ -1550,7 +1583,9 @@ class FormsController(RedditController):
return BoringPage(_("reddit gold"),
show_sidebar=False,
content=content).render()
content=content,
page_classes=["gold-page-ga-tracking"]
).render()
@validate(is_payment=VBoolean("is_payment"),
goldtype=VOneOf("goldtype",
@@ -1639,7 +1674,7 @@ class FormsController(RedditController):
giftmessage,
can_subscribe=can_subscribe,
edit=edit),
page_classes=["gold-page", "gold-signup"],
page_classes=["gold-page", "gold-signup", "gold-page-ga-tracking"],
).render()
else:
# If we have a validating form, and we're not yet on the payment
@@ -1667,7 +1702,7 @@ class FormsController(RedditController):
passthrough = generate_blob(payment_blob)
page_classes = ["gold-page", "gold-payment"]
page_classes = ["gold-page", "gold-payment", "gold-page-ga-tracking"]
if goldtype == "creddits":
page_classes.append("creddits-payment")
@@ -1684,12 +1719,19 @@ class FormsController(RedditController):
return BoringPage(_("purchase creddits"),
show_sidebar=False,
content=Creddits(),
page_classes=["gold-page", "creddits-purchase"],
page_classes=["gold-page", "creddits-purchase", "gold-page-ga-tracking"],
).render()
@validate(VUser())
def GET_subscription(self):
user = c.user
content = GoldSubscription(user)
return BoringPage(_("reddit gold subscription"), show_sidebar=False,
content=content).render()
return BoringPage(_("reddit gold subscription"),
show_sidebar=False,
content=content,
page_classes=["gold-page-ga-tracking"]
).render()
class FrontUnstyledController(FrontController):
allow_stylesheets = False

View File

@@ -791,6 +791,9 @@ class Reddit(Templated):
self.welcomebar, self.infobar, self.locationbar, self.nav_menu,
self._content))
def is_gold_page(self):
return "gold-page-ga-tracking" in self.supplied_page_classes
def page_classes(self):
classes = set()

View File

@@ -87,7 +87,24 @@ r.analytics = {
fireFunnelEvent: function(category, action, options, callback) {
options = options || {};
callback = callback || function() {};
callback = callback || window.Function.prototype;
var page = '/' + _.compact([category, action, options.label]).join('-');
// if it's for Gold tracking and we have new _ga available
// then use it to track the event; otherwise, fallback to old version
if (options.tracker && '_ga' in window && window._ga.getByName(options.tracker)) {
window._ga(options.tracker + '.send', 'pageview', {
'page': page,
'hitCallback': callback
});
if (options.value) {
window._ga(options.tracker + '.send', 'event', category, action, options.label, options.value);
}
return;
}
if (!window._gaq || !this.shouldFireEvent.apply(this, arguments)) {
callback();
@@ -102,7 +119,7 @@ r.analytics = {
// Virtual page views are needed for a funnel to work with GA.
// see: http://gatipoftheday.com/you-can-use-events-for-goals-but-not-for-funnels/
_gaq.push(['_trackPageview', '/' + _.compact([category, action, options.label]).join('-')]);
_gaq.push(['_trackPageview', page]);
// The goal can have a conversion value in GA.
if (options.value) {

View File

@@ -28,39 +28,39 @@ r.gold = {
$('.stripe-gold').click(function(){
$("#stripe-payment").slideToggle()
})
});
$('#stripe-payment.charge .stripe-submit').on('click', function() {
r.gold.tokenThenPost('stripecharge/gold')
})
});
$('#stripe-payment.modify .stripe-submit').on('click', function() {
r.gold.tokenThenPost('modify_subscription')
})
});
$('h3.toggle').on('click', function() {
$(this).toggleClass('toggled')
$(this).siblings('.details').slideToggle()
})
$(this).toggleClass('toggled');
$(this).siblings('.details').slideToggle();
});
$('dt.toggle').on('click', function() {
$(this).toggleClass('toggled')
$(this).next('dd').slideToggle()
})
$(this).toggleClass('toggled');
$(this).next('dd').slideToggle();
});
if ($('body').hasClass('gold-signup')) {
r.gold.signupForm.init()
r.gold.signupForm.init();
}
$('form.creddits-gold .remaining').each(r.gold._renderCredditsAmount)
$('form.creddits-gold .remaining').each(r.gold._renderCredditsAmount);
$(document.body).on('submit', 'form.creddits-gold', function(e) {
e.preventDefault()
e.stopPropagation()
e.preventDefault();
e.stopPropagation();
r.gold._expendCreddits()
r.gold._expendCreddits();
$(this).find('.gold-checkout:not(.creddits-gold)').hide()
$(this).find('.gold-checkout:not(.creddits-gold)').hide();
return post_form(this, 'spendcreddits')
})
},
@@ -75,27 +75,29 @@ r.gold = {
},
_toggleThingGoldForm: function (e) {
var $link = $(e.target)
var $thing = $link.thing()
var thingFullname = $link.thing_id()
var wrapId = 'gold_wrap_' + thingFullname
var oldWrap = $('#' + wrapId)
var cloneClass
var $link = $(e.target);
var $thing = $link.thing();
var thingFullname = $link.thing_id();
var wrapId = 'gold_wrap_' + thingFullname;
var oldWrap = $('#' + wrapId);
var cloneClass;
if (oldWrap.length) {
oldWrap.toggle()
oldWrap.toggle();
return false
}
this._inlineGilding = true;
r.analytics.fireFunnelEvent('gold', 'open-inline-form');
r.analytics.fireFunnelEvent('gold', 'open-inline-form', {
tracker: 'goldTracker'
});
if (!this._googleCheckoutAnalyticsLoaded) {
// we're just gonna hope this loads fast enough since there's no
// way to know if it failed and we'd rather the form is still
// usable if things don't go well with the analytics stuff.
$.getScript('//checkout.google.com/files/digital/ga_post.js')
$.getScript('//checkout.google.com/files/digital/ga_post.js');
this._googleCheckoutAnalyticsLoaded = true
}
@@ -105,38 +107,38 @@ r.gold = {
cloneClass = 'cloneable-comment'
}
var goldwrap = $('.gold-wrap.' + cloneClass + ':first').clone()
var form = goldwrap.find('.gold-form')
var authorName = $link.thing().find('.entry .author:first').text()
var passthroughs = form.find('.passthrough')
var cbBaseUrl = form.find('[name="cbbaseurl"]').val()
var goldwrap = $('.gold-wrap.' + cloneClass + ':first').clone();
var form = goldwrap.find('.gold-form');
var authorName = $link.thing().find('.entry .author:first').text();
var passthroughs = form.find('.passthrough');
var cbBaseUrl = form.find('[name="cbbaseurl"]').val();
var signed = !(form.find('[name="signed"]')).is(':checked');
goldwrap
.removeClass(cloneClass)
.addClass('inline-gold')
.prop('id', wrapId)
.prop('id', wrapId);
form.find('p:first-child em').text(authorName)
form.find('button').attr('disabled', '')
passthroughs.val('')
form.find('p:first-child em').text(authorName);
form.find('button').attr('disabled', '');
passthroughs.val('');
$link.new_thing_child(goldwrap)
$link.new_thing_child(goldwrap);
// show the throbber if this takes longer than 200ms
var workingTimer = setTimeout(function () {
form.addClass('working')
form.addClass('working');
form.find('button').addClass('disabled')
}, 200)
}, 200);
$.request('generate_payment_blob.json', {thing: thingFullname, signed: signed}, function (token) {
clearTimeout(workingTimer)
form.removeClass('working')
passthroughs.val(token)
form.find('.stripe-gold').on('click', function() { window.open('/gold/creditgild/' + token) })
form.find('.coinbase-gold').on('click', function() { window.open(cbBaseUrl + "?c=" + token) })
clearTimeout(workingTimer);
form.removeClass('working');
passthroughs.val(token);
form.find('.stripe-gold').on('click', function() { window.open('/gold/creditgild/' + token) });
form.find('.coinbase-gold').on('click', function() { window.open(cbBaseUrl + "?c=" + token) });
form.find('button').removeAttr('disabled').removeClass('disabled')
})
});
return false
},
@@ -163,10 +165,12 @@ r.gold = {
giftmessage = ($goldwrap.find('[name="giftmessage"]')).val();
}
var vendor = $button.closest('[data-vendor]').data('vendor');
if (this._inlineGilding) {
r.analytics.fireFunnelEvent('gold', 'checkout', vendor);
var options = {
label: $button.closest('[data-vendor]').data('vendor'),
tracker: 'goldTracker'
};
r.analytics.fireFunnelEvent('gold', 'checkout', options);
}
$.request('modify_payment_blob.json', {code: code, signed: signed, message: giftmessage}, function() {
@@ -178,36 +182,36 @@ r.gold = {
// new total creddits remaining, or hide it if we have less than the current cost of gilding (1 creddit).
_expendCreddits: function() {
$('.cloneable-comment, .cloneable-link').find('form.creddits-gold .remaining').each(function() {
var $this = $(this)
var currentCreddits = parseInt($this.data('current'), 10)
var totalCreddits = parseInt($this.data('total'), 10)
var newTotal = totalCreddits - currentCreddits
var $this = $(this);
var currentCreddits = parseInt($this.data('current'), 10);
var totalCreddits = parseInt($this.data('total'), 10);
var newTotal = totalCreddits - currentCreddits;
if (newTotal < currentCreddits) {
$this.parents('form.creddits-gold').remove()
} else {
$(this).data('total', newTotal)
$(this).data('total', newTotal);
r.gold._renderCredditsAmount.apply(this)
}
})
},
_renderCredditsAmount: function() {
var $this = $(this)
var tpl = $this.data('template')
var $this = $(this);
var tpl = $this.data('template');
$this.html(_.template(tpl, _.omit($this.data(), 'template')))
},
gildThing: function (thing_fullname, new_title, specified_gilding_count) {
var thing = $('.id-' + thing_fullname)
var thing = $('.id-' + thing_fullname);
if (!thing.length) {
console.log("couldn't gild thing " + thing_fullname)
console.log("couldn't gild thing " + thing_fullname);
return
}
var tagline = thing.children('.entry').find('p.tagline'),
icon = tagline.find('.gilded-icon')
icon = tagline.find('.gilded-icon');
// when a thing is gilded interactively, we need to increment the
// gilding count displayed by the UI. however, when gildings are
@@ -216,23 +220,23 @@ r.gold = {
// cached comment page already includes the gilding in its count. To
// resolve this ambiguity, thingupdater will provide the correct
// gilding count as specified_gilding_count when calling this function.
var gilding_count
var gilding_count;
if (specified_gilding_count != null) {
gilding_count = specified_gilding_count
} else {
gilding_count = icon.data('count') || 0
gilding_count = icon.data('count') || 0;
gilding_count++
}
thing.addClass('gilded user-gilded')
thing.addClass('gilded user-gilded');
if (!icon.length) {
icon = $('<span>')
.addClass('gilded-icon')
.addClass('gilded-icon');
tagline.append(icon)
}
icon
.attr('title', new_title)
.data('count', gilding_count)
.data('count', gilding_count);
if (gilding_count > 1) {
icon.text('x' + gilding_count)
}
@@ -245,16 +249,16 @@ r.gold = {
var form = $('#stripe-payment'),
submit = form.find('.stripe-submit'),
status = form.find('.status'),
token = form.find('[name="stripeToken"]')
token = form.find('[name="stripeToken"]');
if (response.error) {
submit.removeAttr('disabled')
submit.removeAttr('disabled');
status.html(response.error.message)
} else {
token.val(response.id)
token.val(response.id);
post_form(form, dest)
}
}
};
r.gold.makeStripeToken(postOnSuccess)
},
@@ -274,15 +278,15 @@ r.gold = {
cardCity = form.find('.card-address_city').val(),
cardState = form.find('.card-address_state').val(),
cardCountry = form.find('.card-address_country').val(),
cardZip = form.find('.card-address_zip').val()
Stripe.setPublishableKey(publicKey)
cardZip = form.find('.card-address_zip').val();
Stripe.setPublishableKey(publicKey);
var showError = function(inputSelector, str) {
form.find('.status')
.addClass('error')
.text(str)
.text(str);
$(inputSelector).focus()
}
};
if (!cardName) {
showError('.card-name', r._('missing name'))
@@ -301,8 +305,8 @@ r.gold = {
} else {
status
.removeClass('error')
.text(reddit.status_msg.submitting)
submit.attr('disabled', 'disabled')
.text(reddit.status_msg.submitting);
submit.attr('disabled', 'disabled');
Stripe.createToken({
name: cardName,
number: cardNumber,
@@ -320,31 +324,31 @@ r.gold = {
}
return false
}
}
};
r.gold.signupForm = (function() {
// Get all field names relevant to this goldtype.
// This helps us keep a clean URL state.
function _getRelevantFields() {
var goldtype = $('#goldtype').val()
var fields = ['goldtype']
var goldtype = $('#goldtype').val();
var fields = ['goldtype'];
switch (goldtype) {
case 'autorenew':
fields.push('period')
break
fields.push('period');
break;
case 'onetime':
fields.push('months')
break
fields.push('months');
break;
case 'code':
fields.push('months', 'email')
break
fields.push('months', 'email');
break;
case 'gift':
fields.push('months', 'recipient', 'signed', 'giftmessage')
break
fields.push('months', 'recipient', 'signed', 'giftmessage');
break;
case 'creddits':
fields.push('num_creddits')
fields.push('num_creddits');
break
}
@@ -353,7 +357,7 @@ r.gold.signupForm = (function() {
// Given a field, get its value, regardless of input type.
function _getFieldValue(field) {
var $field = $(field)
var $field = $(field);
if ($field.is(':radio') && !$field.is(':checked')) {
throw 'Unchecked radio button has no value'
@@ -371,9 +375,9 @@ r.gold.signupForm = (function() {
}
function _updateUrlState() {
var a = $("<a />").get(0)
var urlFields = _getRelevantFields()
var params = {}
var a = $("<a />").get(0);
var urlFields = _getRelevantFields();
var params = {};
if (!('replaceState' in window.history)) {
return
@@ -391,43 +395,43 @@ r.gold.signupForm = (function() {
} catch(e) {
return
}
})
});
params['edit'] = true
params['edit'] = true;
a.href = window.location.href
a.search = $.param(params)
a.href = window.location.href;
a.search = $.param(params);
window.history.replaceState({}, "", a.href)
}
function _updateGoldType() {
var $gifttype = $('input[name="gifttype"]:checked')
var $tab = $('.tab.active')
var isGift = $('#gift').is(':checked')
var goldtype
var $gifttype = $('input[name="gifttype"]:checked');
var $tab = $('.tab.active');
var isGift = $('#gift').is(':checked');
var goldtype;
if ($tab.prop('id') == 'autorenew') {
goldtype = 'autorenew'
} else if (isGift && $gifttype.length > 0) {
goldtype = $gifttype.val()
} else if ($tab.prop('id') == 'creddits') {
goldtype = 'creddits'
} else if (isGift && $gifttype.length > 0) {
goldtype = $gifttype.val()
} else {
goldtype = 'onetime'
}
$('#goldtype').val(goldtype)
$('#goldtype').val(goldtype);
_updateUrlState()
}
function _setTabFocus(tab) {
$('#form-options, #payment-options').show()
$('#form-options, #payment-options').show();
$('.active').removeClass('active')
$('#redeem-a-code, .question').hide()
$('.active').removeClass('active');
$('#redeem-a-code, .question').hide();
$(tab).addClass('active')
$(tab.hash).addClass('active')
$(tab).addClass('active');
$(tab.hash).addClass('active');
_updateGoldType()
}
@@ -435,59 +439,66 @@ r.gold.signupForm = (function() {
// On submit, pass only the relevant fields to the payment page, for clean URLs and proper
// display of the payment summary.
function _handleSubmit(e) {
e.stopPropagation()
e.preventDefault()
e.stopPropagation();
e.preventDefault();
/* Our IE placeholder handling is miserable, clear out placeholder text before submission if we have it. */
$('#giftmessage, #recipient').each(function() {
var $this = $(this)
var $this = $(this);
if ($this.val() === $this.attr('placeholder')) {
$this.val('')
}
})
});
// serializeArray returns an array of objects, turn it into key/value pairs
// since we're not worried about multi-value keys and it's what $.param expects
var fields = $('form.gold-form').serializeArray()
var fieldsAsDict = _.object(_.pluck(fields, 'name'), _.pluck(fields, 'value'))
var fields = $('form.gold-form').serializeArray();
var fieldsAsDict = _.object(_.pluck(fields, 'name'), _.pluck(fields, 'value'));
// Only submit fields that are relevant to this goldtype
var submission = _.pick(fieldsAsDict, _getRelevantFields())
var submission = _.pick(fieldsAsDict, _getRelevantFields());
window.location = "/gold/payment?" + $.param(submission)
}
function init() {
var $form = $('form.gold-form')
var $form = $('form.gold-form');
$('a.tab-toggle').on('click', function(e) {
e.stopPropagation()
e.preventDefault()
e.stopPropagation();
e.preventDefault();
_setTabFocus(this)
})
});
$('input[name="gift"]').change(function() {
$('#gifting-details').slideToggle($(this).val())
$('#gifting-details').slideToggle($(this).val());
_updateGoldType()
})
});
var hasPlaceholder = ('placeholder' in document.createElement('input'))
// Workaround for our form cloning to maintain back buttons. When we clone the form
// the selected attribute is respected more than the current state unless we explicitly alter it
$('.gold-dropdown').on('change', function() {
$(this).find('[selected]').removeAttr('selected');
$(this).find(':selected').get(0).setAttribute('selected', 'selected')
});
var hasPlaceholder = ('placeholder' in document.createElement('input'));
$('input[name="gifttype"]').change(function() {
$('#gifttype-details-gift').toggleClass('hidden', this.value !== 'gift')
$('#gifttype-details-gift').toggleClass('hidden', this.value !== 'gift');
if (hasPlaceholder) {
$('#gifttype-details-gift :input:eq(0)').focus()
}
_updateGoldType()
})
});
$('#giftmessage').on('keyup', function() {
$('#message').prop('checked', $(this).val() !== '')
})
});
$form.on('submit', _handleSubmit)
$form.on('submit', _handleSubmit);
$form.find(':input').on('change', _updateUrlState)
$form.find(':input').on('change', _updateUrlState);
$('input[name="code"]').on('focus', function() {
$('.redeem-submit').slideDown()
@@ -495,13 +506,13 @@ r.gold.signupForm = (function() {
}
return {
'init': init,
'init': init
}
}())
}());
!(function($) {
$.gild_thing = function (thing_fullname, new_title) {
r.gold.gildThing(thing_fullname, new_title)
r.gold.gildThing(thing_fullname, new_title);
$('#gold_wrap_' + thing_fullname).fadeOut(400)
}
})(jQuery)
})(jQuery);

View File

@@ -99,7 +99,7 @@ reddit, reddit.com, vote, comment, submit
</script>
%endif
${googleanalytics('web')}
${googleanalytics('web', thing.is_gold_page() if hasattr(thing, 'is_gold_page') else False)}
</%def>
<%def name="pagemeta()">

View File

@@ -397,11 +397,18 @@ ${unsafe(txt)}
</script>
</%def>
<%def name="googleanalytics(uitype)">
<%def name="googleanalytics(uitype, is_gold_page=False)">
%if (g.googleanalytics or g.googleanalytics_gold) and thing.site_tracking:
<script type="text/javascript">
window.user_type = '${"guest" if not c.user_is_loggedin else "goldloggedin" if c.user.gold else "loggedin"}';
window.is_gold_page = '${is_gold_page}'.toLowerCase() === 'true';
</script>
%endif
%if g.googleanalytics and thing.site_tracking:
## it uses old ga.js
<script type="text/javascript">
var user_type = '${"guest" if not c.user_is_loggedin else "goldloggedin" if c.user.gold else "loggedin"}';
var _gaq = _gaq || [];
_gaq.push(
['_require', 'inpage_linkid', '//www.google-analytics.com/plugins/ga/inpage_linkid.js'],
@@ -425,6 +432,32 @@ ${unsafe(txt)}
</script>
%endif
%if g.googleanalytics_gold and thing.site_tracking:
## it uses new analytics.js
<script type="text/javascript">
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','_ga');
window._ga('create', '${g.googleanalytics_gold}', {
'name': 'goldTracker',
'cookieDomain': '${g.domain}',
'1': '${tracking.get_site()}',
'2': '${tracking.get_srpath()}',
'3': window.user_type,
'4': '${uitype}',
'sampleRate': ${g.googleanalytics_sample_rate_gold}
});
if (window.is_gold_page) {
window._ga('goldTracker.send', 'pageview');
}
</script>
%endif
</%def>
<%def name="logout(top=False,dest=None,a_class='')">