How to extract only HTML from imap_body result - php

I want to extract only the HTML content from a imap_body result.
The imap_body give a verbatim copy of the mail.

I found a solution:
function getBody($uid, $imap)
{
$body = $this->get_part($imap, $uid, "TEXT/HTML");
// if HTML body is empty, try getting text body
if ($body == "") {
$body = $this->get_part($imap, $uid, "TEXT/PLAIN");
}
return $body;
}
function get_part($imap, $uid, $mimetype, $structure = false, $partNumber = false)
{
if (!$structure) {
$structure = imap_fetchstructure($imap, $uid, FT_UID);
}
if ($structure) {
if ($mimetype == $this->get_mime_type($structure)) {
if (!$partNumber) {
$partNumber = 1;
}
$text = imap_fetchbody($imap, $uid, $partNumber, FT_UID);
switch ($structure->encoding) {
case 3:
return imap_base64($text);
case 4:
return imap_qprint($text);
default:
return $text;
}
}
// multipart
if ($structure->type == 1) {
foreach ($structure->parts as $index => $subStruct) {
$prefix = "";
if ($partNumber) {
$prefix = $partNumber . ".";
}
$data = $this->get_part($imap, $uid, $mimetype, $subStruct, $prefix . ($index + 1));
if ($data) {
return $data;
}
}
}
}
return false;
}
function get_mime_type($structure)
{
$primaryMimetype = ["TEXT", "MULTIPART", "MESSAGE", "APPLICATION", "AUDIO", "IMAGE", "VIDEO", "OTHER"];
if ($structure->subtype) {
return $primaryMimetype[(int)$structure->type] . "/" . $structure->subtype;
}
return "TEXT/PLAIN";
}

http://php.net/manual/en/function.imap-fetchbody.php
Parameter 3, "the section" is as follows:
The part number. It is a string of integers delimited by period which index into a body part list as per the IMAP4 specification
(empty) - Entire message
0 - Message header
1 - MULTIPART/ALTERNATIVE
1.1 - TEXT/PLAIN
1.2 - TEXT/HTML
2 - file.ext
Therefore, to grab the HTML part of the mail, you would have to use the 1.2 option as the third parameter. Like so:
$message = imap_fetchbody($inbox, $number, 1.2);

I don't have enough reputation to add a comment, but I just wanted to clarify in #GunniH's answer that your call to the function should look like this:
$message = imap_fetchbody($inbox, $number, '1.2');
instead of this
$message = imap_fetchbody($inbox, $number, 1.2);
That final argument should be a string, not an int.

Related

PHP IMAP. Stop Messages Being Marked as Seen

I'm retrieving Gmail e-mails through IMAP. I'm setting the FT_PEEK option wherever I can, and finally, I've even opened the mailbox as read-only (OP_READONLY). Yet, my code is marking the messages as read.
Here is the code:
class imap{
CONST HOSTNAME='{imap.gmail.com:993/imap/ssl}INBOX';
CONST USERNAME = '[Address hidden]';
CONST PASSWORD = '[Password hidden]'; //App password from Google
function getMessagesSince($date){
//This will return a collection of email objects.
$messages=array();
if(!$imap=imap_open($this::HOSTNAME, $this::USERNAME, $this::PASSWORD, OP_READONLY)) throw new exception("Unable to connect to IMAP mailbox. ".imap_last_error());
$since=date_format($date, 'j F Y');
$emails=imap_search($imap, 'SINCE "'.$since.'"', SE_UID|FT_PEEK);
foreach($emails as $email){
$messages[]=$this->getMessage($email, $imap);
}
imap_close($imap);
return $messages;
}
private function getMessage($uid, $imap){
//First get the headers
$headers=$this->getHeaders($uid, $imap);
$datereceived=date('Y-m-d H:i:s', strtotime($headers->date));
$sender=$headers->from[0]->mailbox."#".$headers->from[0]->host;
$cc=$headers->cc;
$subject=$headers->subject;
//Now get the message body
$message=$this->getBody($uid, $imap);
$email=new email();
$email->uid=$uid;
$email->datereceived=$datereceived;
$email->sender=$sender;
$email->cc=$cc;
$email->subject=$subject;
$email->message=$message;
return $email;
}
private function getHeaders($uid, $imap){
//Return an array of headers for the referenced message
//$overview = imap_fetch_overview($imap, $uid, FT_UID); //As we used the SE_UID flag when searching, we have to use it when fetching.
//We use this, rather than fetch_overview, because the overview doesn't have the cc information.
$hText = imap_fetchbody($imap, $uid, '0', FT_UID|FT_PEEK);
$headers = imap_rfc822_parse_headers($hText);
return $headers;
}
private function getBody($uid, $imap){
$body = $this->get_part($imap, $uid, "TEXT/HTML");
// if HTML body is empty, try getting text body
if ($body == "") {
$body = $this->get_part($imap, $uid, "TEXT/PLAIN");
}
return $body;
}
private function get_part($imap, $uid, $mimetype, $structure = false, $partNumber = false){
if (!$structure) {
$structure = imap_fetchstructure($imap, $uid, FT_UID);
}
if ($structure) {
if ($mimetype == $this->get_mime_type($structure)) {
if (!$partNumber) {
$partNumber = 1;
}
$text = imap_fetchbody($imap, $uid, $partNumber, FT_UID|FT_PEEK);
switch ($structure->encoding) {
case 3:
return imap_base64($text);
case 4:
return imap_qprint($text);
default:
return $text;
}
}
// multipart
if ($structure->type == 1) {
foreach ($structure->parts as $index => $subStruct) {
$prefix = "";
if ($partNumber) {
$prefix = $partNumber . ".";
}
$data = $this->get_part($imap, $uid, $mimetype, $subStruct, $prefix . ($index + 1));
if ($data) {
return $data;
}
}
}
}
return false;
}
private function get_mime_type($structure){
$primaryMimetype = ["TEXT", "MULTIPART", "MESSAGE", "APPLICATION", "AUDIO", "IMAGE", "VIDEO", "OTHER"];
if ($structure->subtype) {
return $primaryMimetype[(int)$structure->type] . "/" . $structure->subtype;
}
return "TEXT/PLAIN";
}
}
Can anyone spot something I'm missing?
(This is just some extra text, because it's saying that the post is mostly code. I'm just typing here until it lets me post.)
You should delete OP_READONLY flag from imap_open() .

How to return the mail html in cscart?

I am using the following function to send html mail in cscart.
$value = fn_send_mail($user_data['email'], Registry::get('settings.Company.company_users_department'), 'addons/test/test_subj.tpl', 'addons/test/test_body.tpl');
If i print $value means it only return 1. How do i return html in this function.
I found it :)
To display/retrun html template please use this following code.
$body = Registry::get('view_mail')->display($body, false);
print_r($body);
You have 2 options:
Option 1
You create your on mail sender function which will return the body, and you use this in your add-on. For example:
function custom_send_mail($to, $from, $subj, $body, $attachments = array(), $lang_code = CART_LANGUAGE, $reply_to = '', $is_html = true)
{
fn_disable_translation_mode();
$__from = array();
$__to = array();
fn_init_mailer();
$mailer = & Registry::get('mailer');
$languages = Registry::get('languages');
Registry::get('view_mail')->setLanguage($lang_code);
fn_set_hook('send_mail_pre', $mailer, $to, $from, $subj, $body, $attachments, $lang_code, $reply_to, $is_html);
if (!empty($reply_to)) {
$mailer->ClearReplyTos();
$reply_to = fn_format_emails($reply_to);
foreach ($reply_to as $rep_to) {
$mailer->AddReplyTo($rep_to);
}
}
if (!is_array($from)) {
$__from['email'] = $from;
} else {
$__from = $from;
}
if (empty($__from['email'])) {
$__from['email'] = Registry::get('settings.Company.company_site_administrator');
}
if (empty($__from['name'])) {
$__from['name'] = Registry::get('settings.Company.company_name');
}
$mailer->SetFrom($__from['email'], $__from['name']);
$mailer->IsHTML($is_html);
$mailer->CharSet = CHARSET;
$mailer->Subject = Registry::get('view_mail')->display($subj, false);
$mailer->Subject = trim($mailer->Subject);
$body = Registry::get('view_mail')->display($body, false);
$mailer->Body = fn_attach_images($body, $mailer);
if (!empty($attachments)) {
foreach ($attachments as $name => $file) {
$mailer->AddAttachment($file, $name);
}
}
$__to = fn_format_emails($to);
foreach ($__to as $v) {
$mailer->ClearAddresses();
$mailer->AddAddress($v, '');
$result = $mailer->Send();
if (!$result) {
fn_set_notification('E', fn_get_lang_var('error'), fn_get_lang_var('error_message_not_sent') . ' ' . $mailer->ErrorInfo);
}
fn_set_hook('send_mail', $mailer);
}
return $body;
}
Option 2
You can connect to send_mail_pre hook via your add-on:
function fn_youraddonname_send_mail_pre($mailer, $to, $from, $subj, $body, $attachments, $lang_code, $reply_to, $is_html) {
$rendered_body = Registry::get('view_mail')->display($body, false);
// use your custom code, to do anything you want
// this code will run everytime CS-Cart use the fn_send_mail function
}

How do I read the mail body from Gmail API using PHP? [duplicate]

I'm having trouble with the Gmail PHP API.
I want to retrieve the body content of emails, but I can retrieve it only for emails which have attachments! My question is why?
Here's my code so far:
// Authentication things above...
$client = getClient();
$gmail = new Google_Service_Gmail($client);
$list = $gmail->users_messages->listUsersMessages('me', ['maxResults' => 1000]);
while ($list->getMessages() != null) {
foreach ($list->getMessages() as $mlist) {
$message_id = $mlist->id;
$optParamsGet2['format'] = 'full';
$single_message = $gmail->users_messages->get('me', $message_id, $optParamsGet2);
$threadId = $single_message->getThreadId();
$payload = $single_message->getPayload();
$headers = $payload->getHeaders();
$parts = $payload->getParts();
//print_r($parts); PRINTS SOMETHING ONLY IF I HAVE ATTACHMENTS...
$body = $parts[0]['body'];
$rawData = $body->data;
$sanitizedData = strtr($rawData,'-_', '+/');
$decodedMessage = base64_decode($sanitizedData); //should display my body content
}
if ($list->getNextPageToken() != null) {
$pageToken = $list->getNextPageToken();
$list = $gmail->users_messages->listUsersMessages('me', ['pageToken' => $pageToken, 'maxResults' => 1000]);
} else {
break;
}
}
The second option to retrieve content that I know is by using the snippet located in the Headers part, but it only retrieves the 50 first characters or so, which isn't very useful.
UPDATE: You might want to check my second answer below this one for a more complete code.
Finally, I worked today, so here's the complete code answer for finding the body - thanks to #Tholle:
// Authentication things above
/*
* Decode the body.
* #param : encoded body - or null
* #return : the body if found, else FALSE;
*/
function decodeBody($body) {
$rawData = $body;
$sanitizedData = strtr($rawData,'-_', '+/');
$decodedMessage = base64_decode($sanitizedData);
if(!$decodedMessage){
$decodedMessage = FALSE;
}
return $decodedMessage;
}
$client = getClient();
$gmail = new Google_Service_Gmail($client);
$list = $gmail->users_messages->listUsersMessages('me', ['maxResults' => 1000]);
try{
while ($list->getMessages() != null) {
foreach ($list->getMessages() as $mlist) {
$message_id = $mlist->id;
$optParamsGet2['format'] = 'full';
$single_message = $gmail->users_messages->get('me', $message_id, $optParamsGet2);
$payload = $single_message->getPayload();
// With no attachment, the payload might be directly in the body, encoded.
$body = $payload->getBody();
$FOUND_BODY = decodeBody($body['data']);
// If we didn't find a body, let's look for the parts
if(!$FOUND_BODY) {
$parts = $payload->getParts();
foreach ($parts as $part) {
if($part['body']) {
$FOUND_BODY = decodeBody($part['body']->data);
break;
}
// Last try: if we didn't find the body in the first parts,
// let's loop into the parts of the parts (as #Tholle suggested).
if($part['parts'] && !$FOUND_BODY) {
foreach ($part['parts'] as $p) {
// replace 'text/html' by 'text/plain' if you prefer
if($p['mimeType'] === 'text/html' && $p['body']) {
$FOUND_BODY = decodeBody($p['body']->data);
break;
}
}
}
if($FOUND_BODY) {
break;
}
}
}
// Finally, print the message ID and the body
print_r($message_id . " : " . $FOUND_BODY);
}
if ($list->getNextPageToken() != null) {
$pageToken = $list->getNextPageToken();
$list = $gmail->users_messages->listUsersMessages('me', ['pageToken' => $pageToken, 'maxResults' => 1000]);
} else {
break;
}
}
} catch (Exception $e) {
echo $e->getMessage();
}
As you can see, my problem was, sometimes the body cannot be found in the payload->parts but directly in the payload->body! (plus I add the loop for multiple parts).
Hope this helps somebody else.
Let's do a little experiment. I've sent two messages to myself. One with an attachment, and one without.
Request:
GET https://www.googleapis.com/gmail/v1/users/me/messages?maxResults=2
Response:
{
"messages": [
{
"id": "14fe21fd6b3fb46f",
"threadId": "14fe21fd6b3fb46f"
},
{
"id": "14fe21f9341ed73c",
"threadId": "14fe21f9341ed73c"
}
],
"nextPageToken": "08943597140129624594",
"resultSizeEstimate": 3
}
I only ask for the payload, since that is where all the relevant parts are:
fields = payload
GET https://www.googleapis.com/gmail/v1/users/me/messages/14fe21fd6b3fb46f?fields=payload
GET https://www.googleapis.com/gmail/v1/users/me/messages/14fe21f9341ed73c?fields=payload
Mail without attachment:
{
"payload": {
"parts": [
{
"partId": "0",
"mimeType": "text/plain",
"filename": "",
"headers": [
{
"name": "Content-Type",
"value": "text/plain; charset=UTF-8"
}
],
"body": {
"size": 22,
"data": "aGVjaz8gTm8gYXR0YWNobWVudD8NCg=="
}
},
{
"partId": "1",
"mimeType": "text/html",
"filename": "",
"headers": [
{
"name": "Content-Type",
"value": "text/html; charset=UTF-8"
}
],
"body": {
"size": 43,
"data": "PGRpdiBkaXI9Imx0ciI-aGVjaz8gTm8gYXR0YWNobWVudD88L2Rpdj4NCg=="
}
}
]
}
}
Mail with attachment:
{
"payload": {
"parts": [
{
"mimeType": "multipart/alternative",
"filename": "",
"headers": [
{
"name": "Content-Type",
"value": "multipart/alternative; boundary=001a1142e23c551e8e05200b4be0"
}
],
"body": {
"size": 0
},
"parts": [
{
"partId": "0.0",
"mimeType": "text/plain",
"filename": "",
"headers": [
{
"name": "Content-Type",
"value": "text/plain; charset=UTF-8"
}
],
"body": {
"size": 9,
"data": "V293IG1hbg0K"
}
},
{
"partId": "0.1",
"mimeType": "text/html",
"filename": "",
"headers": [
{
"name": "Content-Type",
"value": "text/html; charset=UTF-8"
}
],
"body": {
"size": 30,
"data": "PGRpdiBkaXI9Imx0ciI-V293IG1hbjwvZGl2Pg0K"
}
}
]
},
{
"partId": "1",
"mimeType": "image/jpeg",
"filename": "feelthebern.jpg",
"headers": [
{
"name": "Content-Type",
"value": "image/jpeg; name=\"feelthebern.jpg\""
},
{
"name": "Content-Disposition",
"value": "attachment; filename=\"feelthebern.jpg\""
},
{
"name": "Content-Transfer-Encoding",
"value": "base64"
},
{
"name": "X-Attachment-Id",
"value": "f_ieq3ev0i0"
}
],
"body": {
"attachmentId": "ANGjdJ_2xG3WOiLh6MbUdYy4vo2VhV2kOso5AyuJW3333rbmk8BIE1GJHIOXkNIVGiphP3fGe7iuIl_MGzXBGNGvNslwlz8hOkvJZg2DaasVZsdVFT_5JGvJOLefgaSL4hqKJgtzOZG9K1XSMrRQAtz2V0NX7puPdXDU4gvalSuMRGwBhr_oDSfx2xljHEbGG6I4VLeLZfrzGGKW7BF-GO_FUxzJR8SizRYqIhgZNA6PfRGyOhf1s7bAPNW3M9KqWRgaK07WTOYl7DzW4hpNBPA4jrl7tgsssExHpfviFL7yL52lxsmbsiLe81Z5UoM",
"size": 100446
}
}
]
}
}
These responses corresponds to the $parts in your code. As you can see, if you are lucky, $parts[0]['body']->data will give you what you want, but most of the time it will not.
There are generally two approaches to this problem. You could implement the following algorithm (you are much better at PHP than me, but this is the general outline of it):
Traverse the payload.parts and check if it contains a part that has the body you were looking for (either text/plain or text/html). If it has, you are done with your searching. If you were parsing a mail like the one above with no attachment, this would be enough.
Do step 1 again, but this time with the parts found inside the parts you just checked, recursively. You will eventually find your part. If you were parsing a mail like the one above with an attachment, this would eventually find you your body.
The algorithm could look something like the following (example in JavaScript):
var response = {
"payload": {
"parts": [
{
"mimeType": "multipart/alternative",
"filename": "",
"headers": [
{
"name": "Content-Type",
"value": "multipart/alternative; boundary=001a1142e23c551e8e05200b4be0"
}
],
"body": {
"size": 0
},
"parts": [
{
"partId": "0.0",
"mimeType": "text/plain",
"filename": "",
"headers": [
{
"name": "Content-Type",
"value": "text/plain; charset=UTF-8"
}
],
"body": {
"size": 9,
"data": "V293IG1hbg0K"
}
},
{
"partId": "0.1",
"mimeType": "text/html",
"filename": "",
"headers": [
{
"name": "Content-Type",
"value": "text/html; charset=UTF-8"
}
],
"body": {
"size": 30,
"data": "PGRpdiBkaXI9Imx0ciI-V293IG1hbjwvZGl2Pg0K"
}
}
]
},
{
"partId": "1",
"mimeType": "image/jpeg",
"filename": "feelthebern.jpg",
"headers": [
{
"name": "Content-Type",
"value": "image/jpeg; name=\"feelthebern.jpg\""
},
{
"name": "Content-Disposition",
"value": "attachment; filename=\"feelthebern.jpg\""
},
{
"name": "Content-Transfer-Encoding",
"value": "base64"
},
{
"name": "X-Attachment-Id",
"value": "f_ieq3ev0i0"
}
],
"body": {
"attachmentId": "ANGjdJ_2xG3WOiLh6MbUdYy4vo2VhV2kOso5AyuJW3333rbmk8BIE1GJHIOXkNIVGiphP3fGe7iuIl_MGzXBGNGvNslwlz8hOkvJZg2DaasVZsdVFT_5JGvJOLefgaSL4hqKJgtzOZG9K1XSMrRQAtz2V0NX7puPdXDU4gvalSuMRGwBhr_oDSfx2xljHEbGG6I4VLeLZfrzGGKW7BF-GO_FUxzJR8SizRYqIhgZNA6PfRGyOhf1s7bAPNW3M9KqWRgaK07WTOYl7DzW4hpNBPA4jrl7tgsssExHpfviFL7yL52lxsmbsiLe81Z5UoM",
"size": 100446
}
}
]
}
};
// In e.g. a plain text message, the payload is the only part.
var parts = [response.payload];
while (parts.length) {
var part = parts.shift();
if (part.parts) {
parts = parts.concat(part.parts);
}
if(part.mimeType === 'text/html') {
var decodedPart = decodeURIComponent(escape(atob(part.body.data.replace(/\-/g, '+').replace(/\_/g, '/'))));
console.log(decodedPart);
}
}
The far easier option is to just get the raw data of the mail, and let a already written library do the work for you:
Request:
format = raw
fields = raw
GET https://www.googleapis.com/gmail/v1/users/me/messages/14fe21fd6b3fb46f?format=raw&fields=raw
Response:
{
"raw": "TUlNRS1WZXJzaW9uOiAxLjANClJlY2VpdmVkOiBieSAxMC4yOC45OS4xOTYgd2l0aCBIVFRQOyBGcmksIDE4IFNlcCAyMDE1IDEzOjIzOjAxIC0wNzAwIChQRFQpDQpEYXRlOiBGcmksIDE4IFNlcCAyMDE1IDIyOjIzOjAxICswMjAwDQpEZWxpdmVyZWQtVG86IGVtdGhvbGluQGdtYWlsLmNvbQ0KTWVzc2FnZS1JRDogPENBRHNaTFJ5eGk2UGt0MHZnUS1iZHd1N2FNLWNHRmZKcEwrRHYyb3ZKOGp4SGN4VWhfQUBtYWlsLmdtYWlsLmNvbT4NClN1YmplY3Q6IFdoYXQgZGENCkZyb206IEVtaWwgVGhvbGluIDxlbXRob2xpbkBnbWFpbC5jb20-DQpUbzogRW1pbCBUaG9saW4gPGVtdGhvbGluQGdtYWlsLmNvbT4NCkNvbnRlbnQtVHlwZTogbXVsdGlwYXJ0L2FsdGVybmF0aXZlOyBib3VuZGFyeT0wMDFhMTE0NjhmMTY1YzUwNDUwNTIwMGI0YzYxDQoNCi0tMDAxYTExNDY4ZjE2NWM1MDQ1MDUyMDBiNGM2MQ0KQ29udGVudC1UeXBlOiB0ZXh0L3BsYWluOyBjaGFyc2V0PVVURi04DQoNCmhlY2s_IE5vIGF0dGFjaG1lbnQ_DQoNCi0tMDAxYTExNDY4ZjE2NWM1MDQ1MDUyMDBiNGM2MQ0KQ29udGVudC1UeXBlOiB0ZXh0L2h0bWw7IGNoYXJzZXQ9VVRGLTgNCg0KPGRpdiBkaXI9Imx0ciI-aGVjaz8gTm8gYXR0YWNobWVudD88L2Rpdj4NCg0KLS0wMDFhMTE0NjhmMTY1YzUwNDUwNTIwMGI0YzYxLS0="
}
The biggest drawback of the second method is that if you get the message raw, you will download all the attachment data right away, which might be far to much data for your use case.
I'm not good at PHP, but this looks promising if you want to go with the second solution! Good luck!
For those who are interested I greatly improved my last answer, making it working with text/html (and fallback to text/plain if necessary) and transforming the images as base64 attachments that will auto-load when printed as full HTML!
Code isn't perfect at all and is way too long to explain in details but it's working for me.
Feel free to take it and adapt it (maybe correct/improve it if necessary).
// Authentication things above
/*
* Decode the body.
* #param : encoded body - or null
* #return : the body if found, else FALSE;
*/
function decodeBody($body) {
$rawData = $body;
$sanitizedData = strtr($rawData,'-_', '+/');
$decodedMessage = base64_decode($sanitizedData);
if(!$decodedMessage){
$decodedMessage = FALSE;
}
return $decodedMessage;
}
$client = getClient();
$gmail = new Google_Service_Gmail($client);
$list = $gmail->users_messages->listUsersMessages('me', ['maxResults' => 1000]);
try{
while ($list->getMessages() != null) {
foreach ($list->getMessages() as $mlist) {
$message_id = $mlist->id;
$optParamsGet2['format'] = 'full';
$single_message = $gmail->users_messages->get('me', $message_id, $optParamsGet2);
$payload = $single_message->getPayload();
$parts = $payload->getParts();
// With no attachment, the payload might be directly in the body, encoded.
$body = $payload->getBody();
$FOUND_BODY = FALSE;
// If we didn't find a body, let's look for the parts
if(!$FOUND_BODY) {
foreach ($parts as $part) {
if($part['parts'] && !$FOUND_BODY) {
foreach ($part['parts'] as $p) {
if($p['parts'] && count($p['parts']) > 0){
foreach ($p['parts'] as $y) {
if(($y['mimeType'] === 'text/html') && $y['body']) {
$FOUND_BODY = decodeBody($y['body']->data);
break;
}
}
} else if(($p['mimeType'] === 'text/html') && $p['body']) {
$FOUND_BODY = decodeBody($p['body']->data);
break;
}
}
}
if($FOUND_BODY) {
break;
}
}
}
// let's save all the images linked to the mail's body:
if($FOUND_BODY && count($parts) > 1){
$images_linked = array();
foreach ($parts as $part) {
if($part['filename']){
array_push($images_linked, $part);
} else{
if($part['parts']) {
foreach ($part['parts'] as $p) {
if($p['parts'] && count($p['parts']) > 0){
foreach ($p['parts'] as $y) {
if(($y['mimeType'] === 'text/html') && $y['body']) {
array_push($images_linked, $y);
}
}
} else if(($p['mimeType'] !== 'text/html') && $p['body']) {
array_push($images_linked, $p);
}
}
}
}
}
// special case for the wdcid...
preg_match_all('/wdcid(.*)"/Uims', $FOUND_BODY, $wdmatches);
if(count($wdmatches)) {
$z = 0;
foreach($wdmatches[0] as $match) {
$z++;
if($z > 9){
$FOUND_BODY = str_replace($match, 'image0' . $z . '#', $FOUND_BODY);
} else {
$FOUND_BODY = str_replace($match, 'image00' . $z . '#', $FOUND_BODY);
}
}
}
preg_match_all('/src="cid:(.*)"/Uims', $FOUND_BODY, $matches);
if(count($matches)) {
$search = array();
$replace = array();
// let's trasnform the CIDs as base64 attachements
foreach($matches[1] as $match) {
foreach($images_linked as $img_linked) {
foreach($img_linked['headers'] as $img_lnk) {
if( $img_lnk['name'] === 'Content-ID' || $img_lnk['name'] === 'Content-Id' || $img_lnk['name'] === 'X-Attachment-Id'){
if ($match === str_replace('>', '', str_replace('<', '', $img_lnk->value))
|| explode("#", $match)[0] === explode(".", $img_linked->filename)[0]
|| explode("#", $match)[0] === $img_linked->filename){
$search = "src=\"cid:$match\"";
$mimetype = $img_linked->mimeType;
$attachment = $gmail->users_messages_attachments->get('me', $mlist->id, $img_linked['body']->attachmentId);
$data64 = strtr($attachment->getData(), array('-' => '+', '_' => '/'));
$replace = "src=\"data:" . $mimetype . ";base64," . $data64 . "\"";
$FOUND_BODY = str_replace($search, $replace, $FOUND_BODY);
}
}
}
}
}
}
}
// If we didn't find the body in the last parts,
// let's loop for the first parts (text-html only)
if(!$FOUND_BODY) {
foreach ($parts as $part) {
if($part['body'] && $part['mimeType'] === 'text/html') {
$FOUND_BODY = decodeBody($part['body']->data);
break;
}
}
}
// With no attachment, the payload might be directly in the body, encoded.
if(!$FOUND_BODY) {
$FOUND_BODY = decodeBody($body['data']);
}
// Last try: if we didn't find the body in the last parts,
// let's loop for the first parts (text-plain only)
if(!$FOUND_BODY) {
foreach ($parts as $part) {
if($part['body']) {
$FOUND_BODY = decodeBody($part['body']->data);
break;
}
}
}
if(!$FOUND_BODY) {
$FOUND_BODY = '(No message)';
}
// Finally, print the message ID and the body
print_r($message_id . ": " . $FOUND_BODY);
}
if ($list->getNextPageToken() != null) {
$pageToken = $list->getNextPageToken();
$list = $gmail->users_messages->listUsersMessages('me', ['pageToken' => $pageToken, 'maxResults' => 1000]);
} else {
break;
}
}
} catch (Exception $e) {
echo $e->getMessage();
}
Cheers.
I wrote this code as an improvement of #F3L1X79's answer as this filters html response correctly.
<?php
ini_set("display_errors", 1);
ini_set("track_errors", 1);
ini_set("html_errors", 1);
error_reporting(E_ALL);
require_once __DIR__ . '/vendor/autoload.php';
session_start();
function decodeBody($body) {
$rawData = $body;
$sanitizedData = strtr($rawData,'-_', '+/');
$decodedMessage = base64_decode($sanitizedData);
if(!$decodedMessage){
$decodedMessage = FALSE;
}
return $decodedMessage;
}
function fetchMails($gmail, $q) {
try{
$list = $gmail->users_messages->listUsersMessages('me', array('q' => $q));
while ($list->getMessages() != null) {
foreach ($list->getMessages() as $mlist) {
$message_id = $mlist->id;
$optParamsGet2['format'] = 'full';
$single_message = $gmail->users_messages->get('me', $message_id, $optParamsGet2);
$payload = $single_message->getPayload();
// With no attachment, the payload might be directly in the body, encoded.
$body = $payload->getBody();
$FOUND_BODY = decodeBody($body['data']);
// If we didn't find a body, let's look for the parts
if(!$FOUND_BODY) {
$parts = $payload->getParts();
foreach ($parts as $part) {
if($part['body'] && $part['mimeType'] == 'text/html') {
$FOUND_BODY = decodeBody($part['body']->data);
break;
}
}
} if(!$FOUND_BODY) {
foreach ($parts as $part) {
// Last try: if we didn't find the body in the first parts,
// let's loop into the parts of the parts (as #Tholle suggested).
if($part['parts'] && !$FOUND_BODY) {
foreach ($part['parts'] as $p) {
// replace 'text/html' by 'text/plain' if you prefer
if($p['mimeType'] === 'text/html' && $p['body']) {
$FOUND_BODY = decodeBody($p['body']->data);
break;
}
}
}
if($FOUND_BODY) {
break;
}
}
}
// Finally, print the message ID and the body
print_r($message_id . " <br> <br> <br> *-*-*- " . $FOUND_BODY);
}
if ($list->getNextPageToken() != null) {
$pageToken = $list->getNextPageToken();
$list = $gmail->users_messages->listUsersMessages('me', array('pageToken' => $pageToken));
} else {
break;
}
}
} catch (Exception $e) {
echo $e->getMessage();
}
}
$client = new Google_Client();
$client->setAuthConfig('client_secrets.json');
$client->addScope(Google_Service_Gmail::GMAIL_READONLY);
if (isset($_SESSION['access_token']) && $_SESSION['access_token']) {
$client->setAccessToken($_SESSION['access_token']);
$gmail = new Google_Service_Gmail($client);
$q = ' after:2016/11/7';
fetchMails($gmail, $q);
} else {
$redirect_uri = 'http://' . $_SERVER['HTTP_HOST'] . '/gmail-api/oauth2callback.php';
header('Location: ' . filter_var($redirect_uri, FILTER_SANITIZE_URL));
}
A simple, ROBUST solution
I wasn't satisfied with the other answers because they're all flawed (elaboration in spoiler), and some are long and mixed with features the asker (and I) didn't look for.
To warn you about potential problems in other answers:
no plain text fallback
or failing to deal with falsy message body - the string '0' (unlikely to happen, but not too unlikely)
or lacking a deep enough search through the payload tree structure
So I thought I'd save others the trouble and share my code (tested on my entire inbox).
// input: the message object (not the payload!)
// output: html or plain text
function msg_body($msg) {
$body = msg_body_recursive($msg->payload);
return array_key_exists('html', $body) ? $body['html'] : $body['plain'];
}
function msg_body_recursive($part) {
if($part->mimeType == 'text/html') {
return ['html' => decodeBody($part->body->data)];
} else if($part->mimeType == 'text/plain') {
return ['plain' => decodeBody($part->body->data)];
} else if($part->parts) {
$return = [];
foreach($part->parts as $sub_part) {
$result = msg_body_recursive($sub_part);
$return = array_merge($return, $result);
if(array_key_exists('html', $return))
break;
}
return $return;
}
return [];
}
function decodeBody($encoded) {
$sanitizedData = strtr($encoded,'-_', '+/');
return base64_decode($sanitizedData);
}
As further improvement the code should be recursive, also you need to load the message in format "full" to extract the body. Below three functions you can put into your own class.
private function decodeBody($body) {
$rawData = $body;
$sanitizedData = strtr($rawData,'-_', '+/');
$decodedMessage = base64_decode($sanitizedData);
if(!$decodedMessage)
return false;
return $decodedMessage;
}
private function decodeParts($parts)
{
foreach ($parts as $part)
{
if ($part->getMimeType() === 'text/html' && $part->getBody())
if ($result = $this->decodeBody($part->getBody()->getData()))
return $result;
}
foreach ($parts as $part)
{
if ($result = $this->decodeParts($part->getParts()))
return $result;
}
}
/**
* #param Google_Service_Gmail_Message $message
* #return bool|null|string
*/
public function getMessageBody($message)
{
$payload = $message->getPayload();
if ($result = $this->decodeBody($payload->getBody()->getData()))
return $result;
return $this->decodeParts($payload->getParts());
}
I just want to complement #F3L1X79 answer, before you break de foreach loop, you have to check the $FOUND_BODY variable is not FALSE, so I added an If condition before every break; in the body search. If you don't do this, the code wil break even if the body was not found.
if($FOUND_BODY !== false) break;

CakePHP- Accessing mail using IMAP

Re-visiting this problem specified in my previous question, I tried and tried, also with different accounts (I tried gmail, as well as outlook), but the problem still persists. The error I get is the following if I try to access my google account
Error: Unable to get imap_thread after 4 retries. 'Can't open mailbox {imap.gmail.com:993/ssl/imap/tls/novalidate-cert}INBOX: invalid remote specification'
if I try accessing email on my outlook account, the error is the same :
Error: Unable to get imap_thread after 4 retries. 'Can't open mailbox {outlook.office365.com:993/ssl/imap/tls/novalidate-cert}INBOX: invalid remote specification'
My setup is as follows :
public $emailTicket = array(
'datasource' => 'ImapSource',
'server' => 'outlook.office365.com',
'connect' => 'imap/tls/novalidate-cert',
'username' => 'my email here',
'password' => 'my password here',
'port' => '993', //incoming port
'ssl' => true,
'encoding' => 'UTF-8',
'error_handler' => 'php',
'auto_mark_as' => array(
'Seen',
// 'Answered',
// 'Flagged',
// 'Deleted',
// 'Draft',
),
);
I am working on a local machine, does anyone know if this might be the problem or not? Has anyone ever tried this and worked for him/her? I am open to all input!
I can't seem to find what's wrong here, I've been at this for about 2days now, so if anyone can help, I appreciate it!
Also here's the link for the plugin i'm using, by Nicolas Ramy..
You can use the following implemented code to fulfill your requirements:
public function generate_email_response_pdf()
{
$this->layout = false;
$this->autoRender = false;
$username = EMP_SMTP_MAIL_FROM;
$password = EMP_SMTP_MAIL_PASSWORD;
$imap = imap_open('{imap.gmail.com:993/imap/ssl/novalidate-cert}INBOX', $username, $password);
$emails = imap_search($imap, 'ALL');
if(!empty($emails))
{
//put the newest emails on top
rsort($emails);
foreach($emails as $email_number)
{
$flag = 0;
$mail_data = array();
$file_name = array();
$output = array();
$savefilename = null;
$filename = null;
$overview = imap_fetch_overview($imap, $email_number, 0);
//initialize the subject index with -000, considering not receving this will not be received in
//subject line of email
$output['subject'] = '-000x';
if(isset($overview[0] -> subject))
{
$output['subject'] = $overview[0] -> subject;
}
$structure = imap_fetchstructure($imap, $email_number);
if(property_exists($structure, 'parts'))
{
$flag = 1;
$flattened_parts = $this->flatten_parts($structure->parts);
foreach($flattened_parts as $part_number => $part)
{
switch($part->type)
{
case 0:
//the HTML or plain text part of the email
if((isset($part->subtype)=='HTML')&&(isset($part->disposition)=='ATTACHMENT'))
{
$part_number = 1.2;
}
else if(isset($part->subtype)=='HTML')
{
$part_number = $part_number;
}
else
{
$part_number = $part_number;
}
$message = $this->get_part($imap, $email_number, $part_number, $part->encoding);
//now do something with the message, e.g. render it
break;
case 1:
// multi-part headers, can ignore
break;
case 2:
// attached message headers, can ignore
break;
case 3: // application
case 4: // audio
case 5: // image
case 6: // video
case 7: // other
break;
}
if(isset($part->disposition))
{
$filename = $this->get_filename_from_part($part);
if($filename)
{
// it's an attachment
$attachment = $this->get_part($imap, $email_number, $part_number, $part->encoding);
$file_info = pathinfo($filename);
$savefilename = RESPONSE_ATTACHMENT_PREFIX.$file_info['filename'].'_'.$this->_getRandId(4).'.'.$file_info['extension'];
$file_name[] = $savefilename;
$attachment_file_name = $this->save_attachment($attachment, $savefilename, $directory_path);
//imap_fetchbody($imap, $email_number, 2); //This marks message as read
}
else
{
// don't know what it is
}
}
}
}
else
{
$encoding = $structure->encoding;
$message = imap_fetchbody($imap, $email_number, 1.2);
//echo $message; die;
if($message == "")
{
$message = imap_body($imap, $email_number);
if($encoding == 3)
{
$message = base64_decode($message);
}
else if($encoding == 4)
{
$message = quoted_printable_decode($message);
}
}
}
$header = imap_headerinfo($imap, $email_number);
$from_email = $header->from[0]->mailbox."#".$header->from[0]->host;
$to_email = $header->to[0]->mailbox."#".$header->to[0]->host;
$reply_to_email = $header->reply_to[0]->mailbox."#".$header->reply_to[0]->host;
$cc_email = array();
if(isset($header->cc))
{
foreach($header->cc as $ccmail)
{
$cc_email[] = $ccmail->mailbox.'#'.$ccmail->host;
}
$cc_email = implode(", ", $cc_email);
}
$output['to'] = $to_email;
$output['from'] = $from_email;
$output['reply_to'] = $reply_to_email;
$output['cc'] = $cc_email;
$formatted_date = date('D, d M Y h:i A', strtotime($overview[0] -> date));
$output['date'] = $formatted_date;
$output['message'] = $message;
$output['flag'] = $flag;
$mail_data['Attachment'] = $file_name;
$mail_data['Data'] = $output;
$this->set('response_data', $mail_data);
$mail_content = null;
if(!empty($mail_data))
{
$this->viewPath = 'Elements/default';
$mail_content = $this->render('pdf_content');
}
$header = null;
$footer = null;
$html = preg_replace(array('/[^\r\n\t\x20-\x7E\xA0-\xFF]*/'), '', $mail_content);
$pdfFile = $this->_generateWkPdf($html, $directory_path, $new_file_name, $header, $footer);
$image_type = EXT_JPG;
$response_files_array = $this->_generateImagesFromPdf($directory_path.$pdfFile, $directory_path, $new_file_name, $image_type);
}
}
imap_close($imap);
}

PHP Imap reading replies in inbox

I have written/copied a script that reads emails from an inbox and updates a ticket and then moves the email to a proccessed folder. This all works perfectly on new emails to the inbox but when someone replys to an email and it ends up in the inbox my scrtipt reads nothing on the email.
Is there something different to the way an email is structured when its a reply? I need a way of reading whats in the email so I can update a ticket with the contents. Knowing which email it is to update is all taken care of its just purely reading the content Im struggling with.
Here is the code
class Email
{
// imap server connection
public $conn;
// inbox storage and inbox message count
public $inbox;
private $msg_cnt;
// email login credentials
private $server = '????????????';
private $user = '????????';
private $pass = '?????????????';
private $port = ??;
// connect to the server and get the inbox emails
function __construct()
{
$this->connect();
$this->inbox();
}
function getdecodevalue($message,$coding)
{
switch($coding) {
case 0:
case 1:
$message = imap_8bit($message);
break;
case 2:
$message = imap_binary($message);
break;
case 3:
case 5:
$message=imap_base64($message);
break;
case 4:
$message = imap_qprint($message);
break;
}
return $message;
}
// close the server connection
function close()
{
$this->inbox = array();
$this->msg_cnt = 0;
imap_close($this->conn);
}
// open the server connection
// the imap_open function parameters will need to be changed for the particular server
// these are laid out to connect to a Dreamhost IMAP server
function connect()
{
$this->conn = imap_open("{".$this->server.":".$this->port."/imap/novalidate-cert}INBOX", $this->user, $this->pass);
}
// move the message to a new folder
function move($msg_index, $folder='Read')
{
// move on server
imap_mail_move($this->conn, $msg_index, $folder);
// re-read the inbox
//$this->inbox();
}
// get a specific message (1 = first email, 2 = second email, etc.)
function get($msg_index=NULL)
{
if(count($this->inbox) <= 0)
{
return array();
}
elseif( ! is_null($msg_index) && isset($this->inbox[$msg_index]))
{
return $this->inbox[$msg_index];
}
return $this->inbox[0];
}
// read the inbox
function inbox()
{
$this->msg_cnt = imap_num_msg($this->conn);
$in = array();
for($i = 1; $i <= $this->msg_cnt; $i++)
{
$in[] = array(
'index' => $i,
'header' => imap_headerinfo($this->conn, $i),
'body' => $this->cleanBody(imap_fetchbody($this->conn, $i,1)),
'structure' => imap_fetchstructure($this->conn, $i)
);
}
$this->inbox = $in;
}
function cleanBody($body)
{
$delimiter = '#';
$startTag = '----------START REPLY----------';
$endTag = '----------END REPLY----------';
$regex = $delimiter . preg_quote($startTag, $delimiter)
. '(.*?)'
. preg_quote($endTag, $delimiter)
. $delimiter
. 's';
preg_match($regex,$body,$matches);
$ret = trim($matches[1]);
return $ret;
}
}
$emails = new Email();
$email = array();
$emailCount = 1;
foreach($emails->inbox as $ems => $em)
{
$email[$emailCount]['subject'] = $sub = $em['header']->subject;
//echo $sub;
$subParts = explode('-',$sub);
$ticketUniqueCode = trim($subParts[1]);
$sql = "SELECT * FROM ticket_main WHERE uniquecode = '".mysql_escape_string($ticketUniqueCode)."' LIMIT 1";
$query = mysql_query($sql);
if(mysql_num_rows($query))
{
$res = mysql_fetch_object($query);
$ticketBody = $em['body'];
$customerID = $res->customerID;
$sql2 = "INSERT INTO ticket_updates SET ticketID = '".$res->ticketID."' , submitted = NOW() , submittedBy = '".$res->customerID."' , message = '".mysql_escape_string($ticketBody)."' , inhouse = 0";
$query = mysql_query($sql2);
// attachment section
$message_number = $em['index'];
$attachments = array();
if(isset($em['structure']->parts) && count($em['structure']->parts))
{
//echo 'hi';
for($i = 0; $i < count($em['structure']->parts); $i++)
{
$attachments[$i] = array(
'is_attachment' => false,
'filename' => '',
'name' => '',
'attachment' => ''
);
if($em['structure']->parts[$i]->ifdparameters) {
foreach($em['structure']->parts[$i]->dparameters as $object)
{
if(strtolower($object->attribute) == 'filename')
{
$attachments[$i]['is_attachment'] = true;
$attachments[$i]['filename'] = $object->value;
}
}
}
if($em['structure']->parts[$i]->ifparameters) {
foreach($em['structure']->parts[$i]->parameters as $object)
{
if(strtolower($object->attribute) == 'name')
{
$attachments[$i]['is_attachment'] = true;
$attachments[$i]['name'] = $object->value;
}
}
}
if($attachments[$i]['is_attachment'])
{
$attachments[$i]['attachment'] = imap_fetchbody($emails->conn, $message_number, $i+1);
if($em['structure']->parts[$i]->encoding == 3)
{ // 3 = BASE64
$attachments[$i]['attachment'] = base64_decode($attachments[$i]['attachment']);
}
elseif($em['structure']->parts[$i]->encoding == 4)
{ // 4 = QUOTED-PRINTABLE
$attachments[$i]['attachment'] = quoted_printable_decode($attachments[$i]['attachment']);
}
}
if(isset($em['structure']->parts[$i]->disposition) && $em['structure']->parts[$i]->disposition == "attachment")
{
$filename = $attachments[$i]['name'];
$mege="";
$data="";
$mege = imap_fetchbody($emails->conn, $message_number, $i+1);
$filename= $filename;
$fp=fopen('???????????????'.$filename,"w");
$data=$emails->getdecodevalue($mege,$em['structure']->parts[$i]->type);
fputs($fp,$data);
fclose($fp);
$email[$emailCount]['attachment'] = $attachments;
}
}
}
}
$emailCount++;
}
$emailNumbers = imap_search($emails->conn,'ALL');
if(!empty($emailNumbers))
{
foreach($emailNumbers as $a)
{
$emails->move($a);
}
imap_expunge($emails->conn);
}
$emails->close();
Hope that makes some sense and someone can actually help.
Many many thanks in advance
Jon
Well the most obvious thing is your code assumes a message is always in part 1, your line:
'body' => $this->cleanBody(imap_fetchbody($this->conn, $i,1)),
Means it's only looking at body 1. If it's a pure text email body 1 will be right, but if it's multipart/alternative (text & html) body 1 would be the base MIME message which tells you that there are sub bodies (body 1.1, 1.2) that actually contain the content.
Further if the reply includes embedded images, or includes the message it's replying to as an attachment then you could have even more bodies/locations.
So how do I find the body? (you ask)... well you can use imap_fetchstructure to learn about all the body parts, then search through it to find a piece with type=0 (text) and then download that body part. (The first found text should be the right one, but note there could be more than one text body type in an email).

Categories