feat: smart visitor detection for GA/GTM #8

Merged
jmiller merged 1 commits from feat/smart-visitor-detection into main 2026-05-05 20:27:30 +00:00
7 changed files with 62 additions and 1 deletions
+5
View File
@@ -19,6 +19,11 @@ All notable changes to the MokoOnyx Joomla template are documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- **Smart Visitor Detection** — Pushes anonymised visitor properties (login status, user group, page type) to the dataLayer for Google Analytics / Tag Manager. Sets GA4 `user_properties` for persistent session-scoped dimensions. No PII is sent. Default enabled when GTM or GA4 is active.
## [03.10.00] - 2026-04-18 — Bridge Release (MokoOnyx → MokoOnyx)
### Important
+1 -1
View File
@@ -33,7 +33,7 @@ MokoOnyx is a modern, lightweight enhancement layer built on top of Joomla's Cas
- **Bootstrap 5**: Extended utility classes and responsive grid system
- **Template Overrides**: Includes overrides for all core Joomla modules, Community Builder, and DPCalendar
- **Dark Mode Support**: Built-in light/dark mode toggle with system preference detection
- **Google Tag Manager / GA4**: Optional analytics integrations
- **Google Tag Manager / GA4**: Optional analytics integrations with smart visitor detection (login status, user group, page type)
- **Table of Contents**: Automatic TOC generation for long articles
## Requirements
+10
View File
@@ -565,6 +565,15 @@ Users still running MokoCassiopeia must install MokoOnyx v01.x first to migrate
- **Universal Analytics Fallback**: Legacy UA support
- **Privacy-First**: Conditional loading based on settings
#### Smart Visitor Detection
- **Enable/Disable**: Admin toggle (default: enabled)
- **Visitor Type**: Pushes `logged_in` or `guest` to dataLayer
- **Visitor Group**: Pushes highest-privilege Joomla user group name (e.g., "Registered", "Author")
- **Page Type**: Pushes component + view (e.g., `com_content.article`)
- **GA4 User Properties**: Sets `visitor_type` and `visitor_group` as persistent user-scoped dimensions
- **Privacy-Safe**: No PII (usernames, emails, or user IDs) is ever sent
- **Dual Integration**: Works with both GTM (`moko.visitor_detect` event) and standalone GA4 (`user_properties`)
### 🎛️ Customization & Developer Tools
#### Custom Code Injection
@@ -657,6 +666,7 @@ Users still running MokoCassiopeia must install MokoOnyx v01.x first to migrate
- `googletagmanagerid` - GTM container ID
- `googleanalytics` - Enable GA4
- `googleanalyticsid` - GA4 property ID
- `googlevisitordetection` - Smart Visitor Detection (default: enabled)
#### Custom Code Tab
- `custom_head_start` - Custom code at head start
+38
View File
@@ -37,6 +37,7 @@ $params_googletagmanagerid = $this->params->get('googletagmanagerid', null);
$params_googleanalytics = $this->params->get('googleanalytics', false);
$params_googleanalyticsid = $this->params->get('googleanalyticsid', null);
$params_googlesitekey = $this->params->get('googlesitekey', null);
$params_visitordetection = $this->params->get('googlevisitordetection', true);
$params_custom_head_start = $this->params->get('custom_head_start', null);
$params_custom_head_end = $this->params->get('custom_head_end', null);
$params_developmentmode = $this->params->get('developmentmode', false) || $app->get('debug', false);
@@ -349,6 +350,37 @@ $wa->useScript('user.js'); // js/user.js
. $hasClass
. ($this->direction == 'rtl' ? ' rtl' : '');
?>">
<?php if (!empty($params_visitordetection) && (!empty($params_googletagmanager) || !empty($params_googleanalytics))) :
$user = Factory::getUser();
$visitorType = $user->guest ? 'guest' : 'logged_in';
$visitorGroup = 'none';
if (!$user->guest) {
$groupIds = $user->getAuthorisedGroups();
if (!empty($groupIds)) {
$db = Factory::getContainer()->get(\Joomla\Database\DatabaseInterface::class);
$query = $db->getQuery(true)
->select($db->quoteName('title'))
->from($db->quoteName('#__usergroups'))
->where($db->quoteName('id') . ' = ' . (int) end($groupIds));
$db->setQuery($query);
$visitorGroup = $db->loadResult() ?: 'unknown';
}
}
$pageType = $option . ($view ? '.' . $view : '');
?>
<!-- Smart Visitor Detection -->
<script>
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
'event': 'moko.visitor_detect',
'visitor_type': '<?php echo $visitorType; ?>',
'visitor_group': '<?php echo htmlspecialchars($visitorGroup, ENT_QUOTES, 'UTF-8'); ?>',
'page_type': '<?php echo htmlspecialchars($pageType, ENT_QUOTES, 'UTF-8'); ?>'
});
</script>
<!-- End Smart Visitor Detection -->
<?php endif; ?>
<?php if (!empty($params_googletagmanager) && !empty($params_googletagmanagerid)) :
$gtmID = htmlspecialchars($params_googletagmanagerid, ENT_QUOTES, 'UTF-8'); ?>
<!-- Google Tag Manager -->
@@ -398,6 +430,12 @@ $wa->useScript('user.js'); // js/user.js
console.warn('Unrecognized Google Analytics ID format:', id);
}
})('<?php echo $gaId; ?>');
<?php if (!empty($params_visitordetection)) : ?>
gtag('set', 'user_properties', {
'visitor_type': '<?php echo $user->guest ? 'guest' : 'logged_in'; ?>',
'visitor_group': '<?php echo htmlspecialchars($visitorGroup ?? 'none', ENT_QUOTES, 'UTF-8'); ?>'
});
<?php endif; ?>
</script>
<!-- End Google Analytics -->
<?php endif; ?>
+2
View File
@@ -46,6 +46,8 @@ TPL_MOKOONYX_GOOGLEANALYTICSID_LABEL="Google Analytics ID"
TPL_MOKOONYX_GOOGLEANALYTICSID_DESC="Begins with 'G-'"
TPL_MOKOONYX_GOOGLESITEKEY_LABEL="Google Search Console Verification"
TPL_MOKOONYX_GOOGLESITEKEY_DESC="Paste the content value from the &lt;meta name=&quot;google-site-verification&quot;&gt; tag. Find this in Google Search Console under Ownership Verification &rarr; HTML tag method."
TPL_MOKOONYX_GOOGLE_VISITOR_DETECTION_LABEL="Smart Visitor Detection"
TPL_MOKOONYX_GOOGLE_VISITOR_DETECTION_DESC="Push anonymised visitor properties (login status, user group, page type) to the dataLayer for use in Google Analytics / Tag Manager. No personally identifiable information is sent."
; ===== Branding & icons (Theme tab) =====
TPL_MOKOONYX_BRAND_LABEL="Brand"
+2
View File
@@ -46,6 +46,8 @@ TPL_MOKOONYX_GOOGLEANALYTICSID_LABEL="Google Analytics ID"
TPL_MOKOONYX_GOOGLEANALYTICSID_DESC="Begins with 'G-'"
TPL_MOKOONYX_GOOGLESITEKEY_LABEL="Google Search Console Verification"
TPL_MOKOONYX_GOOGLESITEKEY_DESC="Paste the content value from the &lt;meta name=&quot;google-site-verification&quot;&gt; tag. Find this in Google Search Console under Ownership Verification &rarr; HTML tag method."
TPL_MOKOONYX_GOOGLE_VISITOR_DETECTION_LABEL="Smart Visitor Detection"
TPL_MOKOONYX_GOOGLE_VISITOR_DETECTION_DESC="Push anonymised visitor properties (login status, user group, page type) to the dataLayer for use in Google Analytics / Tag Manager. No personally identifiable information is sent."
; ===== Branding & icons (Theme tab) =====
TPL_MOKOONYX_BRAND_LABEL="Brand"
+4
View File
@@ -142,6 +142,10 @@
</field>
<field name="googleanalyticsid" type="text" default="" label="TPL_MOKOONYX_GOOGLEANALYTICSID_LABEL" description="TPL_MOKOONYX_GOOGLEANALYTICSID_DESC" filter="string" showon="googleanalytics:1" />
<field name="googlesitekey" type="text" default="" label="TPL_MOKOONYX_GOOGLESITEKEY_LABEL" description="TPL_MOKOONYX_GOOGLESITEKEY_DESC" filter="string" />
<field name="googlevisitordetection" type="radio" default="1" label="TPL_MOKOONYX_GOOGLE_VISITOR_DETECTION_LABEL" description="TPL_MOKOONYX_GOOGLE_VISITOR_DETECTION_DESC" layout="joomla.form.field.radio.switcher" filter="boolean" showon="googletagmanager:1[OR]googleanalytics:1">
<option value="0">JNO</option>
<option value="1">JYES</option>
</field>
</fieldset>
<!-- Custom Code tab -->