Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make compatible with WP and Joomla #1

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
178 changes: 49 additions & 129 deletions CRM/Sendgrid/Form/SendGrid.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,134 +10,54 @@
*/
class CRM_Sendgrid_Form_SendGrid extends CRM_Core_Form {

function buildQuickForm() {
require_once('CRM/Core/Resources.php');

$settings = sendgrid_get_settings();

$is_writable = is_writable(EXT_DIR);
$url = CRM_Core_Resources::singleton()->getUrl('com.imba.sendgrid') . 'webhook.php';
if ($settings['username']) {
$p = parse_url($url);
$url = "{$p['scheme']}://{$settings['username']}:{$settings['password']}@{$p['host']}" .
(!empty($p['port']) ? ":{$p['port']}" : '') . "{$p['path']}";
}

if (!$is_writable) {
$settings['username'] = $settings['password'] = 'DISABLED: see warning above';
$attr = 'disabled="disabled"';
}
else $attr = null;

$el = $this->add('text', 'username', ts('Username'), $attr);
if (!$is_writable)
$el->setSize(40);
$el = $this->add($is_writable ? 'password' : 'text', 'password', ts('Password'), $attr);
if (!$is_writable)
$el->setSize(40);
$el = $this->add('select', 'open_click_processor', ts('Open / Click Processing'));
$el->loadArray(array('Never' => ts('Do No Track'), 'CiviMail' => ts('CiviMail'), 'SendGrid' => ts('SendGrid')));
$el = $this->add('checkbox', 'track_optional', ts('Optional'), ts('When tracking, make it optional per mailing.'));
$el->setChecked((bool)$settings['track_optional']);

$this->addButtons(array(
array(
'type' => 'done',
'name' => 'Save Configuration',
)
));
$this->setDefaults($settings);

$this->assign('ext_dir', EXT_DIR);
$this->assign('is_writable', $is_writable);
$this->assign('url', $url);

parent::buildQuickForm();
}

function postProcess() {
$is_writable = is_writable(EXT_DIR);
// save settings to database
$vars = $this->getSubmitValues();

if (!$is_writable || !$vars['username'] || !$vars['password'])
$vars['username'] = $vars['password'] = '';
if (!isset($vars['track_optional']))
$vars['track_optional'] = '0';

$settings = sendgrid_get_settings();
foreach($vars as $k => $v) {
if (array_key_exists($k, $settings))
$settings[$k] = $v;
}

sendgrid_save_settings($settings);

// generate .htaccess and .htpasswd, but only if we have write access
if ($is_writable) {

if ($username = $vars['username']) {
$plainpasswd = $vars['password'];
// begin code I found on Stack Overflow...
// generates an apr1/md5 password for use in htpasswd files
$tmp = '';
$salt = substr(str_shuffle('abcdefghijklmnopqrstuvwxyz0123456789'), 0, 8);
$len = strlen($plainpasswd);
$text = $plainpasswd . '$apr1$' . $salt;
$bin = pack('H32', md5($plainpasswd . $salt . $plainpasswd));
for($i = $len; $i > 0; $i -= 16) {
$text .= substr($bin, 0, min(16, $i));
}
for($i = $len; $i > 0; $i >>= 1) {
$text .= ($i & 1) ? chr(0) : $plainpasswd{0};
}
$bin = pack('H32', md5($text));
for($i = 0; $i < 1000; $i++) {
$new = ($i & 1) ? $plainpasswd : $bin;
if ($i % 3)
$new .= $salt;
if ($i % 7)
$new .= $plainpasswd;
$new .= ($i & 1) ? $bin : $plainpasswd;
$bin = pack('H32', md5($new));
}
for ($i = 0; $i < 5; $i++) {
$k = $i + 6;
$j = $i + 12;
if ($j == 16) $j = 5;
$tmp = $bin[$i] . $bin[$k] . $bin[$j] . $tmp;
}
$tmp = chr(0) . chr(0) . $bin[11] . $tmp;
$tmp = strtr(strrev(substr(base64_encode($tmp), 2)),
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
'./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz');
$passwd = '$apr1$' . $salt . '$' . $tmp;
// ...end code I found on Stack Overflow

// create the .htpasswd file; works with both apache and nginx
file_put_contents(HTPASSWD, "$username:$passwd\n");
chmod(HTPASSWD, 0644);

// create the .htaccess for use with apache
file_put_contents(HTACCESS, "Options -Indexes\n\n" .
"AuthType Basic\n" .
"AuthName \"You Shall Not Pass\"\n" .
"AuthUserFile ". HTPASSWD . "\n" .
"Require valid-user\n");
chmod(HTACCESS, 0644);
}
else {
// if there is no username, delete the password file and reduce .htaccess to not produce indices
if (file_exists(HTPASSWD))
unlink(HTPASSWD);
file_put_contents(HTACCESS, "Options -Indexes\n");
chmod(HTACCESS, 0644);
}
}

parent::postProcess();

CRM_Core_Session::singleton()->pushUserContext('sendgrid');
}
public function buildQuickForm() {
$settings = sendgrid_get_settings();

$q = empty($settings['secretcode']) ? 'reset=1' : "reset=1&secretcode={$settings['secretcode']}";
$url = CRM_Utils_System::url('civicrm/sendgrid/webhook', $q, TRUE, NULL, FALSE, TRUE);

$attr = NULL;

$el = $this->add('text', 'secretcode', ts('Secret Code'), $attr);
$el->setSize(40);
$el = $this->add('select', 'open_click_processor', ts('Open / Click Processing'));
$el->loadArray(array('Never' => ts('Do No Track'), 'CiviMail' => ts('CiviMail'), 'SendGrid' => ts('SendGrid')));
$el = $this->add('checkbox', 'track_optional', ts('Optional'), ts('When tracking, make it optional per mailing.'));
$el->setChecked((bool) $settings['track_optional']);

$this->addButtons(array(
array(
'type' => 'done',
'name' => 'Save Configuration',
),
));
$this->setDefaults($settings);

$this->assign('url', $url);

parent::buildQuickForm();
}

public function postProcess() {
// save settings to database
$vars = $this->getSubmitValues();

if (!isset($vars['track_optional'])) {
$vars['track_optional'] = '0';
}

$settings = sendgrid_get_settings();
foreach ($vars as $k => $v) {
if (array_key_exists($k, $settings)) {
$settings[$k] = $v;
}
}

sendgrid_save_settings($settings);

parent::postProcess();

CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/sendgrid', 'reset=1'));
}

}
162 changes: 162 additions & 0 deletions CRM/Sendgrid/Page/Webhook.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
<?php

require_once 'CRM/Core/Page.php';

class CRM_Sendgrid_Page_Webhook extends CRM_Core_Page {
public function run() {
$events = json_decode(file_get_contents('php://input'));

try {
$secretCode = civicrm_api3('Setting', 'getvalue', array(
'name' => 'sendgrid_secretcode',
'group' => 'Sendgrid Preferences',
));
}
catch (CiviCRM_API3_Exception $e) {
$error = $e->getMessage();
CRM_Core_Error::debug_log_message(ts('API Error: %1', array(1 => $error, 'domain' => 'com.imba.sendgrid')));
}

if (!$events || !is_array($events)
|| (!empty($secretCode) && $secretCode != CRM_Utils_Array::value('secretcode', $_REQUEST))) {
// SendGrid sends a json encoded array of events
// if that's not what we get, we're done here
// or if the secret code doesn't match
header("HTTP/1.0 404 Not Found");
CRM_Utils_System::civiExit();
}

$config = CRM_Core_Config::singleton();
$delivered = array();

foreach ($events as $event) {

if (!empty($event->job_id)) {
/************
* CiviMail *
************/
$job_id = $event->job_id;
$event_queue_id = $event->event_queue_id;
$hash = $event->hash;

switch ($event->event) {
case 'delivered':
/*
$ts = $event->timestamp;
if (empty($delivered[$ts]))
$delivered[$ts] = array();
$delivered[$ts][] = $event_queue_id;
*/
break;

case 'deferred':
// temp failure, just write it to the log
CRM_Core_Error::debug_log_message("Sendgrid webhook (deferred)\n" . print_r($event, TRUE));
break;

case 'bounce':
self::bounce($job_id, $event_queue_id, $hash, $event->reason);
break;

case 'spamreport':
self::spamreport($job_id, $event_queue_id, $hash, $event->event);
break;

case 'unsubscribe':
self::unsubscribe($job_id, $event_queue_id, $hash, $event->event);
break;

case 'dropped':
// if dropped because of previous bounce, unsubscribe, or spam report, treat it as such...
// ...otherwise log it
if ($event->reason == 'Bounced Address') {
self::bounce($job_id, $event_queue_id, $hash, $event->reason);
}
elseif ($event->reason == 'Unsubscribed Address') {
self::unsubscribe($job_id, $event_queue_id, $hash, $event->event);
}
elseif ($event->reason == 'Spam Reporting Address') {
self::spamreport($job_id, $event_queue_id, $hash, $event->event);
}
else {
CRM_Core_Error::debug_log_message("Sendgrid webhook (dropped)\n" . print_r($event, TRUE));
}
break;

case 'open':
CRM_Mailing_Event_BAO_Opened::open($event_queue_id);
break;

case 'click':
// first off, strip off any utm_??? query parameters for google analytics
$info = parse_url($event->url);
if (!empty($info['query'])) {
$qs = array();
$pairs = explode('&', $info['query']);
foreach ($pairs as $pair) {
if (strpos($pair, 'utm_') !== 0) {
$qs[] = $pair;
}
}
$info['query'] = implode('&', $qs);

$event->url = $info['scheme'] . '://';
if (!empty($info['user']) && !empty($info['pass'])) {
$event->url .= $info['user'] . ':' . $info['pass'] . '@';
}
$event->url .= $info['host'];
$event->url .= CRM_Utils_Array::value('path', $info, '');
$event->url .= empty($info['query']) ? '' : '?' . $info['query'];
$event->url .= empty($info['fragment']) ? '' : '#' . $info['fragment'];
}
try {
$url = CRM_Core_DAO::escapeString($event->url);
$mailing_id = CRM_Core_DAO::singleValueQuery("SELECT mailing_id FROM civicrm_mailing_job WHERE id='$job_id'");
if ($url_id = CRM_Core_DAO::singleValueQuery("SELECT id FROM civicrm_mailing_trackable_url WHERE mailing_id='$mailing_id' AND url='$url'")) {
CRM_Mailing_Event_BAO_TrackableURLOpen::track($event_queue_id, $url_id);
}
}
catch (Exception $e) {
CRM_Core_Error::debug_log_message("SendGrid webhook (click)\n" . $e->getMessage());
}
break;
}
}
}

CRM_Utils_System::civiExit();
}

public static function bounce($job_id, $event_queue_id, $hash, $reason) {
try {
civicrm_api3('Mailing', 'event_bounce', array(
'job_id' => $job_id,
'event_queue_id' => $event_queue_id,
'hash' => $hash,
'body' => $reason,
));
}
catch (CiviCRM_API3_Exception $e) {
CRM_Core_Error::debug_log_message("SendGrid webhook (bounce)\n" . $e->getMessage());
}
}

public static function unsubscribe($job_id, $event_queue_id, $hash, $event) {
try {
civicrm_api3('MailingGroup', 'event_unsubscribe', array(
'job_id' => $job_id,
'event_queue_id' => $event_queue_id,
'hash' => $hash,
));
}
catch (CiviCRM_API3_Exception $e) {
CRM_Core_Error::debug_log_message("SendGrid webhook ($event)\n" . $e->getMessage());
}
}

public static function spamreport($job_id, $event_queue_id, $hash, $event) {
CRM_Mailing_Event_BAO_SpamReport::report($event_queue_id);
self::unsubscribe($job_id, $event_queue_id, $hash, $event);
}

}
Loading