diff --git a/includes/components/pagetemplate.class.php b/includes/components/pagetemplate.class.php
index a688114a..d05e0267 100644
--- a/includes/components/pagetemplate.class.php
+++ b/includes/components/pagetemplate.class.php
@@ -27,14 +27,14 @@ class PageTemplate
private array $pageData = []; // processed by display hooks
// template data that needs further processing .. ! WARNING ! they will not get aut fetched from $context as they are already defined here
- private string $gStaticUrl;
- private string $gHost;
- private string $gServerTime;
- private string $gUser;
- private string $gFavorites;
- private ?string $analyticsTag = null;
- private bool $consentFooter = false;
- private string $dbProfiles = '';
+ private string $gStaticUrl;
+ private string $gHost;
+ private string $gServerTime;
+ private string $gUser;
+ private string $gFavorites;
+ private bool $hasAnalytics = false;
+ private bool $consentFooter = false;
+ private string $dbProfiles = '';
private readonly string $user; // becomes User object
@@ -49,7 +49,7 @@ class PageTemplate
$this->locale = Lang::getLocale();
$this->gStaticUrl = Cfg::get('STATIC_URL');
$this->gHost = Cfg::get('HOST_URL');
- $this->analyticsTag = Cfg::get('GTAG_MEASUREMENT_ID');
+ $this->hasAnalytics = !!Cfg::get('GTAG_MEASUREMENT_ID');
$this->gServerTime = sprintf("new Date('%s')", date(Util::$dateFormatInternal));
$this->user = User::class;
}
@@ -472,16 +472,16 @@ class PageTemplate
private function update() : void
{
// analytics + consent
- if ($this->analyticsTag && !isset($_COOKIE['consent']))
+ if ($this->hasAnalytics && !isset($_COOKIE['consent']))
{
$this->addScript(SC_CSS_FILE, 'css/consent.css');
$this->addScript(SC_JS_FILE, 'js/consent.js');
$this->consentFooter = true;
- $this->analyticsTag = null;
+ $this->hasAnalytics = false;
}
- else if ($this->analyticsTag && !$_COOKIE['consent'])
- $this->analyticsTag = null;
+ else if ($this->hasAnalytics && !$_COOKIE['consent'])
+ $this->hasAnalytics = false;
// js + css
$this->prepareScripts();
@@ -526,7 +526,7 @@ class PageTemplate
{
$this->gStaticUrl = Cfg::get('STATIC_URL');
$this->gHost = Cfg::get('HOST_URL');
- $this->analyticsTag = Cfg::get('GTAG_MEASUREMENT_ID');
+ $this->hasAnalytics = !!Cfg::get('GTAG_MEASUREMENT_ID');
$this->gServerTime = sprintf("new Date('%s')", date(Util::$dateFormatInternal));
}
diff --git a/setup/sql/updates/1765569409_01.sql b/setup/sql/updates/1765569409_01.sql
new file mode 100644
index 00000000..03adf418
--- /dev/null
+++ b/setup/sql/updates/1765569409_01.sql
@@ -0,0 +1 @@
+UPDATE `aowow_dbversion` SET `build` = CONCAT(IFNULL(`build`, ''), ' globaljs');
diff --git a/setup/tools/filegen/templates/global.js/announcement.js b/setup/tools/filegen/templates/global.js/announcement.js
index 0149b984..82628760 100644
--- a/setup/tools/filegen/templates/global.js/announcement.js
+++ b/setup/tools/filegen/templates/global.js/announcement.js
@@ -110,7 +110,12 @@ Announcement.prototype = {
// aowow - animation fix - jQuery.animate hard snaps into place after half the time passed
this.parentDiv.style.opacity = '100';
this.parentDiv.style.height = (this.parentDiv.offsetHeight + 10) + 'px';
- g_trackEvent('Announcements', 'Show', '' + this.name);
+
+ $WH.Track.nonInteractiveEvent({
+ category: 'Announcements',
+ action: 'Show',
+ label: '' + this.name
+ });
},
hide: function()
@@ -132,7 +137,11 @@ Announcement.prototype = {
markRead: function()
{
- g_trackEvent('Announcements', 'Close', '' + this.name);
+ $WH.Track.interactiveEvent({
+ category: 'Announcements',
+ action: 'Close',
+ label: '' + this.name
+ });
g_setWowheadCookie('announcement-' + this.id, 'closed');
this.hide();
},
@@ -147,11 +156,22 @@ Announcement.prototype = {
{
this.text = text;
Markup.printHtml(this.text, this.parent + '-markup');
- g_addAnalyticsToNode($WH.ge(this.parent + '-markup'), {
- 'category': 'Announcements',
- 'actions': {
- 'Follow link': function(node) { return true; }
- }
- }, this.id);
+
+ let parent = $WH.ge(this.parent + '-markup');
+ $WH.qsa('a', parent).forEach(link => {
+ $WH.aE(link, 'click', () => {
+ let label = 'unknown';
+ let txt = g_getFirstTextContent(link);
+ if (txt)
+ label = g_urlize(txt).substr(0, 80);
+ else if (link.title)
+ label = g_urlize(link.title).substr(0, 80);
+ else if (link.id)
+ label = g_urlize(link.id).substr(0, 80);
+
+ label = `${ this.id || 0 }-${ label }`;
+ $WH.Track.linkClick(link, { category: 'Announcements', label: label });
+ });
+ });
}
};
diff --git a/setup/tools/filegen/templates/global.js/listview_templates.js b/setup/tools/filegen/templates/global.js/listview_templates.js
index 2c3dcb85..d05913fd 100644
--- a/setup/tools/filegen/templates/global.js/listview_templates.js
+++ b/setup/tools/filegen/templates/global.js/listview_templates.js
@@ -3296,7 +3296,7 @@ Listview.templates = {
return Listview.funcBox.assocArrCmp(a.skill, b.skill, g_spell_skills);
}
},
- /* AoWoW: custom start */
+ /* AoWoW: custom start */
{
id: 'stackRules',
name: LANG.asr_behaviour,
@@ -3578,7 +3578,7 @@ Listview.templates = {
return 0;
}
},
- /* AoWoW: custom end */
+ /* AoWoW: custom end */
{
id: 'completed', // Listview.COLUMN_ID_COMPLETION
name: LANG.completion, // WH.TERMS.completion
@@ -7350,7 +7350,7 @@ Listview.templates = {
$(td).mouseover(function (event, menu) { $WH.Tooltip.showAtCursor(menu, event, 0, 0); }.bind(td, tt));
$(td).mousemove(function (event) { $WH.Tooltip.cursorUpdate(event); })
.mouseout(function () { $WH.Tooltip.hide(); });
-/* aowow - we dont do patches
+ /* aowow - we dont do patches
var g = typeof g_hearthhead != "undefined" && g_hearthhead ? "hearthstone" : "wow";
if (!g_getPatchVersionObject.hasOwnProperty("parsed") || !g_getPatchVersionObject.parsed[g]) {
g_getPatchVersionObject();
@@ -7369,7 +7369,7 @@ Listview.templates = {
j = j.replace(f, f + " (" + new Date(c.timestamp).toDateString() + ")");
}
}
-*/
+ */
let j = changelog.version; // aowow - tmp
$(td).html(j);
},
diff --git a/setup/tools/filegen/templates/global.js/mapviewer.js b/setup/tools/filegen/templates/global.js/mapviewer.js
index 2f6ad712..59cec45b 100644
--- a/setup/tools/filegen/templates/global.js/mapviewer.js
+++ b/setup/tools/filegen/templates/global.js/mapviewer.js
@@ -276,6 +276,12 @@ var MapViewer = new function()
this.show = function(opt)
{
+ $WH.Track.interactiveEvent({
+ category: "Zone Maps",
+ action: "Show",
+ label: opt.link ? opt.link : "General"
+ });
+
if (opt.link)
{
tempParent = $WH.ce('div');
diff --git a/setup/tools/filegen/templates/global.js/menu.js b/setup/tools/filegen/templates/global.js/menu.js
index c2416ae6..f65be754 100644
--- a/setup/tools/filegen/templates/global.js/menu.js
+++ b/setup/tools/filegen/templates/global.js/menu.js
@@ -792,7 +792,7 @@ var Menu = new function()
});
explodeInto(menu, implodedMenu);
- };
+ }
function explodeInto(menu, implodedMenu) // Reverse of implode
{
diff --git a/setup/tools/filegen/templates/global.js/modelviewer.js b/setup/tools/filegen/templates/global.js/modelviewer.js
index 182dffa6..0c078614 100644
--- a/setup/tools/filegen/templates/global.js/modelviewer.js
+++ b/setup/tools/filegen/templates/global.js/modelviewer.js
@@ -604,7 +604,11 @@ var ModelViewer = new function()
}
}
- g_trackEvent('Model Viewer', 'Show', g_urlize(trackCode));
+ $WH.Track.interactiveEvent({
+ category: 'Model Viewer',
+ action: 'Show',
+ label: g_urlize(trackCode) // WH.Strings.slug(trackCode)
+ });
oldHash = location.hash;
}
diff --git a/setup/tools/filegen/templates/global.js/screenshots.js b/setup/tools/filegen/templates/global.js/screenshots.js
index 44ea902d..3a42f3c8 100644
--- a/setup/tools/filegen/templates/global.js/screenshots.js
+++ b/setup/tools/filegen/templates/global.js/screenshots.js
@@ -182,7 +182,11 @@ var ScreenshotViewer = new function()
if (!resizing)
{
- g_trackEvent('Screenshots', 'Show', screenshot.id + ( (screenshot.caption && screenshot.caption.length) ? ' (' + screenshot.caption + ')' : ''));
+ $WH.Track.interactiveEvent({
+ category: 'Screenshots',
+ action: 'Show',
+ label: screenshot.id + (screenshot.caption && screenshot.caption.length ? ` (${ screenshot.caption })` : '')
+ });
// ORIGINAL
diff --git a/setup/tools/filegen/templates/global.js/tabs.js b/setup/tools/filegen/templates/global.js/tabs.js
index 8f3406ca..c12767c0 100644
--- a/setup/tools/filegen/templates/global.js/tabs.js
+++ b/setup/tools/filegen/templates/global.js/tabs.js
@@ -359,6 +359,11 @@ Tabs.trackClick = function(tab)
if (!this.trackable || tab.tracked)
return;
- g_trackEvent('Tabs', 'Show', this.trackable + ': ' + tab.id);
+ $WH.Track.interactiveEvent({
+ category: 'Tab Click',
+ action: 'Page: ' + this.trackable,
+ label: 'Tab: ' + tab.id
+ });
+
tab.tracked = 1;
}
diff --git a/setup/tools/filegen/templates/global.js/tracking.js b/setup/tools/filegen/templates/global.js/tracking.js
index 6fe22256..a91901c6 100644
--- a/setup/tools/filegen/templates/global.js/tracking.js
+++ b/setup/tools/filegen/templates/global.js/tracking.js
@@ -1,130 +1,207 @@
-/*
-TODO: Create "Tracking" class
-*/
-
-function g_trackPageview(tag)
+// https://developers.google.com/tag-platform/security/guides/consent
+$WH.Track = new function ()
{
- function track()
- {
- if (typeof ga == 'function')
- ga('send', 'pageview', tag);
+ const trackAction = 'Click';
+ const siteVariables = {
+ // adsBlocked: 3,
+ // adsUnblocked: 4,
+ loggedInUserIsPremium: 2,
+ userIsLoggedIn: 1,
+ // userShouldSeeAds: 5
+ };
+ const maxRetryTime = 10000;
+ const retryTimeout = 10;
+ const scrollDepthPoints = [25, 50, 75, 90, 100];
+ const _self = {
+ gaReady: false,
+ scriptAdded: undefined
};
- $(document).ready(track);
-}
-
-function g_trackEvent(category, action, label, value)
-{
- function track()
+ this.gaInit = function (nTries)
{
- if (typeof ga == 'function')
- ga('send', 'event', category, action, label, value);
- };
-
- $(document).ready(track);
-}
-
-function g_attachTracking(node, category, action, label, value)
-{
- var $node = $(node);
-
- $node.click(function()
- {
- g_trackEvent(category, action, label, value);
- });
-}
-
-function g_addAnalytics()
-{
- var objs = {
- 'home-logo': {
- 'category': 'Homepage Logo',
- 'actions': {
- 'Click image': function(node) { return true; }
- }
- },
- 'home-featuredbox': {
- 'category': 'Featured Box',
- 'actions': {
- 'Follow link': function(node) { return (node.parentNode.className != 'home-featuredbox-links'); },
- 'Click image': function(node) { return (node.parentNode.className == 'home-featuredbox-links'); }
- }
- },
- 'home-oneliner': {
- 'category': 'Oneliner',
- 'actions': {
- 'Follow link': function(node) { return true; }
- }
- },
- 'sidebar-container': {
- 'category': 'Page sidebar',
- 'actions': {
- 'Click image': function(node) { return true; }
- }
- },
- 'toptabs-promo': {
- 'category': 'Page header',
- 'actions': {
- 'Click image': function(node) { return true; }
- }
- }
- };
-
- for (var i in objs)
- {
- var e = $WH.ge(i);
- if (e)
- g_addAnalyticsToNode(e, objs[i]);
- }
-}
-
-function g_getNodeTextId(node)
-{
- var id = null,
- text = g_getFirstTextContent(node);
-
- if (text)
- id = g_urlize(text);
- else if (node.title)
- id = g_urlize(node.title);
- else if (node.id)
- id = g_urlize(node.id);
-
- return id;
-}
-
-function g_addAnalyticsToNode(node, opts, labelPrefix)
-{
- if (!opts || !opts.actions || !opts.category)
- {
- if ($WH.isset('g_dev') && g_dev)
+ if (!_self.scriptAdded)
{
- console.log('Tried to add analytics event without appropriate parameters.');
- console.log(node);
- console.log(opts);
- }
-
- return;
- }
-
- var category = opts.category;
- var tags = $WH.gE(node, 'a');
- for (var i = 0; i < tags.length; ++i)
- {
- var node = tags[i];
- var action = 'Follow link';
- for (var a in opts.actions)
- {
- if (opts.actions[a] && opts.actions[a](node))
+ (function (_window, _document, node, src, varName, gaJSNode, firstJSNode)
{
- action = a;
- break;
- }
+ _window['GoogleAnalyticsObject'] = varName;
+ _window[varName] = _window[varName] || function () { (_window[varName].q = _window[varName].q || []).push(arguments) },
+ _window[varName].l = 1 * new Date;
+
+ gaJSNode = _document.createElement(node),
+ firstJSNode = _document.getElementsByTagName(node)[0];
+ gaJSNode.async = 1;
+ gaJSNode.src = src;
+ firstJSNode.parentNode.insertBefore(gaJSNode, firstJSNode);
+ })(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga');
+
+ let attachGTAG = () => {
+ let script = document.createElement('script');
+ script.async = true;
+ script.src = 'https://www.googletagmanager.com/gtag/js?id=CFG_GTAG_MEASUREMENT_ID';
+ document.body.appendChild(script);
+ };
+
+ if (document.body)
+ attachGTAG();
+ else
+ $WH.aE(document, 'DOMContentLoaded', attachGTAG);
+
+ window.dataLayer = window.dataLayer || [];
+ window.gtag = function () { dataLayer.push(arguments) };
+
+ gtag('js', new Date);
+ gtag('config', 'CFG_GTAG_MEASUREMENT_ID');
+
+ _self.scriptAdded = true;
}
- var label = (labelPrefix ? labelPrefix + '-' : '') + g_getNodeTextId(node);
- g_attachTracking(node, category, action, label);
+ if (!window.ga || !ga.create)
+ {
+ if (!nTries)
+ nTries = 1;
+
+ if (nTries > 100)
+ return;
+
+ setTimeout($WH.Track.gaInit.bind($WH.Track, nTries + 1), nTries * 9);
+ return;
+ }
+
+ ga('create', 'UA_MEASUREMENT_KEY', 'CFG_UA_MEASUREMENT_KEY');
+ // trackSiteVar(siteVariables.userShouldSeeAds, $WH.WAS.showAds());
+ trackSiteVar(siteVariables.userIsLoggedIn, /* $WH.User.isLoggedIn() */g_user.id > 0);
+ // if ($WH.User.isLoggedIn())
+ if (g_user.id > 0)
+ trackSiteVar(siteVariables.loggedInUserIsPremium, /* $WH.User.isPremium() */g_user.premium);
+
+ ga('set', 'anonymizeIp', true);
+ ga('send', 'pageview');
+
+ _self.gaReady = true;
+ scrollDepthPoints.forEach(registerTrackScroll);
+ };
+
+ this.interactiveEvent = evt => trackEvent(evt );
+ this.nonInteractiveEvent = evt => trackEvent(evt, { nonInteraction: true });
+ this.interactiveEventOutgoing = evt => trackEvent(evt, { isOutgoing: true });
+ this.linkClick = (anchor, evt) => trackEvent({ ...evt, action: trackAction, value: anchor.href }, { isOutgoing: true });
+
+ function trackSiteVar(idx, val)
+ {
+ ga('set', 'dimension' + idx, val)
}
-}
-$(document).ready(g_addAnalytics);
+ function registerTrackScroll(depth)
+ {
+ let trackDone = false;
+ const trackScroll = () => {
+ if (trackDone)
+ return;
+
+ trackDone = true;
+ requestAnimationFrame(() => {
+ const y = window.scrollY;
+ const h = document.documentElement.scrollHeight - document.documentElement.clientHeight;
+ if (y / h * 100 >= depth)
+ {
+ trackEvent({
+ action: 'scroll_event',
+ event_category: 'Scroll Depth',
+ event_label: `${ depth }%`,
+ scroll_depth: depth
+ });
+
+ window.removeEventListener('scroll', trackScroll, { passive: true })
+ }
+ trackDone = false;
+ });
+ };
+
+ window.addEventListener('scroll', trackScroll, { passive: true })
+ }
+
+ function trackEvent(evt, opts)
+ {
+ const { action: act, ...o } = evt;
+ const { category: cat, label: lab, value: val } = o;
+ const { nonInteraction: ni, isOutgoing: io } = opts || {};
+ let { retryCount: rc } = opts || {};
+
+ if (!_self.gaReady)
+ {
+ if ($WH.isset('g_dev') && g_dev)
+ return;
+
+ if (!rc)
+ rc = 0;
+
+ rc++;
+ if (rc * retryTimeout > maxRetryTime)
+ return;
+
+ setTimeout(trackEvent.bind(null, evt, {
+ nonInteraction: ni,
+ isOutgoing: io,
+ retryCount: rc
+ }), retryTimeout);
+
+ return;
+ }
+
+ let attr;
+ if (typeof ni === 'boolean')
+ {
+ attr ??= {};
+ attr.nonInteraction = ni ? 1 : 0;
+ }
+
+ if (io)
+ {
+ attr ??= {};
+ attr.transport = 'beacon';
+ }
+
+ if (cat)
+ ga('send', 'event', cat, act, lab, val, attr);
+
+ gtag('event', act, o);
+ }
+};
+
+// aowow - repurpose old tracking
+$(document).ready(function () {
+ var trackObjs = {
+ 'header-logo': { 'label': 'Database Logo', 'actions': { 'Click image': (node) => true } },
+ 'home-logo': { 'label': 'Homepage Logo', 'actions': { 'Click image': (node) => true } },
+ 'home-oneliner': { 'label': 'Oneliner', 'actions': { 'Follow link': (node) => true } },
+ 'home-featuredbox': { 'label': 'Featured Box', 'actions': { 'Follow link': (node) => node.parentNode.className != 'home-featuredbox-links',
+ 'Click image': (node) => node.parentNode.className == 'home-featuredbox-links' }
+ }
+ };
+
+ Object.entries(trackObjs).forEach(([nodeId, trackInfo]) => {
+ let parent = $WH.ge(nodeId);
+ if (!parent)
+ return;
+
+ $WH.qsa('a', parent).forEach(link => {
+ Object.entries(trackInfo.actions).forEach(([action, testFn]) => {
+ $WH.aE(link, 'click', evt => {
+ if (!testFn(link))
+ return;
+
+ let txt = 'unknown';
+ if (_ = g_getFirstTextContent(link))
+ txt = g_urlize(_).substr(0, 80);
+ else if (link.title)
+ txt = g_urlize(link.title).substr(0, 80);
+ else if (link.id)
+ txt = g_urlize(link.id).substr(0, 80);
+
+ label = `${trackInfo.label}-${action}-${txt}`;
+ $WH.Track.linkClick(link, { category: PageTemplate.get('pageName') || 'unknown', label: label });
+ });
+ });
+ });
+ });
+});
diff --git a/setup/tools/filegen/templates/global.js/videos.js b/setup/tools/filegen/templates/global.js/videos.js
index 5d8a858e..1e4664ae 100644
--- a/setup/tools/filegen/templates/global.js/videos.js
+++ b/setup/tools/filegen/templates/global.js/videos.js
@@ -176,7 +176,13 @@ var VideoViewer = new function()
if (!resizing)
{
- g_trackEvent('Videos', 'Show', video.id + (video.caption.length ? ' (' + video.caption + ')' : ''));
+ var hasCaption = (video.caption != null && video.caption.length);
+
+ $WH.Track.interactiveEvent({
+ category: 'Videos',
+ action: 'Show',
+ label: video.id + (hasCaption ? ` (${ video.caption })` : '')
+ });
if (video.videoType == 1)
imgDiv.innerHTML = Markup.toHtml('[youtube=' + video.videoId + ' width=' + imgWidth + ' height=' + imgHeight + ' autoplay=true]', {mode:Markup.MODE_ARTICLE});
@@ -249,7 +255,6 @@ var VideoViewer = new function()
// CAPTION
- var hasCaption = (video.caption != null && video.caption.length);
var hasSubject = (video.subject != null && video.subject.length && video.type && video.typeId);
if (hasCaption || hasSubject)
diff --git a/static/js/filters.js b/static/js/filters.js
index a09d5e6e..67c6e541 100644
--- a/static/js/filters.js
+++ b/static/js/filters.js
@@ -1088,15 +1088,20 @@ function fi_setCriteria(cr, crs, crv) {
var
i,
- c = _.childNodes[0].childNodes[0];
+ c = _.childNodes[0].childNodes[0],
+ s;
_ = c.getElementsByTagName('option');
for (i = 0; i < _.length; ++i) {
if (_[i].value == cr[0]) {
_[i].selected = true;
- if (fi_Lookup(cr[0])) {
- g_trackEvent('Filters', fi_type, fi_Lookup(cr[0]).name);
+ if (s = fi_Lookup(cr[0])) {
+ $WH.Track.nonInteractiveEvent({
+ category: "Filters",
+ action: fi_type, // vars.page,
+ label: s.name
+ });
}
break;
@@ -1108,8 +1113,12 @@ function fi_setCriteria(cr, crs, crv) {
for (i = 1; i < cr.length && i < 5; ++i) {
fi_criterionChange(fi_addCriterion(a, cr[i]), crs[i], crv[i]);
- if (fi_Lookup(cr[i])) {
- g_trackEvent('Filters', fi_type, fi_Lookup(cr[i]).name);
+ if (s = fi_Lookup(cr[i])) {
+ $WH.Track.nonInteractiveEvent({
+ category: "Filters",
+ action: fi_type, // vars.page,
+ label: s.name
+ });
}
}
}
diff --git a/static/js/home.js b/static/js/home.js
index ae6969c3..6de5865f 100644
--- a/static/js/home.js
+++ b/static/js/home.js
@@ -16,7 +16,7 @@ $(document).ready(function () {
$('.home-featuredbox-links a').hover(
function () { $(this).next('var').addClass('active') },
function () { $(this).next('var').removeClass('active') }
- ).click(function () { g_trackEvent('Featured Box', 'Click', this.title) }
- ).each( function () { g_trackEvent('Featured Box', 'Impression', this.title) }
- )
+ ).click(function () { $WH.Track.interactiveEvent({category: 'Featured Box', action: 'Click', label: this.title}) }
+ ).each( function () { $WH.Track.nonInteractiveEvent({category: 'Featured Box', action: 'Show', label: this.title}) }
+ );
});
diff --git a/template/bricks/head.tpl.php b/template/bricks/head.tpl.php
index 1f20cec2..2ecc6468 100644
--- a/template/bricks/head.tpl.php
+++ b/template/bricks/head.tpl.php
@@ -30,13 +30,9 @@ endif;
?>
-analyticsTag): ?>
-
+hasAnalytics): ?>