Skip to content

Commit

Permalink
[#2] implemented anonymous click tracking (WIP)
Browse files Browse the repository at this point in the history
  • Loading branch information
bjendres committed Jan 18, 2019
1 parent f510e99 commit f2d74e3
Show file tree
Hide file tree
Showing 14 changed files with 413 additions and 50 deletions.
51 changes: 34 additions & 17 deletions CRM/Mailingtools/AnonymousOpen.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,48 @@ public static function processAnonymousOpenEvent($mid) {
throw new Exception("Invalid mailing ID");
}

// NOW: find the event queue ID
$event_queue_id = NULL;
// get a matching event queue ID
$event_queue_id = self::getEventQueueID($mid, 'anonymous_open_contact_id');

// ERROR: if this is not set yet, something is wrong.
if (empty($event_queue_id)) {
throw new Exception("No found event in queue for mailing [{$mid}]");
}

// all good: add entry
CRM_Core_Error::debug_log_message("Tracked anonymous open event for mailing [{$mid}]");
CRM_Core_DAO::executeQuery("
INSERT INTO civicrm_mailing_event_opened (event_queue_id, time_stamp)
VALUES (%1, NOW())", [
1 => [$event_queue_id, 'Integer']]);

return $event_queue_id;
}



/**
* Get (or create) an event queue ID for the given
* @param integer $mid mailing ID
* @param string $default_contact_setting setting that yields the default contact ID
*
* @return integer|null event queue ID
* @throws Exception if anything went wrong
*/
public static function getEventQueueID($mid, $default_contact_setting = NULL) {
$config = CRM_Mailingtools_Config::singleton();

// FIRST: try by preferred contact
$preferred_contact_id = (int) $config->getSetting('anonymous_open_contact_id');
$preferred_contact_id = (int) $config->getSetting($default_contact_setting);
if ($preferred_contact_id) {
$event_queue_id = CRM_Core_DAO::singleValueQuery("
SELECT queue.id
FROM civicrm_mailing_event_queue queue
LEFT JOIN civicrm_mailing_job job ON queue.job_id = job.id
WHERE queue.contact_id = %1
AND job.mailing_id = %2", [
1 => [$preferred_contact_id, 'Integer'],
2 => [$mid, 'Integer']]);
1 => [$preferred_contact_id, 'Integer'],
2 => [$mid, 'Integer']]);

// check if this worked...
if (empty($event_queue_id)) {
Expand Down Expand Up @@ -87,21 +115,10 @@ public static function processAnonymousOpenEvent($mid) {
}
}

// ERROR: if this is not set yet, something is wrong.
if (empty($event_queue_id)) {
throw new Exception("No found event in queue for mailing [{$mid}]");
}

// all good: add entry
CRM_Core_Error::debug_log_message("Tracked anonymous open event for mailing [{$mid}]");
CRM_Core_DAO::executeQuery("
INSERT INTO civicrm_mailing_event_opened (event_queue_id, time_stamp)
VALUES (%1, NOW())", [
1 => [$event_queue_id, 'Integer']]);

return $event_queue_id;
}


/**
* This function will manipulate open tracker URLs in emails, so they point
* to the anonymous handler instead of the native one
Expand Down
97 changes: 97 additions & 0 deletions CRM/Mailingtools/AnonymousURL.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?php
/*-------------------------------------------------------+
| SYSTOPIA Mailingtools Extension |
| Copyright (C) 2019 SYSTOPIA |
| Author: B. Endres ([email protected]) |
+--------------------------------------------------------+
| This program is released as free software under the |
| Affero GPL license. You can redistribute it and/or |
| modify it under the terms of this license which you |
| can read by viewing the included agpl.txt or online |
| at www.gnu.org/licenses/agpl.html. Removal of this |
| copyright header is strictly prohibited without |
| written permission from the original author(s). |
+--------------------------------------------------------*/


use CRM_Mailingtools_ExtensionUtil as E;

/**
* Processor for anonymous url tracking
*/
class CRM_Mailingtools_AnonymousURL {

/**
* Process an anonymous url tracking event
*
* @param $trackable_url_id int URL id
* @return string|null the URL this ID belongs to
* @throws Exception if something failed.
*/
public static function processAnonymousClickEvent($trackable_url_id) {
$config = CRM_Mailingtools_Config::singleton();

// check if we're enabled
$enabled = $config->getSetting('anonymous_link_enabled');
if (!$enabled) {
return NULL;
}

// check the link ID...
$trackable_url_id = (int) $trackable_url_id;
if (!$trackable_url_id) {
throw new Exception("Bad link ID");
}
// load the link
$link = CRM_Core_DAO::executeQuery("
SELECT mailing_id, url
FROM civicrm_mailing_trackable_url
WHERE id = %1", [1 => [$trackable_url_id, 'Integer']]);
if (!$link->fetch()) {
throw new Exception("Invalid link ID");
}

// NOW: find a matching event queue ID
$event_queue_id = CRM_Mailingtools_AnonymousOpen::getEventQueueID($link->mailing_id, 'anonymous_link_contact_id');
if (empty($event_queue_id)) {
throw new Exception("No found event in queue for mailing [{$link->mailing_id}]");
}

// all good: add entry
CRM_Core_Error::debug_log_message("Tracked anonymous click event for link {$trackable_url_id} in mailing [{$link->mailing_id}]");
CRM_Core_DAO::executeQuery("
INSERT INTO civicrm_mailing_event_trackable_url_open (event_queue_id, trackable_url_id, time_stamp)
VALUES (%1, %2, NOW())", [
1 => [$event_queue_id, 'Integer'],
2 => [$trackable_url_id, 'Integer']]);

return $link->url;
}



/**
* This function will manipulate open tracker URLs in emails, so they point
* to the anonymous handler instead of the native one
*/
public static function modifyEmailBody(&$body) {
$config = CRM_Mailingtools_Config::singleton();
if (!$config->getSetting('anonymous_link_enabled')
|| !$config->getSetting('anonymous_link_url')) {
// NOT ENABLED
return;
}

// get the base URL
$core_config = CRM_Core_Config::singleton();
$system_base = $core_config->userFrameworkBaseURL;

// find all all relevant links and collect queue IDs
if (preg_match_all("#{$system_base}sites/all/modules/civicrm/extern/url.php\?u=(?P<link_id>[0-9]+)[^'\"\\n]+#i", $body, $matches)) {
foreach ($matches[0] as $i => $string) {
$new_url = $config->getSetting('anonymous_link_url') . "?u={$matches['link_id'][$i]}";
$body = str_replace($string, $new_url, $body);
}
}
}
}
70 changes: 55 additions & 15 deletions CRM/Mailingtools/Form/Settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public function buildQuickForm() {
FALSE
);

// anonymous mailing stuff
// ANONYMOUS open mailing stuff
$this->add(
'checkbox',
'anonymous_open_enabled',
Expand Down Expand Up @@ -89,18 +89,52 @@ public function buildQuickForm() {
["style" => "width: 50px;"],
FALSE
);
// load contact
$this->renderContact($current_values);

$this->renderContact($current_values, 'open');

// ANONYMOUS link tracking stuff
$this->add(
'checkbox',
'anonymous_link_enabled',
E::ts('Enabled')
);

$this->add(
'text',
'anonymous_link_url',
E::ts('URL Endpoint'),
['class' => 'huge'],
FALSE
);

$this->add(
'select',
'anonymous_link_permission',
E::ts('API Permission'),
CRM_Core_Permission::basicPermissions(TRUE),
FALSE
);

$this->add(
'text',
'anonymous_link_contact_id',
E::ts('Anonymous Contact ID'),
["style" => "width: 50px;"],
FALSE
);

// load contacts
$this->renderContact($current_values, 'link');

// set default values
$this->setDefaults($current_values);

// submit
$this->addButtons(array(
array(
'type' => 'submit',
'name' => E::ts('Save'),
'isDefault' => TRUE,
'type' => 'submit',
'name' => E::ts('Save'),
'isDefault' => TRUE,
),
));

Expand All @@ -111,25 +145,26 @@ public function buildQuickForm() {
/**
* Render the current anonymous_open_contact_id value
*
* @param $data
* @param $data array data
* @param $key string key (open|link)
* @throws CiviCRM_API3_Exception
*/
protected function renderContact($data) {
if (!empty($data['anonymous_open_contact_id'])) {
$contact_id = (int) $data['anonymous_open_contact_id'];
protected function renderContact($data, $key) {
if (!empty($data["anonymous_{$key}_contact_id"])) {
$contact_id = (int) CRM_Utils_Array::value("anonymous_{$key}_contact_id", $data, 0);
if ($contact_id) {
$result = civicrm_api3('Contact', 'get', ['id' => $contact_id, 'return' => 'display_name,contact_type']);
if (!empty($result['id'])) {
$contact = reset($result['values']);
$this->assign('anonymous_open_contact_name', "{$contact['display_name']} ({$contact['contact_type']})");
$this->assign("anonymous_{$key}_contact_name", "{$contact['display_name']} ({$contact['contact_type']})");
} else {
$this->assign('anonymous_open_contact_name', E::ts("Contact [%1] not found!", [1 => $contact_id]));
$this->assign("anonymous_{$key}_contact_name", E::ts("Contact [%1] not found!", [1 => $contact_id]));
}
} else {
$this->assign('anonymous_open_contact_name', E::ts("Bad contact ID: '%1'", [1 => $data['anonymous_open_contact_id']]));
$this->assign("anonymous_{$key}_contact_name", E::ts("Bad contact ID: '%1'", [1 => CRM_Utils_Array::value("anonymous_{$key}_contact_id", $data, '')]));
}
} else {
$this->assign('anonymous_open_contact_name', E::ts("disabled"));
$this->assign("anonymous_{$key}_contact_name", E::ts("disabled"));
}
}

Expand All @@ -148,6 +183,10 @@ protected function getSettingsInForm() {
'anonymous_open_url',
'anonymous_open_permission',
'anonymous_open_contact_id',
'anonymous_link_enabled',
'anonymous_link_url',
'anonymous_link_permission',
'anonymous_link_contact_id',
);
}

Expand All @@ -165,7 +204,8 @@ public function postProcess() {
$config->setSettings($settings);

// re-render new value
$this->renderContact($values);
$this->renderContact($values, 'open');
$this->renderContact($values, 'link');

parent::postProcess();
}
Expand Down
7 changes: 4 additions & 3 deletions CRM/Mailingtools/Mailer.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
class CRM_Mailingtools_Mailer {

/**
* this is the orginal, wrapped mailer
* this is the original, wrapped mailer
*/
protected $mailer = NULL;

Expand All @@ -28,8 +28,8 @@ class CRM_Mailingtools_Mailer {
*/
public static function isNeeded() {
$config = CRM_Mailingtools_Config::singleton();
return $config->getSetting('anonymous_open_enabled')
&& $config->getSetting('anonymous_open_url');
return ($config->getSetting('anonymous_open_enabled') && $config->getSetting('anonymous_open_url'))
|| ($config->getSetting('anonymous_link_enabled') && $config->getSetting('anonymous_link_url'));
}

/**
Expand All @@ -45,6 +45,7 @@ public function __construct($mailer) {
*/
function send($recipients, $headers, $body) {
CRM_Mailingtools_AnonymousOpen::modifyEmailBody($body);
CRM_Mailingtools_AnonymousURL::modifyEmailBody($body);
$this->mailer->send($recipients, $headers, $body);
}
}
2 changes: 1 addition & 1 deletion api/v3/Mailingtools/Anonopen.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ function civicrm_api3_mailingtools_anonopen($params) {
return civicrm_api3_create_success("Anonymous open tracking disabled.");
}
} catch (Exception $ex) {
throw new CiviCRM_API3_Exception($ex->getMessage());
throw new CiviCRM_API3_Exception($ex->getMessage(), $ex->getCode());
}
}

Expand Down
54 changes: 54 additions & 0 deletions api/v3/Mailingtools/Anonurl.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php
/*-------------------------------------------------------+
| SYSTOPIA Mailingtools Extension |
| Copyright (C) 2018-2019 SYSTOPIA |
| Author: B. Endres ([email protected]) |
+--------------------------------------------------------+
| This program is released as free software under the |
| Affero GPL license. You can redistribute it and/or |
| modify it under the terms of this license which you |
| can read by viewing the included agpl.txt or online |
| at www.gnu.org/licenses/agpl.html. Removal of this |
| copyright header is strictly prohibited without |
| written permission from the original author(s). |
+--------------------------------------------------------*/

use CRM_Mailingtools_ExtensionUtil as E;


/**
* API: Mailingtools.Anonurl
*
* Processes an anonymous click event on a trackable URL
* based only on the mailing ID and the link ID
*
* @param array $params containing 'mid'
* @return array result;
* @throws CiviCRM_API3_Exception
*/
function civicrm_api3_mailingtools_anonurl($params) {
try {
$url = CRM_Mailingtools_AnonymousURL::processAnonymousClickEvent($params['u']);
if ($url) {
$link_id = (int) $params['u'];
return civicrm_api3_create_success([$link_id => $url]);
} else {
return civicrm_api3_create_success("Anonymous click tracking disabled.");
}
} catch (Exception $ex) {
throw new CiviCRM_API3_Exception($ex->getMessage(), $ex->getCode());
}
}

/**
* API Specs: Mailingtools.Anonopen
*/
function _civicrm_api3_mailingtools_anonurl_spec(&$spec) {
$spec['u'] = array(
'name' => 'u',
'api.required' => 1,
'type' => CRM_Utils_Type::T_INT,
'title' => 'URL ID',
'description' => 'URL ID for which the click event should be recorded',
);
}
Loading

0 comments on commit f2d74e3

Please sign in to comment.