Last active
July 28, 2022 01:55
-
-
Save rawmain/30b8504b04a98d4dae4bcf053bbdac4c to your computer and use it in GitHub Desktop.
GDPR-compliant UA/GA3 single-host proxy implementation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
include_once('gaproxy.class.php'); | |
$ga = new GaProxy(); | |
$ga->sendHit(); | |
?> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
class GaProxy { | |
// Configuration Start | |
// Set your real Property Nmber where you want to redirect the data | |
private $property_id = 'UA-XXXXXXXXX-Y'; // Devi mettere il tuo tag UA | |
// This will be attached to the hostname value, so we can then filter any hit not coming from this script | |
private $filterHash = 'GDPR-TEST-'; | |
// set this to true, if you want to remove the last Ip's Octet | |
private $anonymizeIp2 = true; | |
// Configuration End | |
public $payload; | |
// We are setting the jail for the visitors, a PHP is started and the session_id is saved into a session variable | |
// When collect.php is loaded we will check the current session_id against this value, and if it doesn't match we'll abort sending the hit to Google Analytics | |
function setupProxy() | |
{ | |
session_start(); | |
$_SESSION["gajail_session_id"] = session_id(); | |
$_SESSION["gajail_current_url"] = $_SERVER['REQUEST_URI']; | |
} | |
// We'll build the hit on there, adding the current user IP address to keep the Geolocation reports, and the user agent | |
// Again, we'll setup out real filtered UA property in here. An attacker will not be able to guess our real UA account. | |
function setupHit() | |
{ | |
$this->payload = $_GET; | |
$this->payload["uip"] = $this->getIpAddress(); | |
$this->payload["ua"] = $this->getUserAgent(); | |
$this->payload["tid"] = $this->property_id; | |
$this->payload["dh"] = $this->filterHash.'-'.$this->getRequestHostName(); | |
} | |
// This will be used to add more antispam mechanism in a future. | |
function checkRequestHeaders() | |
{ | |
// TODO | |
// Check User Agent Format : bots, malformed user agents, etc | |
// Check Againts spammers blacklist IP's | |
// Check throttling | |
} | |
// Gets the current loading user agent | |
function getUserAgent() | |
{ | |
$user_agent = $_SERVER["HTTP_USER_AGENT"]; | |
if (strpos($user_agent, 'Opera') || strpos($user_agent, 'OPR/')) return 'Opera'; | |
elseif (strpos($user_agent, 'Edg/') | |
|| strpos($user_agent, 'EdgA/') | |
|| strpos($user_agent, 'EdgiOS/') | |
|| strpos($user_agent, 'Edge/')) return 'Edge'; | |
elseif (strpos($user_agent, 'Chrome')) return 'Chrome'; | |
elseif (strpos($user_agent, 'Safari')) return 'Safari'; | |
elseif (strpos($user_agent, 'Firefox')) return 'Firefox'; | |
elseif (strpos($user_agent, 'MSIE') || strpos($user_agent, 'Trident')) return 'Internet Explorer'; | |
return 'Other'; | |
} | |
// Gets the current hostnamea | |
function getRequestHostName() | |
{ | |
return $_SERVER["SERVER_NAME"]; | |
} | |
// Gets the current loading Ip Address | |
function getIpAddress() | |
{ | |
$ipAddress = $_SERVER['REMOTE_ADDR']?:($_SERVER['HTTP_X_FORWARDED_FOR']?:$_SERVER['HTTP_CLIENT_IP']); | |
if (strpos($ipAddress, ':')) | |
{ | |
$replace_num = strrpos($ipAddress, ':') - strlen($ipAddress) + 1; | |
$ipAddress6 = substr_replace($ipAddress, '0000', $replace_num); | |
return $ipAddress6; | |
} | |
elseif (strpos($ipAddress, '.')) | |
{ | |
$octets = explode('.', $ipAddress); | |
$second_to_last = array_slice($octets, -2, 1, true); | |
$second_to_last_key = array_key_first($second_to_last); | |
$second_to_last[$second_to_last_key] = '0'; | |
$last = array_slice($octets, -1, 1, true); | |
$last_key = array_key_first($last); | |
$last[$last_key] = '0'; | |
$octets = array_replace($octets, $second_to_last, $last); | |
return implode('.', $octets); | |
} | |
return '185.27.0.0'; | |
} | |
// This function will care of building the hit payload and sending it to Universal Analytics endpoint | |
function sendHit() | |
{ | |
session_start(); | |
$this->setupHit(); | |
if($_SESSION["gajail_session_id"] == session_id()) | |
{ | |
$context = stream_context_create(array('http' => array( | |
'method' => 'GET', | |
'header'=>"Accept-language: ".$this->payload["ul"]."\r\n" . | |
"User-Agent: ".$this->payload["ua"]."\r\n" | |
))); | |
$url = 'https://www.google-analytics.com/collect?'.http_build_query($this->payload,'','&'); | |
$fp = fopen($url, 'r', false, $context); | |
} | |
header('Content-Type: image/gif'); | |
echo base64_decode("R0lGODdhAQABAIAAAPxqbAAAACwAAAAAAQABAAACAkQBADs="); | |
die(); | |
} | |
} | |
?> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<script src='https://YOURWEBSITE/mazinger.js'></script> | |
// Include it in every webpage you want to collect pageview hits | |
// P.S.: Replace YOURWEBSITE with your own site address |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function (context, trackingId, options) { | |
const history = context.history; | |
const doc = document; | |
const nav = navigator || {}; | |
const storage = localStorage; | |
const encode = encodeURIComponent; | |
const pushState = history.pushState; | |
const typeException = 'exception'; | |
const generateZ = () => Math.floor(1000000000 + (Math.random() * 8999999999)).toString(); | |
if (!Math.imul) Math.imul = function(opA, opB) { | |
opB |= 0; | |
var result = (opA & 0x003fffff) * opB; | |
if (opA & 0xffc00000) result += (opA & 0xffc00000) * opB|0; | |
return result|0; | |
}; | |
const cyrb53 = function(str, seed = 0) { | |
let h1 = 0xdeadbeef ^ seed, | |
h2 = 0x41c6ce57 ^ seed; | |
for (let i = 0, ch; i < str.length; i++) { | |
ch = str.charCodeAt(i); | |
h1 = Math.imul(h1 ^ ch, 2654435761); | |
h2 = Math.imul(h2 ^ ch, 1597334677); | |
} | |
h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507) ^ Math.imul(h2 ^ h2 >>> 13, 3266489909); | |
h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507) ^ Math.imul(h1 ^ h1 >>> 13, 3266489909); | |
return 4294967296 * (2097151 & h2) + (h1 >>> 0); | |
}; | |
const generateId = () => { | |
let creationTime = new Date().getTime(); | |
let expiryTime = creationTime + (30 * 24 * 3600 * 1000); // Change 30 to any number of days you want the CID to be valid. | |
let CIDSource = window.location.host + ";" + navigator.userAgent + ";" + navigator.language + ";" + creationTime; | |
if (window.localStorage) { | |
CIDhashed = localStorage.getItem('CID_HASHED'); | |
CIDexpiry = localStorage.getItem('CID_EXPIRY'); | |
if ((CIDhashed === null || CIDexpiry === null) | |
|| (CIDhashed !== null && CIDexpiry !== null && CIDexpiry >= expiryTime)) { | |
localStorage.setItem('CID_HASHED', cyrb53(CIDSource).toString(16)); | |
localStorage.setItem('CID_EXPIRY', expiryTime); | |
} | |
return storage.CID_HASHED; | |
} else { | |
return undefined; | |
} | |
}; | |
const getId = () => { | |
if (!storage.CID_HASHED) { | |
storage.CID_HASHED = generateId(); | |
} | |
return storage.CID_HASHED; | |
}; | |
const serialize = (obj) => { | |
var str = []; | |
for (var p in obj) { | |
if (obj.hasOwnProperty(p)) { | |
if(obj[p] !== undefined) { | |
str.push(encode(p) + "=" + encode(obj[p])); | |
} | |
} | |
} | |
return str.join("&"); | |
}; | |
const track = ( | |
type, | |
eventCategory, | |
eventAction, | |
eventLabel, | |
eventValue, | |
exceptionDescription, | |
exceptionFatal | |
) => { | |
const url = '/collect.php'; | |
const data = serialize({ | |
v: '1', | |
ds: undefined, | |
aip: options.anonymizeIp ? 1 : undefined, | |
tid: trackingId, | |
cid: getId(), | |
t: type || 'pageview', | |
sd: options.colorDepth && screen.colorDepth ? `${screen.colorDepth}-bits` : undefined, | |
dr: undefined, | |
dt: doc.title, | |
dl: doc.location.origin + doc.location.pathname, | |
ul: options.language ? (nav.language || "").toLowerCase() : undefined, | |
de: options.characterSet ? doc.characterSet : undefined, | |
sr: options.screenSize ? `${(context.screen || {}).width}x${(context.screen || {}).height}` : undefined, | |
vp: options.screenSize && context.visualViewport ? `${(context.visualViewport || {}).width}x${(context.visualViewport || {}).height}` : undefined, | |
ec: eventCategory || undefined, | |
ea: eventAction || undefined, | |
el: eventLabel || undefined, | |
ev: eventValue || undefined, | |
exd: exceptionDescription || undefined, | |
exf: typeof exceptionFatal !== 'undefined' && !!exceptionFatal === false ? 0 : undefined, | |
z: generateZ(), | |
}); | |
var xhr = new XMLHttpRequest(); | |
xhr.open("GET", url+"?"+data); | |
xhr.send(); | |
}; | |
const trackEvent = (category, action, label, value) => track('event', category, action, label, value); | |
const trackException = (description, fatal) => track(typeException, null, null, null, null, description, fatal); | |
history.pushState = function (state) { | |
if (typeof history.onpushstate == "function") { | |
history.onpushstate({ state: state }); | |
} | |
setTimeout(track, options.delay || 10); | |
return pushState.apply(history, arguments); | |
} | |
track(); | |
context.ma = { | |
trackEvent, | |
trackException | |
} | |
})(window, "MAZINGER", { | |
anonymizeIp: true, | |
colorDepth: true, | |
characterSet: true, | |
screenSize: true, | |
language: true | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
include_once('gaproxy.class.php'); | |
$ga = new GaProxy(); | |
$ga->setupProxy(); | |
/** | |
* Include this code before HTML tag | |
* for every webpage you want to | |
* collect pageview hits | |
*/ | |
?> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment