Skip to content

Instantly share code, notes, and snippets.

@mbaersch
Last active August 28, 2023 23:28
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save mbaersch/34af06d1336f878ea10f3f7b0e49d45c to your computer and use it in GitHub Desktop.
Save mbaersch/34af06d1336f878ea10f3f7b0e49d45c to your computer and use it in GitHub Desktop.
PHP Helper für serverseitiges und / oder cookieloses Tracking mit Google Analytics
//Einfaches Beispiel der Nutzung des serverseitigen Loggers im Client - also der entsprechende "Trackingcode":
function doLog(tp, pr) {
tp = tp.toLowerCase();
if (!pr) pr = "";
var rf = document.referrer;
var params = "?ht=" + tp;
if (rf) params += "&rf=" + encodeURI(rf);
if (pr!="") params += "&in=" + encodeURI(pr);
var img = new Image();
img.style.left = "-5000px";
img.style.position = "absolute";
img.src = '/logger.php'+params;
document.querySelector('body').appendChild(img);
}
/*Hinweis: Als Parameter nimmt diese Funktion nur zwei Werte entgegen: Den "HitTyp" und die "Nutzlast".
Der Typ wird passend zum unten stehenden Beispiel-Logger mit "p" für Seitenaufrufe und "e" für Events aufgerufen.
Als Nutzlast verwendet dieses Beispiel Kategorie, Aktion, Label und eines Ereignisses, die als Array in einem
JSON String übergeben und im Logger wieder in die Einzelteile zerlegt werden. Zum Abruf wird "ganz
klassisch" ein Pixel verwendet, das vom Logger zurückgeliefert wird (ja genau: drängt sich für eigene
GTM Templates geradezu auf ;)).
** Nutzung als Ersatz zum normalen Trackingcode **
Im einfachsten Fall reicht eine bedingte Initialisierung entweder des vollwertigen Trackers oder
einer "Dummy-Funktion", die alle Aufrufe an die o. a. Funktion weiterleitet. Dieser Ersatz ist
kompatibel zu den typischen Aufrufen der ga()-Funktion, so dass man alles "wie sonst auch" an
Trackingaufrufen nutzen kann. Durch die Reduktion auf Seitenaufrufe und Events mit maximal vier
Eigenschaften geht das ziemlich einfach. Beispiel:
*/
function initGaDummy() {
ga = new Function("p1", "p2", "p3", "p4", "p5", "p6", "p7", "p8",
"if ((p1.toLowerCase() == 'send') && (p2.toLowerCase() == 'pageview')) "+
"doLog('p', p3); else "+
"if ((p1.toLowerCase() == 'send') && (p2.toLowerCase() == 'event')) "+
"doLog('e', JSON.stringify({ec: encodeURI(p3), ea: encodeURI(p4), el: encodeURI(p5), ev: encodeURI(p6||\"0\")})); "+
"return true;");
doLog();
}
//Zur Erklärung: Durch die Deklaration einer eigenen ga()-Funktion werden alle Aufrufe inkl. der Parameter
//an doLog() weitergegeben und gelangen von dort zum serverseitigen Logger, der dann tut, was auch immer er
//tun soll. In unserem Fall das Weitergeben der Hits an Google Analytics. Wird diese Funktion als Ersatz
//des normalen Trackingcodes aufgerufen, dient die letzte Zeile dazu, den Messpunkt für den aktuellen
//Seitenaufruf auszulösen (der keine Parameter benötigt).
<?php
const USE_FINGERPRINT = 1;
const USE_COOKIE = 2;
const USE_SESSION = 3;
/*************************** SETUP ********************************************************************/
//Google Analytics Property Id
$ua = "UA-12345678-1";
//Parameterstring mit beliebigen Konstanten oder variablen Werten definieren, der an ausgehende Hits
//angehaengt wird. Dabei koennen folgende Platzhalter verwendet werden:
//%%SESSION_ID%% : Die Session- bzw. ClientId, die auch als Parameter cid uebergeben wird
//%%HIT_TIMESTAMP%% : Zeitstempel des Servers zum Zeitpunkt des Absendens des Hits
//%%USER_AGENT%% : Der User Agent String des aufrufenden Browsers
//%%RANDOM_UUID%% : Eine UUID als Zufalls-String, die z. B. als Sitzuingskennung im Session Scope dienen kann
//Hinweis: Kein "&" am Anfang des Strings erforderlich, wenn er definiert werden sollte.
$customParameters = "";
//Verfahren der ClientId Generierung
$clientDetectionMethod = USE_SESSION;
//Optionen:
//USE_FINGERPRINT : Es wird eine ClientId als Hash aus der anonymisierten IP, dem User Agent und
// der Sprache gebildet. Verfahren im Ergebnis vergleichbar mit Matomo, siehe
// https://matomo.org/faq/general/faq_21418/
// Wie die Session kann auch dieser Wert per Parameter abgerufen werden, um ihn
// z. B. in den dataLayer zu schreiben (sehe USE_SESSION)
//USE_COOKIE : Es wird ein Cookiewert ausgelesen und als ClientId verwendet. Dazu muss unten
// ein Cookiename definiert werden. Das Handling des Cookies kann optional auch
// von diesem Logger uebernommen werden (siehe Optionen zu Cookies unten). Solange
// Cookies nicht geloescht werden, bleibt der User erkennbar
//USE_SESSION : Die PHP Session-ID wird als ClientId genutzt. Im einfachsten Fall existiert
// diese bereits durch das System und es gibt ein PHPSESSID Cookie. Anderenfalls
// kann eine Session auch durch Aufruf des Loggers mit dem Parameter ht=sid gestartet
// und abgerufen werden. In diesem Fall ist der Besucher nach der Sitzung nicht mehr
// wiedererkennbar und gilt bei jedem neuen Besuch als neuer Benutzer
//Als Alternative kann die Session- bzw. Client Id auch als Parameter "si" uebergeben werden. Ist der
//Parameter vorhanden, ueberschreibt er die Session aus der o. a. Methode.
//Wenn deaktiviert, wird die Session Id genutzt, um den User - dann nur fuer diese eine Session - zu
//identifizieren. Bei Aktivierung ist der User wiedererkennbar, solange die IP bestehen bleibt und der
//gleiche Browser genutzt wird. Auf diese Weise sind allerdings auch mehrere User im gleichen Netztwerk
//in einem Topf zu finden, wenn die User Agents und Sprachen ebenso gleich gestaltet sind.
//Optionen bei Verwendung von USE_COOKIE:
//Kennung eines Cookies eintragen, wenn dieses verwendet werden soll. Beispiel: _ga zur Verwendung der
//ClientId aus dem Analytics Cookie. Leer lassen = kein Zugriff auf Cookies und auch kein Setzen
//die folgende Option ist in diesem Fall irrelevant
$ClientCookieValue = "_UID";
//Wenn o. Cookie nicht vom Browser oder System erzeugt und verwaltet wird, kann der Logger dies selbst
//uebernehmen, wenn oben eine Kennung angegeben ist. Es wird ein Cookie mit 2 Jahren Laufzeit verwendet.
$setCookieIfNotPresent = false;
//Wenn ein Cookie gesetzt werden soll, kann dieses hiermit wahlweise als Secure und httpOnly gesetzt
//werden, so dass es im Browser von Scripts nicht gelesen werden kann.
$setSecureCookie = true;
/*************************** ENDE SETUP ***************************************************************/
if ($clientDetectionMethod === USE_SESSION)
session_start(['cookie_secure' => true,
'cookie_httponly' => true,
'cookie_samesite' => true]);
function gen_uuid() {
return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ),
mt_rand( 0, 0xffff ),
mt_rand( 0, 0x0fff ) | 0x4000,
mt_rand( 0, 0x3fff ) | 0x8000,
mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff )
);
}
function get_domain_from_host($h) {
$hst = explode(".", $h);
return $hst[count($hst)-2] . "." . $hst[count($hst)-1];
}
function anonymizeIp($ip) {
//IPV4/IPV6
return preg_replace(['/\.\d*$/','/[\da-f]*:[\da-f]*$/'],['.0','0000:0000'],$ip);
}
function get_fingerprint($i, $l, $a) {
//nicht nachvollziehbarer Hash aus anonymer IP, Sprache und User Agent
$in = $i . 'k1|' . $l . 'k2|' . $a . 's|';
$salt = 'S&6!l%aV<*MFy;~Uy9)t#^ygl';
return hash('md5', $in.$salt);
}
function send_ga_hit($url) {
$ch = cURL_init($url);
cURL_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
cURL_setopt($ch, CURLOPT_FRESH_CONNECT, true);
cURL_setopt($ch, CURLOPT_TIMEOUT_MS, 50);
curl_exec($ch);
cURL_close($ch);
return true ;
}
//Parameter auslesen
$htype = strtolower($_GET['ht']); //"p" f. Seitenaufruf, "e" f. Event, "sid" zum Abruf der Session- / Client Id
$pageref = $_GET['rf']; //Referrer der aufrufenden Seite
$input = $_GET['in']; //Seite als Parameter oder Angaben f. Event im Format Kategorie||Aktion||Label
$set_sid = $_GET['si']; //Session- Client Id als Parameter. Ueberschreibt Session- o. Cookie-Wert
$pgurl = urlencode($_SERVER['HTTP_REFERER']);
$call_from = strtolower(get_domain_from_host(parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST)));
$this_domain = strtolower(get_domain_from_host($_SERVER['SERVER_NAME']));
//Browser und Merkmale lesen
$lng = strtolower($_SERVER['HTTP_ACCEPT_LANGUAGE']);
if ($lng != "") $lng = explode(',', $lng)[0];
$agent = urlencode($_SERVER['HTTP_USER_AGENT']);
if ($set_sid != "") {
$session = $set_sid;
} else if ($clientDetectionMethod === USE_COOKIE) {
$session = $_COOKIE[$ClientCookieValue];
if (($session == "") && ($setCookieIfNotPresent === true)) {
//Session ID Cookie erzeugen
$session = rand(10000, 99999).rand(20000, 99999).".".time();
//Set secure http cookie with 2 years lifetime
setcookie($ClientCookieValue, $session, time()+60*60*24*365*2, "/",
$this_domain, $secure = $setSecureCookie, $httponly = $setSecureCookie);
}
} else if ($clientDetectionMethod === USE_SESSION) {
$session = session_id();
} else if ($clientDetectionMethod === USE_FINGERPRINT) {
if (! isset($_SERVER['HTTP_X_FORWARDED_FOR']))
$clientIp = anonymizeIp($_SERVER['REMOTE_ADDR']);
else
$clientIp = anonymizeIp($_SERVER['HTTP_X_FORWARDED_FOR']);
//Client Id als Fingerprintberechnen
$session = urlencode(get_fingerprint($clientIp, $lng, $agent));
}
//Hits validieren
if (($htype != 'sid') && ($htype != 'p') && ($htype != 'e')) $session = "";
if ($call_from != $this_domain) $session = "";
//Alles da? Dann Trackinghit bauen und senden
if (($session != "") && ($agent != "")) {
$cid = urlencode($session);
//Hinweis: Es kann, sollte aber nicht die IP des Clients verwendet werden. Wir gehen vom nicht
//Consent-Fall aus und nutzen daher keine IP jenseits des Fingerprints. Ansonsten kann auch die
//IP wie oben im Fall von USE_FINGERPRINT ermittelt und anonymisiert werden.
$uip = "0.0.0.0";
if ($htype === 'sid') {
//Session- / ClientId wurde angefordert
echo $cid;
} else {
//Hit zusammenbauen und senden
$collect_url = "https://www.google-analytics.com/collect?v=1&ul=$lng&ua=$agent&tid=$ua&cid=$cid&aip=1";
$collect_url .= "&uip=$uip";
if ($htype === 'p') {
$collect_url .= "&t=pageview";
if ($input != "") $collect_url .= "&dp=$input";
} else {
$prs = json_decode($input, true);
$ec = urlencode($prs["ec"]);
$ea = urlencode($prs["ea"]);
$el = urlencode($prs["el"]);
$ev = urlencode($prs["ev"]);
$collect_url .= "&t=event&ec=$ec&ea=$ea&el=$el&ev=$ev";
}
$collect_url .= "&dl=$pgurl&dh=$this_domain&dr=$pageref";
//Custom Dimensions anhaengen, wenn definiert
if ($customParameters != "") {
$cd = str_replace("%%USER_AGENT%%", $agent, $customParameters);
$cd = str_replace("%%SESSION_ID%%", $cid, $cd);
$cd = str_replace("%%HIT_TIMESTAMP%%", date(DATE_ATOM, time()), $cd);
if (strpos($customParameters, "%%RANDOM_UUID%%"))
$cd = str_replace("%%RANDOM_UUID%%", gen_uuid(), $cd);
$collect_url .= "&".$cd;
}
send_ga_hit($collect_url);
//Geschafft. Wir liefern ein Pixel aus, weil wir ein Tracker sind!
header('Content-Type: image/gif');
echo(hex2bin('47494638396101000100900000ff000000000021f90405100000002c00000000010001000002020401003b'));
}
} else {
header('HTTP/1.0 403 Forbidden');
echo "No!";
}
?>
@mbaersch
Copy link
Author

Hinweise zum Einsatz siehe Blogbeitrag zum serverseitigen Tracking

@mbaersch
Copy link
Author

Hinweis zu Revision 4: Neben einem Fehler im gaDummy (hat so leider nie funktioniert, Sorry) wurde das Transportformat der Event-Parameter auf ein via JSON serialisiertes Array umgestellt. Dies korrespondiert mit dem im Blog vorgestellten serverseitigen Client für das Format dieses Loggers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment