Skip to content

Commit c718993

Browse files
bramleysuelaP
authored andcommitted
Add support for including Matomo query parameters on URLs instead of Google Analytics parameters.
Correct existing bug that removed uid parameter from URLs for plain-text format emails.
1 parent 98cd4b2 commit c718993

File tree

9 files changed

+333
-70
lines changed

9 files changed

+333
-70
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
/**
4+
* This interface is implemented by internal phplist classes.
5+
* A plugin can also implement this interface to customise the parameters for analytic tracking, such as Google Analytics or Matomo.
6+
*/
7+
interface AnalyticsQuery
8+
{
9+
/**
10+
* Provide the query parameters to be added to a URL.
11+
*
12+
* @param string $emailFormat HTML or text
13+
* @param array $messageData
14+
*
15+
* @return array query parameters as key => value
16+
*/
17+
public function trackingParameters($emailFormat, $messageData);
18+
19+
/**
20+
* Provide the query parameters that can be edited on the Finish tab.
21+
*
22+
* @param array $messageData
23+
*
24+
* @return array query parameters as key => default value
25+
*/
26+
public function editableParameters($messageData);
27+
28+
/**
29+
* Provide the prefix of the tracking parameters.
30+
* This is used to remove existing tracking parameters from a URL.
31+
*
32+
* @return string
33+
*/
34+
public function prefix();
35+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
<?php
2+
3+
class GoogleAnalyticsQuery implements AnalyticsQuery
4+
{
5+
public function trackingParameters($emailFormat, $messageData)
6+
{
7+
return array(
8+
'utm_source' => $messageData['utm_source'],
9+
'utm_medium' => $messageData['utm_medium'],
10+
'utm_campaign' => $messageData['utm_campaign'],
11+
'utm_content' => $emailFormat,
12+
);
13+
}
14+
15+
public function editableParameters($messageData)
16+
{
17+
return array(
18+
'utm_source' => 'phpList',
19+
'utm_medium' => 'email',
20+
'utm_campaign' => $messageData['subject'],
21+
);
22+
}
23+
24+
public function prefix()
25+
{
26+
return 'utm_';
27+
}
28+
}
29+
30+
class MatomoAnalyticsQuery implements AnalyticsQuery
31+
{
32+
public function trackingParameters($emailFormat, $messageData)
33+
{
34+
return array(
35+
'pk_source' => $messageData['pk_source'],
36+
'pk_medium' => $messageData['pk_medium'],
37+
'pk_campaign' => $messageData['pk_campaign'],
38+
'pk_content' => $emailFormat,
39+
);
40+
}
41+
42+
public function editableParameters($messageData)
43+
{
44+
return array(
45+
'pk_source' => 'phpList',
46+
'pk_medium' => 'email',
47+
'pk_campaign' => $messageData['subject'],
48+
);
49+
}
50+
51+
public function prefix()
52+
{
53+
return 'pk_';
54+
}
55+
}
56+
57+
/**
58+
* Return an instance of a class that will provide the parameters for an analytic tracker.
59+
*
60+
* @return AnalyticsQuery class instance
61+
*/
62+
function getAnalyticsQuery()
63+
{
64+
global $analyticsqueryplugin;
65+
66+
$config = getConfig('analytic_tracker');
67+
68+
if ($config == 'plugin' && $analyticsqueryplugin) {
69+
return $analyticsqueryplugin;
70+
}
71+
72+
return $config == 'matomo'
73+
? new MatomoAnalyticsQuery()
74+
: new GoogleAnalyticsQuery();
75+
}
76+
77+
/**
78+
* Add analytic query parameters to the URL also removing any existing values.
79+
*
80+
* @param string $url
81+
* @param array $trackingParameters query parameters as key => value
82+
* @param string $prefix tracking parameter prefix
83+
*
84+
* @return string
85+
*/
86+
function addAnalyticsTracking($url, $trackingParameters, $prefix)
87+
{
88+
$position = strpos($url, '#');
89+
90+
if ($position !== false) {
91+
$baseUrl = substr($url, 0, $position);
92+
$fragment = substr($url, $position);
93+
} else {
94+
$baseUrl = $url;
95+
$fragment = '';
96+
}
97+
98+
if (strpos($baseUrl, $prefix) !== false) {
99+
// Take off existing tracking code. The regex can leave a trailing ? or & which needs to be removed.
100+
$regex = sprintf('/%s.+?=[^&]+(&|$)/', $prefix);
101+
$baseUrl = rtrim(preg_replace($regex, '', $baseUrl), '?&');
102+
}
103+
104+
// re-construct the URL but exclude any empty parameters
105+
$trackingcode = http_build_query(array_filter($trackingParameters));
106+
$separator = strpos($baseUrl, '?') ? '&' : '?';
107+
108+
return $baseUrl.$separator.$trackingcode.$fragment;
109+
}

public_html/lists/admin/defaultconfig.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,11 +146,19 @@
146146
),
147147
'always_add_googletracking' => array(
148148
'value' => '0',
149-
'description' => s('Always add Google tracking code to campaigns'),
149+
'description' => s('Always add analytics tracking code to campaigns'),
150150
'type' => 'boolean',
151151
'allowempty' => true,
152152
'category' => 'campaign',
153153
),
154+
'analytic_tracker' => array(
155+
'values' => array('google' => 'Google Analytics', 'matomo' => 'Matomo'),
156+
'value' => 'google',
157+
'description' => s('Analytics tracking code to add to campaign URLs'),
158+
'type' => 'select',
159+
'allowempty' => false,
160+
'category' => 'campaign',
161+
),
154162
// report address is the person who gets the reports
155163
'report_address' => array(
156164
'value' => 'listreports@[DOMAIN]',

public_html/lists/admin/js/phplistapp.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,14 @@ $(document).ready(function () {
286286
}
287287
});
288288

289+
$("#google_track").on("click",function () {
290+
if (this.checked) {
291+
$("#analytics").show();
292+
} else {
293+
$("#analytics").hide();
294+
}
295+
});
296+
289297
$("input:radio[name=sendmethod]").on("change",function () {
290298
if (this.value == "remoteurl") {
291299
$("#remoteurl").show();

public_html/lists/admin/pluginlib.php

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
require_once dirname(__FILE__).'/accesscheck.php';
44
require_once dirname(__FILE__).'/EmailSender.php';
55
include_once dirname(__FILE__).'/defaultplugin.php';
6+
require_once dirname(__FILE__).'/AnalyticsQuery.php';
67

78
$GLOBALS['plugins'] = array();
89
$GLOBALS['editorplugin'] = false;
910
$GLOBALS['authenticationplugin'] = false;
1011
$GLOBALS['emailsenderplugin'] = false;
12+
$GLOBALS['analyticsqueryplugin'] = false;
1113

1214
$pluginRootDirs = array();
1315
if (PLUGIN_ROOTDIRS != '') {
@@ -115,6 +117,12 @@
115117
$GLOBALS['emailsenderplugin'] = $pluginInstance;
116118
}
117119

120+
if (!$GLOBALS['analyticsqueryplugin'] && $pluginInstance instanceof AnalyticsQuery) {
121+
$GLOBALS['analyticsqueryplugin'] = $pluginInstance;
122+
// Add 'plugin' as an option on the Settings page
123+
$default_config['analytic_tracker']['values'] += array('plugin' => $analyticsqueryplugin->name);
124+
}
125+
118126
if (!empty($pluginInstance->DBstruct)) {
119127
foreach ($pluginInstance->DBstruct as $tablename => $tablecolumns) {
120128
$GLOBALS['tables'][$className.'_'.$tablename] = $GLOBALS['table_prefix'].$className.'_'.$tablename;
@@ -145,7 +153,7 @@ function($a, $b) {
145153
$pluginInstance->enabled = false;
146154
unset($plugins[$className]);
147155
continue;
148-
}
156+
}
149157
$pluginInstance->activate();
150158
}
151159

public_html/lists/admin/send_core.php

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
require_once dirname(__FILE__).'/accesscheck.php';
33

44
include_once dirname(__FILE__).'/date.php';
5+
include_once dirname(__FILE__).'/analytics.php';
56

67
$errormsg = '';
78
$done = 0;
@@ -1022,9 +1023,32 @@ function submitform() {
10221023
<div class="campaignTracking">
10231024
<label for="cb[google_track]">%s</label><input type="hidden" name="cb[google_track]" value="1" /><input type="checkbox" name="google_track" id="google_track" value="1" %s />
10241025
</div>',
1025-
Help('googletrack').' '.s('add Google Analytics tracking code'),
1026+
Help('googletrack').' '.s('Add analytics tracking code'),
10261027
!empty($messagedata['google_track']) ? 'checked="checked"' : '');
10271028

1029+
/* add analytics query parameters then hide if not currently enabled */
1030+
$analytics = getAnalyticsQuery();
1031+
$editableParameters = $analytics->editableParameters($messagedata);
1032+
1033+
if (count($editableParameters) > 0) {
1034+
$send_content .= '<div id="analytics">';
1035+
1036+
foreach ($editableParameters as $field => $default) {
1037+
$value = isset($messagedata[$field]) ? $messagedata[$field] : $default;
1038+
$send_content .= sprintf(
1039+
'<label>%s <input type="text" name="%s" id="%s" value="%s" size="35"/></label>',
1040+
$field,
1041+
$field,
1042+
$field,
1043+
$value
1044+
) . "\n";
1045+
}
1046+
$send_content .= '</div>';
1047+
1048+
if (empty($messagedata['google_track'])) {
1049+
$GLOBALS['pagefooter']['hideanalytics'] = '<script type="text/javascript">$("#analytics").hide()</script>';
1050+
}
1051+
}
10281052
$numsent = Sql_Fetch_Row_Query(sprintf('select count(*) from %s where messageid = %d',
10291053
$GLOBALS['tables']['usermessage'], $messagedata['id']));
10301054
if ($numsent[0] < RESETSTATS_MAX) {

public_html/lists/admin/sendemaillib.php

Lines changed: 14 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
// send an email library
66
include_once dirname(__FILE__).'/class.phplistmailer.php';
7+
require_once __DIR__ . '/analytics.php';
78

89
if (!function_exists('output')) {
910
function output($text)
@@ -598,37 +599,25 @@ function sendEmail($messageid, $email, $hash, $htmlpref = 0, $rssitems = array()
598599
//# if we're not tracking clicks, we should add Google tracking here
599600
//# otherwise, we can add it when redirecting on the click
600601
if (!CLICKTRACK && !empty($cached[$messageid]['google_track'])) {
602+
/*
603+
* process html format email
604+
*/
605+
$analytics = getAnalyticsQuery();
606+
$trackingParameters = $analytics->trackingParameters('HTML', loadMessageData($messageid));
607+
$prefix = $analytics->prefix();
601608
preg_match_all('/<a (.*)href=(["\'])(.*)\2([^>]*)>(.*)<\/a>/Umis', $htmlmessage, $links);
609+
602610
for ($i = 0; $i < count($links[3]); ++$i) {
603611
$link = cleanUrl($links[3][$i]);
604612
$link = str_replace('"', '', $link);
605-
//# http://www.google.com/support/analytics/bin/answer.py?hl=en&answer=55578
606-
607-
$trackingcode = 'utm_source=phplist'.$messageid.'&utm_medium=email&utm_content=HTML&utm_campaign='.urlencode($cached[$messageid]['subject']);
608-
//# take off existing tracking code, if found
609-
if (strpos($link, 'utm_medium') !== false) {
610-
$link = preg_replace('/utm_(\w+)\=[^&]+&/U', '', $link);
611-
}
612-
//# 16894 make sure to keep the fragment value at the end of the URL
613-
if (strpos($link, '#')) {
614-
list($tmplink, $fragment) = explode('#', $link);
615-
$link = $tmplink;
616-
unset($tmplink);
617-
$fragment = '#'.$fragment;
618-
} else {
619-
$fragment = '';
620-
}
621-
622-
if (strpos($link, '?')) {
623-
$newurl = $link.'&'.$trackingcode.$fragment;
624-
} else {
625-
$newurl = $link.'?'.$trackingcode.$fragment;
626-
}
627-
// print $link. ' '.$newurl.' <br/>';
613+
$newurl = addAnalyticsTracking($link, $trackingParameters, $prefix);
628614
$newlink = sprintf('<a %shref="%s" %s>%s</a>', $links[1][$i], $newurl, $links[4][$i], $links[5][$i]);
629615
$htmlmessage = str_replace($links[0][$i], $newlink, $htmlmessage);
630616
}
631-
617+
/*
618+
* process plain-text format email
619+
*/
620+
$trackingParameters = $analytics->trackingParameters('text', loadMessageData($messageid));
632621
preg_match_all('#(https?://[^\s\>\}\,]+)#mis', $textmessage, $links);
633622
rsort($links[1]);
634623
$newlinks = array();
@@ -641,28 +630,7 @@ function sendEmail($messageid, $email, $hash, $htmlpref = 0, $rssitems = array()
641630

642631
if (preg_match('/^http|ftp/i', $link)) {
643632
// && !strpos($link,$clicktrack_root)) {
644-
$url = cleanUrl($link, array('PHPSESSID', 'uid'));
645-
//@alpha1: maybe source should be message id?
646-
$trackingcode = 'utm_source=phplist'.$messageid.'&utm_medium=email&utm_content=text&utm_campaign='.urlencode($cached[$messageid]['subject']);
647-
//# take off existing tracking code, if found
648-
if (strpos($link, 'utm_medium') !== false) {
649-
$link = preg_replace('/utm_(\w+)\=[^&]+/', '', $link);
650-
}
651-
//# 16894 make sure to keep the fragment value at the end of the URL
652-
if (strpos($link, '#')) {
653-
list($tmplink, $fragment) = explode('#', $link);
654-
$link = $tmplink;
655-
unset($tmplink);
656-
$fragment = '#'.$fragment;
657-
} else {
658-
$fragment = '';
659-
}
660-
if (strpos($link, '?')) {
661-
$newurl = $link.'&'.$trackingcode.$fragment;
662-
} else {
663-
$newurl = $link.'?'.$trackingcode.$fragment;
664-
}
665-
633+
$newurl = addAnalyticsTracking($link, $trackingParameters, $prefix);
666634
$newlinks[$i] = $newurl;
667635
$textmessage = str_replace($links[1][$i], '[%%%'.$i.'%%%]', $textmessage);
668636
}

public_html/lists/lt.php

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,6 @@
162162

163163
//# hmm a bit heavy to use here @@@optimise
164164
$messagedata = loadMessageData($messageid);
165-
$trackingcode = '';
166165
//print "$track<br/>";
167166
//print "User $userid, Mess $messageid, Link $linkid";
168167

@@ -180,11 +179,9 @@
180179
if ($msgtype == 'H') {
181180
Sql_query(sprintf('update %s set htmlclicked = htmlclicked + 1 where forwardid = %d and messageid = %d',
182181
$GLOBALS['tables']['linktrack_ml'], $fwdid, $messageid));
183-
$trackingcode = 'utm_source=phplist'.$messageid.'&utm_medium=email&utm_content=HTML&utm_campaign='.urlencode($messagedata['subject']);
184182
} else {
185183
Sql_query(sprintf('update %s set textclicked = textclicked + 1 where forwardid = %d and messageid = %d',
186184
$GLOBALS['tables']['linktrack_ml'], $fwdid, $messageid));
187-
$trackingcode = 'utm_source=phplist'.$messageid.'&utm_medium=email&utm_content=text&utm_campaign='.urlencode($messagedata['subject']);
188185
}
189186

190187
$viewed = Sql_Fetch_Row_query(sprintf('select viewed from %s where messageid = %d and userid = %d',
@@ -245,24 +242,13 @@
245242
}
246243

247244
if (!empty($messagedata['google_track'])) {
248-
//# take off existing tracking code, if found
249-
if (strpos($url, 'utm_medium') !== false) {
250-
$url = preg_replace('/utm_(\w+)\=[^&]+/', '', $url);
251-
}
252-
//# 16894 make sure to keep the fragment value at the end of the URL
253-
if (strpos($url, '#')) {
254-
list($tmplink, $fragment) = explode('#', $url);
255-
$url = $tmplink;
256-
unset($tmplink);
257-
$fragment = '#'.$fragment;
258-
} else {
259-
$fragment = '';
260-
}
261-
if (strpos($url, '?')) {
262-
$url = $url.'&'.$trackingcode.$fragment;
263-
} else {
264-
$url = $url.'?'.$trackingcode.$fragment;
265-
}
245+
require __DIR__ . '/admin/analytics.php';
246+
247+
$analytics = getAnalyticsQuery();
248+
$format = $msgtype == 'H' ? 'HTML' : 'text';
249+
$trackingParameters = $analytics->trackingParameters($format, loadMessageData($messageid));
250+
$prefix = $analytics->prefix();
251+
$url = addAnalyticsTracking($url, $trackingParameters, $prefix);
266252
}
267253

268254
foreach ($plugins as $pi) {

0 commit comments

Comments
 (0)