Skip to content

Instantly share code, notes, and snippets.

@colorwebdesigner
Last active June 8, 2025 07:29
Show Gist options
  • Save colorwebdesigner/1b689075777e038739845896f3e59c4d to your computer and use it in GitHub Desktop.
Save colorwebdesigner/1b689075777e038739845896f3e59c4d to your computer and use it in GitHub Desktop.
MODx Plugin for Unique page views counter with cookie-based tracking
<?php
/**
* pageViewCounter - Unique page views counter with cookie-based tracking
* by Colorwebdesigner
* based on: https://itchief.ru/modx/page-hit-counter
*
* EVENT: OnLoadWebDocument
*
* CONFIGURATION:
* 1. Create a TV (Template Variable) with name "views_page" (type: Number)
* 2. Attach TV to relevant templates
* 3. Install this plugin and activate
*
* USAGE:
* - Use placeholder [[+views_page]] in templates
* - View counts update only for published resources
* - Cookie expires after 1 year
* - Cookies are set for all subdomains (domain-wide)
*
* SECURITY FEATURES:
* - HttpOnly cookies
* - Secure flag for HTTPS
* - Bot detection
* - TV template validation
* - Domain-wide cookie support (.yourdomain.com)
*/
// ====================== CONFIGURATION ======================
$tvName = 'views_page'; // Template Variable name for counter
$placeholder = 'views_page'; // Placeholder for output
$cookiePrefix = 'viewed_'; // Cookie prefix (followed by resource ID)
$cookieLifetime = 86400 * 365; // 1 year cookie duration
$cookieDomain = ''; // Auto-detected (all subdomains). Manually set like: '.yourdomain.com'
// ===========================================================
// Event handling
if ($modx->event->name !== 'OnLoadWebDocument') return;
// Main resource check
$resource = $modx->resource;
if (!$resource || !$resource->get('published') || $resource->get('deleted')) return;
// Get TV by name
$tv = $modx->getObject('modTemplateVar', ['name' => $tvName]);
if (!$tv) {
$modx->log(modX::LOG_LEVEL_ERROR, '[pageViewCounter] TV not found: ' . $tvName);
return;
}
$tvId = $tv->get('id');
// Verify TV-template association
$tvs = $resource->getTemplateVars();
$tvAttached = false;
foreach ($tvs as $tvObj) {
if ($tvObj->get('id') == $tvId) {
$tvAttached = true;
break;
}
}
if (!$tvAttached) return;
// Ignore bots/crawlers
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';
$isBot = preg_match('/bot|crawl|slurp|spider|mediapartners/i', $userAgent);
if ($isBot) return;
// Get or create TV record
$tvResource = $modx->getObject('modTemplateVarResource', [
'tmplvarid' => $tvId,
'contentid' => $resource->id
]);
if (!$tvResource) {
$tvResource = $modx->newObject('modTemplateVarResource');
$tvResource->fromArray([
'tmplvarid' => $tvId,
'contentid' => $resource->id,
'value' => 0
]);
$tvResource->save();
}
// Process view counting
$currentViews = (int)$tvResource->get('value');
$cookieName = $cookiePrefix . $resource->id;
if (empty($_COOKIE[$cookieName])) {
// Update view count
$newViews = $currentViews + 1;
$tvResource->set('value', $newViews);
if ($tvResource->save()) {
// Set HTTP-only secure cookie (domain-wide)
$secure = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off';
// Auto-detect domain for cross-subdomain support
$domain = $cookieDomain ?: (
$_SERVER['HTTP_HOST'] ? '.' . ltrim($_SERVER['HTTP_HOST'], '.') : ''
);
setcookie(
$cookieName,
'1',
time() + $cookieLifetime,
'/',
$domain, // Sets for all subdomains
$secure,
true // HTTP-only
);
$currentViews = $newViews;
} else {
$modx->log(modX::LOG_LEVEL_ERROR,
'[pageViewCounter] Failed to save TV for resource ' . $resource->id
);
}
}
// Output placeholder
$modx->setPlaceholder($placeholder, $currentViews);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment