function engagements_handle_incoming_mailgun_email
Search API
| 7.x engagements.module | engagements_handle_incoming_mailgun_email() |
2 string references to 'engagements_handle_incoming_mailgun_email'
- engagements.module in modules/
engagements/ engagements.module - This is the primary module file for the engagements module.
- engagements_menu in modules/
engagements/ engagements.module - Implement hook_menu
File
- modules/
engagements/ engagements.module, line 204 - This is the primary module file for the engagements module.
Code
function engagements_handle_incoming_mailgun_email() {
watchdog('debug', 'From Post: <pre>' . print_r($_POST, TRUE) . '</pre>', array(), WATCHDOG_DEBUG);
watchdog('debug', 'From Files: <pre>' . print_r($_FILES, TRUE) . '</pre>', array(), WATCHDOG_DEBUG);
// See Mailgun's Securing Web hooks documentation to know what to do with "signature" and "token", to confirm
// it came from MailGun. https://www.mailgun.com/blog/email/your-guide-to-webhooks/#chapter-5
// This code shows how we verify....
// if (isset($event->signature) && hash_hmac('sha256', $event->signature->timestamp.$event->signature->token, $key) === $event->signature->signature) {
// In this case, $key is our mailgun api key. Configured in the Engagements - email settings section.
// This should be the "HTTP webhook signing Key". Go to mailgun dashboard. Lower right will be a link to the API Keys
$timestamp = fp_trim(@$_POST ['timestamp']);
$token = fp_trim(@$_POST ['token']);
$signature = fp_trim(@$_POST ['signature']);
$api_key = variable_get('engagements_mailgun_api_key', 'NONE SET');
$confirm_hash = hash_hmac('sha256', $timestamp . $token, $api_key);
// Before continuing, verify that the signature is correct (that we really came from MailGun)
if (!$signature || strlen($signature) == 0 || $confirm_hash !== $signature) {
watchdog('engagements_mailgun', "Tried to receive mailgun email, but could not verify signature. timestamp: $timestamp, token: $token, signature: $signature, confirm_hash = $confirm_hash", array(), WATCHDOG_ERROR);
// As this was some sort of hacking attempt, we don't want mailgun to keep trying to notify us of the message,
// so send back 200 and exit.
// To tell MailGun not to retry this message, we need to deliver a 200 response. Otherwise they will keep trying for 3 days.
http_response_code(200);
exit;
}
// If we are here, it means this email is valid and we can continue.
$sender = $_POST ['sender'];
$recipient = $_POST ['recipient'];
$body_html = $_POST ['body-html'];
$body_plain = $_POST ['body-plain'];
$body_stripped_html = $_POST ['stripped-html'];
$body_stripped_text = $_POST ['stripped-text'];
$subject = $_POST ['subject'];
$timestamp = intval($_POST ['timestamp']); // in UTC already?
$date = $_POST ['Date'];
$body = $body_stripped_text;
if ($body_plain) {
$body = $body_plain;
}
if ($body_html) {
$body = $body_html;
}
// Sometimes, emails contain odd encoding. Ex: =C2=A0, which is apparently a non-breaking space. Let's convert just in case.
// From: https://stackoverflow.com/questions/12682208/parsing-email-body-with-7bit-content-transfer-encoding-php
// and: https://github.com/geerlingguy/Imap/blob/c4b54e52576bf71a93045d2a4581589eab9a00b6/JJG/Imap.php#L398
// Manually convert common encoded characters into their UTF-8 equivalents.
$characters = array(
'=20' => ' ', // space.
'=2C' => ',', // comma.
'=E2=80=99' => "'", // single quote.
'=C2=A0' => ' ', // non-breaking space.
'=0A' => "\r\n", // line break.
'=0D' => "\r\n", // carriage return.
'=A0' => ' ', // non-breaking space.
"=\r\n" => '', // joined line.
'=E2=80=A6' => '…', // ellipsis.
'=E2=80=A2' => '•', // bullet.
'=E2=80=93' => '–', // en dash.
'=E2=80=94' => '—', // em dash.
);
// Loop through the encoded characters and replace any that are found.
foreach ($characters as $key => $value) {
$body = str_replace($key, $value, $body);
}
// This will map any inline images in the body of the email to their attachment names.
// Example, this is in the body_html where an inline image was:
// <img title="Inline image" alt="Inline image" src="cid:285ac981-ff52-55bb-f766-fb05ec0c9a74@yahoo.com" class="yahoo-inline-image" draggable="false" style="max-width: 800px; width: 100%;" data-id="<285ac981-ff52-55bb-f766-fb05ec0c9a74@yahoo.com>">
// Notice the src.
// Now, in the content-id-map, we see this:
// {"<06d5a647-5600-086a-12e2-29a3dc32a7c8@yahoo.com>":"attachment-3",
// "<138bab3e-5b05-91d2-133b-adbba379dd23@yahoo.com>":"attachment-2",
// "<285ac981-ff52-55bb-f766-fb05ec0c9a74@yahoo.com>":"attachment-1"}
// Notice that 285ac......@yahoo.com says it matches "attachment-1". That is the same src= for our inline image tag.
//
// Note: From gmail, the content-id-map has no useful information. Very strange. But what we DO have is the "alt" tag.
// See: <img src="cid:ii_lwy9bb9j1" alt="um actually.png" width="399" height="398">
// We can use THAT to figure out the inline image, since [name] in the FILES states the filename.
$content_id_map = fp_trim(@$_POST ['content-id-map']);
// Our attachments are in $_FILES...
/*
* Array
(
[attachment-1] => Array
(
[name] => The_More_You_Know_0-0_screenshot.jpg
[full_path] => The_More_You_Know_0-0_screenshot.jpg
[type] => image/jpeg
[tmp_name] => /tmp/phpV7mds1
[error] => 0
[size] => 33147
)
[attachment-2] => Array
(
[name] => ATS notes.rtf
[full_path] => ATS notes.rtf
[type] => application/rtf
[tmp_name] => /tmp/phph4nE9m
[error] => 0
[size] => 10728
)
[attachment-3] => Array
(
[name] => jetstream script.odt
[full_path] => jetstream script.odt
[type] => application/vnd.oasis.opendocument.text
[tmp_name] => /tmp/phpbbQK2W
[error] => 0
[size] => 32185
)
)
*/
// Curiously, mailgun one time sent me the same email twice. We didn't have attachments with the first POST, but we did on the second. We should
// always overwrite any email we've already got with a new one, if such comes in.
// We can't use the Message-Id to identify unique messages!
// Note: from Gmail, the Message-Id is blank! In that case, we can hash the date, sender, recipient, subject, message, etc, to create our own ID.
$external_msg_id = hash('sha256', $sender . $subject . $date . $recipient . $body . $body_html . $body_plain . $body_stripped_text . $content_id_map);
// Create the email entry in FlightPath:
// Match the sender's email to a student.
$student_user_id = db_get_user_id_from_email($sender, 'student');
if ($student_user_id) {
watchdog('engagements_mailgun', "Found from student $student_user_id, creating engagement content.", array(), WATCHDOG_DEBUG);
// create this as an engagement for this student.
$account = fp_load_user($student_user_id);
$student_cwid = $account->cwid;
// TODO: If another engagement with external_msg_id exists, then load that instead of
// TODO: creating a new one. ((Is this necessary?))
$content = new stdClass();
$content->type = 'engagement';
$content->cid = "new";
$content->published = 1;
$content->delete_flag = 0;
$content->title = $subject; // required
$content->field__activity_datetime ['value'] = date('Y-m-d H:i:s', $timestamp);
$content->field__student_id ['value'] = $student_cwid;
$content->field__engagement_type ['value'] = 'email';
$content->field__direction ['value'] = 'received';
$content->field__engagement_msg ['value'] = filter_markup($body, 'basic');
$content->field__external_msg_id ['value'] = $external_msg_id;
content_save($content);
$cid = $content->cid; // We need the cid to already be created in order to save attachments
// add attachments
$attachments = array();
if ($content_id_map) {
$map = json_decode($content_id_map, TRUE);
watchdog('debug', 'map is: <pre>' . print_r($map, TRUE) . "</pre>", array(), WATCHDOG_DEBUG);
$sender_machine_readable = fp_get_machine_readable($sender);
foreach ($_FILES as $xid => $details) {
$filename_prefix = "email__" . $sender_machine_readable;
$file = array(
'name' => $details ['name'],
'type' => $details ['type'],
'tmp_name' => $details ['tmp_name'],
);
$fid = content_add_new_uploaded_file($file, $cid, TRUE, CONTENT_ENCRYPTED_FILE, FALSE, $filename_prefix);
if (!$fid) {
watchdog('engagements_mailgun', 'Unable to save email attachment file: @fn', array('@fn' => $file ['name']), WATCHDOG_ERROR);
$body .= "\n\n[ATTACHMENT UNABLE TO SAVE. Check with system administrator.]";
continue;
}
// If we are here, it means we DID save the file.
// Modify body as necessary. Replace embedded <img> tag with a message stating that for
// security, the image is encrypted and stored with attachments?
if (strstr($body, "<img")) {
// Go through and replace elements from our map with names... Helps fix a bug with Yahoo where the preg_replace breaks.
$c = 1;
foreach ($map as $e => $v) {
$body = str_replace($e, $v, $body);
$body = preg_replace("/<img[^>]+\>/i", "<div class='email-inline-img-replace'>Inline image #$c securely encrypted - See attachments</div> ", $body, 1);
$c++;
}
// Get any others left not replaced in the foreach above.
$body = preg_replace("/<img[^>]+\>/i", "<div class='email-inline-img-replace'>Inline image securely encrypted - See attachments</div> ", $body);
}
// Add to our list of attachments
$attachments [] = array('fid' => $fid);
} // foreach FILES
} // if content_id_map
// save attachements along with the engagement, if there are any
if (count($attachments) > 0) {
$fid_line = "";
foreach ($attachments as $adetails) {
$fid = $adetails ['fid'];
$fid_line .= $fid . ",";
}
$fid_line = rtrim($fid_line, ",");
$content->field__attachment ['value'] = $fid_line;
}
// Re-add body, as it may have changed because of attachments.
$content->field__engagement_msg ['value'] = filter_markup($body, 'basic');
$content->field__visibility ['value'] = 'public';
content_save($content);
// if received, create an activity record.
// Create a new "activity record" that the student has sent an email message
$acontent = new stdClass();
$acontent->type = 'activity_record';
$acontent->cid = "new";
$acontent->published = 1;
$acontent->delete_flag = 0;
$acontent->title = t('Student replied to email message.');
$acontent->field__student_id ['value'] = $student_cwid;
$acontent->field__activity_type ['value'] = 'mail';
content_save($acontent);
// Notify the advisor(s).
// get list of all advisors for this student, and send notifications.
$advisors = advise_get_advisors_for_student($student_cwid);
foreach ($advisors as $c => $afaculty_id) {
$account_user_id = db_get_user_id_from_cwid($afaculty_id, 'faculty');
if ($account_user_id) {
$student_name = fp_get_student_name($student_cwid, TRUE);
$base_url = $GLOBALS ['fp_system_settings']['base_url'];
$link = $base_url . "/engagements?current_student_id=$student_cwid";
$tmsg = "";
$tmsg .= "$student_name has submitted an email to FlightPath.<br><br>\n\n
To view, visit the student's Engagements tab by logging in and following this link:<br>\n
<a href='$link'>$link</a>";
notify_send_notification_to_user($account_user_id, $tmsg, $content->cid, 'engagement');
}
}
} // if student_user_id
else {
// The student_user_id could not be found! This is possibly spam.
// TODO: handle the rejection of this email?
}
// To tell MailGun not to retry this message, we need to deliver a 200 response. Otherwise they will keep trying for 3 days.
http_response_code(200);
exit;
}
