PHP Parse Json from HTTP response & Foreach Iterate Array - php

There are several focused questions on this topic but after trying them all I felt the need to seek out help. I am using Sendgrid, their webhook sends a json response and for some reason I have been unable to figure out how to integrate with our CRM.
Update: 12/3/2015 # 1:01pm - I got some code to work (probably not the most efficient but it works) see below.
Example JSON from Sendgrid being sent:
[
{
"sg_message_id":"sendgrid_internal_message_id",
"email": "john.doe#sendgrid.com",
"timestamp": 1337197600,
"smtp-id": "<4FB4041F.6080505#sendgrid.com>",
"event": "spamreport"
},
{
"sg_message_id":"sendgrid_internal_message_id",
"email": "john.doe#sendgrid.com",
"timestamp": 1337966815,
"category": "newuser",
"event": "bounce",
"url": "https://sendgrid.com"
},
{
"sg_message_id":"sendgrid_internal_message_id",
"email": "john.doe#sendgrid.com",
"timestamp": 1337969592,
"smtp-id": "<20120525181309.C1A9B40405B3#Example-Mac.local>",
"event": "unsubscribe",
"asm_group_id": 42
}
]
The issue I am running into is that each array in the JSON pertains to a particular event. For example "Spam", or "Bounced" or "Unsubscribe". I've set it up so that only those three events are sent. However there is a possibility that someone will bounce first, then get it, then hit spam, then hit unsubscribe.
The simplest solution for this (disregarding a heirarchy system) is to have each of these events pass along to a specific field in our CRM. For example if the event = spam, then it would fill $spam with "spam". However if it is not present it should do nothing.
One last note, I have to pass a header across so that Sendgrid will stop spamming the script. Also I am not a coder, or programmer, I have just picked up a few things over the last few months.
My Script that Isn't Working:
<?php
$data = file_get_contents("php://input");
$events = json_decode($data, true);
require_once('Infusionsoft/infusionsoft.php');
$contact = new Infusionsoft_Contact();
$custom = array(
'_SGBounce',
'_SGSpam',
'_SGUnsub'
);
$contact->addCustomFields($custom);
if (is_array($events) || $events instanceof Traversable) {
foreach ($events as $event) {
$email = $event['email'];
if($event['event'] === 'bounce') {
$bounce = 'bounce';
} elseif ($event['event'] === 'spamreport') {
$spam = 'spam';
} elseif ($event['event'] === 'unsubscribe') {
$unsub = 'unsubscribe';
} else {
die echo header("HTTP/1.1 200 OK");
}
process_event($event);
}}
if (empty($email)) {
die echo header("HTTP/1.1 200 OK");
} else {
$contact->Email = $email;
$contact->_SGBounce = $bounce;
$contact->_SGSpam = $spam;
$contact->_SGUnsub = $unsub;
$contact->save();
}
header("HTTP/1.1 200 OK");
?>
I also have a script I am using that writes to a file, just to test and see if I can get the correct events to write. However I have been unsuccessful to get it to iterate past the first array. I've tried nesting the foreach() in another foreach() with a key => value solution posted in another answer here. However that was also a dead end.
Any tips, help, and guidance... would be greatly appreciated.
Update: Working Code Below (in case this helps someone)
<?php
$data = file_get_contents("php://input");
$events = json_decode($data, true);
require_once('Infusionsoft/infusionsoft.php');
$contact = new Infusionsoft_Contact();
$custom = array(
'_SendGrid',
);
$contact->addCustomFields($custom);
$emails = array();
$em = '';
if (is_array($events) || $events instanceof Traversable) {
foreach ($events as $event) {
$email = $event['email'];
$em = $email;
if($event['event'] === 'unsubscribe') {
$event = 'unsubscribe';
$unsubscribe = 'unsubscribe';
} elseif($event['event'] === 'spamreport') {
$event = 'spam';
$spam = 'spam';
} elseif($event['event'] === 'bounce') {
$event = 'bounce';
$bounce = 'bounce';
} else {
continue;
}
$default = array(
'bounce' => false,
'spam' => false,
'unsubscribe' => false
);
if(!is_null($event)) {
if(array_key_exists($email, $emails)) {
$entry = $emails[$email];
} else {
$entry = $default;
}
switch($event) {
case "bounce":
$entry['bounce'] = true;
break;
case "spam":
$entry['spam'] = true;
break;
case "unsubscribe":
$entry['unsubscribe'] = true;
break;
}
}
}}
if($unsubscribe === 'unsubscribe'){
$param = 'unsubscribe';
} elseif($spam === 'spam'){
$param = 'spam';
} elseif($bounce === 'bounce'){
$param = 'bounce';
} else {
echo header("HTTP/1.1 200 OK");
}
$contact->Email = $em;
$contact->_SendGrid = $param;
$contact->save();
header("HTTP/1.1 200 OK");
?>

What you could do, is process it like this;
$emails = array();
foreach ($events as $event) {
$email = $event['email'];
if($event['event'] === 'bounce') {
$event = 'bounce';
} elseif ($event['event'] === 'spamreport') {
$event = 'spam';
} elseif ($event['event'] === 'unsubscribe') {
$event = 'unsubscribe';
} else {
continue;
}
// Set defaults
$default = array(
'bounce' => false,
'spam' => false,
'unsubscribe' => false
);
if(!is_null($event)) {
if(array_key_exists($email, $emails)) {
$entry = $emails[$email];
} else {
$entry = $default;
}
switch($event) {
case "bounce":
$entry['bounce'] = true;
break;
case "spam":
$entry['spam'] = true;
break;
case "unsubscribe":
$entry['unsubscribe'] = true;
break;
}
}
}
Then you'd end up with a list of emails that are their own array with boolean values for keys bounce, spam and unsubscribe. You could do a loop over that array to save the data.

Related

How can i modify my array code to complete even if a value does not exist in the array?

I am modifying some code i had written for my by a developer who is no longer working with us, essentially the code pulls data from a web service and parses to an array, it then looks for keys within the array and publishes associated values to an HTML table, it all works fine when each of the entries exists in the data however the point of modifying the code if to return a generic value ("not submitted") if the value is not found in the array.
background info is helpful so the use case is:
Array is made up of the sales team, they each login to a website and submit their sales totals for the day, we interrogate the web service to return the data via a SOAP call which pulls the data into the code. The data is parsed and the values are represented in a table on a web page.
If a single sales team member does not submit data then the code fails to return any data.
I've been googling and hitting my pluralsight account to learn as much as i can about how this is done but i'm falling short.
class SalesResponseFactory
{
private $date;
//private $keyArray = ['KE', 'JY', 'OR', 'KR', 'SY', 'DB'];
public $responseData = [];
function __construct($date)
{
libxml_use_internal_errors(true);
$this->date = $date;
}
private function getResponseData($date)
{
....
}
private function parserToArray($data)
{
$data = substr($data, strpos($data, "<DataResult>"));
$data = substr($data, 0, strpos($data, "</DataResult>") + strlen("</DataResult>"));
$xml = simplexml_load_string($data);
if ($xml === false) {
throw new Exception("Invalid user credentials.");
}
$json = json_encode($xml);
$array = json_decode($json,TRUE);
if (empty($array['Results'])) {
throw new Exception("Holiday - No Sales Data Recorded");
}
return $array['Results']['Data'];
}
private function alignmentArray($sales)
{
if ($sales['Name'] == 'Market Code' || $sales['Name'] == 'Sales Volume' || $sales['Name'] == 'Turnover Volume' || $sales['Name'] == 'Date') {
if($sales['Name'] == 'Market Code') {
$this->responseData[$sales['Instrument']]['Market Code'] = $sales['Value'];
}
if($sales['Name'] == 'Sales Volume') {
$this->responseData[$sales['Instrument']]['Sales Volume'] = $sales['Value'];
}
if($sales['Name'] == 'Turnover Volume') {
$this->responseData[$sales['Instrument']]['Turnover Volume'] = $sales['Value'];
}
else {
$this->responseData[$sales['Instrument']]['Date'] = $sales['Value'];
}
}
}
public function parser()
{
try {
$salesArray = $this->getResponseData($this->date);
} catch (Exception $e) {
$response['status'] = 'error';
$response['message'] = $e->getMessage();
$response['results'] = '';
return json_encode($response);
}
foreach ($salesArray as $sales) {
$this->alignmentArray($sales);
}
$response['status'] = 'ok';
$response['message'] = 'success';
$response['results'] = [];
foreach ($this->responseData as $row) {
array_push($response['results'], $row);
}
return json_encode($response);
}
}
?>
i'd expect that null values should still be output but whats actually happening is that no data is being returned to the HTML table.

Use PHP function to override default JSON target with Array

We built an API to directly access other social networks APIs using our keys.
I'm trying to build a fuction to access that API.
The default function has been written and is working.
Question
How can I specify a new array to target the json data?
This will override the default setting.
function SocialAPI($handle, $service, $path="") {
$handle = strtolower($handle);
$service = strtolower($service);
$api = file_get_contents("https://api.service.domain.com/v1/Social?handle=$handle&service=$service");
if($api !== false) {
$data = json_decode($api, true);
if($data !== null) {
if($service === "twitter") {
return $data['0']['followers_count'];
}
if($service === "instagram") {
if(!empty($path)) {
while($id = array_shift($path)) {
echo $data[$id];
}
return $data;
} else {
return $data['user']['followed_by']['count'];
}
}
} else {
return false;
}
} else {
return "API call failed.";
}
}
//Test API Function - ** TO BE DELETED **
echo SocialAPI("JohnDoe", "Instagram", "['user']['full_name']");
exit();
function array_deref($data, $keys) {
return empty($keys) ? $data
: array_deref($data[$keys[0]], array_slice($data, 1))
}
function SocialAPI($handle, $service, $path="") {
$handle = strtolower($handle);
$service = strtolower($service);
$api = file_get_contents("https://api.service.domain.com/v1/Social?handle=$handle&service=$service");
if ($api === false) {
return "API call failed.";
}
$data = json_decode($api, true);
if($data !== null) {
return false;
}
if ($service === "twitter") {
if (empty($path)) $path = ['0','followers_count'];
return array_deref($data, $path);
} elseif ($service === "instagram") {
if (empty($path)) $path = ['user','followed_by'];
return array_deref($data, $path);
}
}
//Test API Function - ** TO BE DELETED **
echo SocialAPI("JohnDoe", "Instagram", ['user', 'full_name']);
echo SocialAPI("JohnDoe", "Instagram");
exit();
I added a utility function, array_deref, to walk the arrays recursively (calls itself to handle each level down).

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;

How to use eager loading here, i mean just by using one variable instead of two. So i don't need to run two separate queries

1) How can use eager loading here, i mean just by using $SponceringUser instead of $ProfileUser. so i don't need to run two separate queries.
if i exchange my $profileUser variable with $SponceringUser where in the code i should save it to get the right output
here is the Post_edit method for more details please refer gist here : https://gist.github.com/gupta2205/b33dcf762876e5df34d9
public function post_edit($id = null)
{
if ($id)
{
$Petition = Petition::find($id);
if (!$Petition)
{
//oh noes! invalid petition specified!
Alert::error("That petition no longer exists");
return Redirect::action('AdminPetitionsController#index');
}
}
else
{
$Petition = new Petition;
}
$PetitionCreationForm = new AdminPetitionCreationForm;
$errors = array();
if ($PetitionCreationForm->passes())
{
$Petition->call_to_action = Input::get('call_to_action');
if (empty($Petition->id))
{
$Petition->slug = Str::slug($Petition->call_to_action);
}
$Petition->recipient = Input::get('recipient');
if (Input::get('feature_type') == '1')
{
$Petition->featured_sort_order = Input::get('featured_sort_order') + 1;
$Petition->flag_featured = 1;
}
else if(Input::get('feature_type') == '0')
{
$Petition->featured_sort_order=null;
$Petition->flag_featured = 0;
}
//$selected_position = Input::get('dropdown_menu_list');
$Petition->description_md = Input::get('description_md');
$Petition->description = Petition::parseMD($Petition->description_md);
$Petition->letter_md = Input::get('letter_md');
$Petition->letter = Petition::parseMD($Petition->letter_md);
$Petition->target_signatures = Input::get('target_signatures', 50000);
$Petition->flag_published = Input::get('flag_published');
$Petition->media_type = Input::get('media_type', null);
if (Input::get('media_type') == 'img' && Input::hasFile('petition_image'))
{
$Petition->media_url = Petition::uploadFile(Input::file('petition_image'));
}
else if (Input::get('media_type') == 'youtube' && Input::get('media_url_youtube'))
{
$Petition->media_url = Input::get('media_url_youtube');
}
// how to fix this part .... gurrrrrrrrrrrr=======================
$ProfileUser= $Petition->User;
if (Input::get('profile_type') == 'image' && Input::hasFile('profile_image'))
{
$ProfileUser->profile_img_url = Petition::uploadFile(Input::file('profile_image'));
}
else if (Input::get('profile_type') == 'url' && Input::get('profile_url'))
{
$ProfileUser->profile_img_url = Input::get('profile_url');
}
//$Petition->sponsor_user_id = $SponsoringUser->id;
$ProfileUser->save();
//====================================================
try {
try {
$SponsoringUser = User::where('email', Input::get('user.email'))->firstOrFail();
}
catch (Exception $e)
{
$PetitionSponsorForm = new AdminPetitionSponsorForm(Input::get('user'));
if ($PetitionSponsorForm->passes())
{
$SponsoringUser = new User;
$SponsoringUser->email = Input::get('user.email');
$SponsoringUser->first_name = Input::get('user.first_name');
$SponsoringUser->last_name = Input::get('user.last_name');
$SponsoringUser->populateLocation(Input::get('user.zip'));
$SponsoringUser->save();
}
else
{
throw new Exception();
}
}
$Petition->save();
1) How can use eager loading here, i mean just by using $SponceringUser instead of $ProfileUser. so i don't need to run two separate queries.
i got it :)
if (Input::get('profile_type') == 'image' && Input::hasFile('profile_image'))
{
$SponsoringUser->profile_img_url = Petition::uploadFile(Input::file('profile_image'));
}
else if (Input::get('profile_type') == 'url' && Input::get('profile_url'))
{
$$SponsoringUser->profile_img_url = Input::get('profile_url');
}
$SponsoringUser->save();

avoid duplicates in flash based messaging

I am using a php session-based flash messenger available here. The issue is sometimes I get multiple messages of the same type when I generate errors, display messages, so on and so fourth. This is mostly due to some AJAX issues. Assuming that I wanted to only apply a fix in the display code here:
public function display($type = 'all', $print = true)
{
$messages = '';
$data = '';
if (!isset($_SESSION['flash_messages'])) {
return false;
}
// print a certain type of message?
if (in_array($type, $this->msgTypes)) {
foreach ($_SESSION['flash_messages'][$type] as $msg) {
$messages .= $this->msgBefore . $msg . $this->msgAfter;
}
$data .= sprintf($this->msgWrapper, $this->msgClass, $this->msgClassPrepend.'-'.$type, str_replace('messages', 'autoclose',$this->msgClassPrepend.'-'.$type), $messages);
// clear the viewed messages
$this->clear($type);
// print ALL queued messages
} elseif ($type == 'all') {
$counter = 1;
foreach ($_SESSION['flash_messages'] as $type => $msgArray) {
$count = $counter++;
$messages = '';
foreach ($msgArray as $msg) {
$messages .= $this->msgBefore . $msg . $this->msgAfter;
}
$data .= sprintf($this->msgWrapper, $this->msgClass, $this->msgClassPrepend.'-'.$type, str_replace('messages', 'autoclose', $this->msgClassPrepend.'-'.$type), $messages);
}
// clear ALL of the messages
$this->clear();
// invalid message type?
} else {
return false;
}
// print everything to the screen or return the data
if ($print) {
echo $data;
} else {
return $data;
}
}
How would I make it so that duplicate messages are detected on a 1 for 1 basis. So if the message is "Hello" and "Hello" and "Hello." I can remove one of the first two, and keep the later as it is a different message so to speak. All the workarounds I can think of would be overly complex, and I was wondering if anyone could think of a simple solution.
Additional info: display is encased in class Messages and a new message is created with
$msg = new Messages();
$msg->add('e', 'Some error here.');
You could simply run the message array through array_unique() before the $messages string is built. For example, these two additions to the display method should do the trick...
if (in_array($type, $this->msgTypes)) {
$filtered = array_unique($_SESSION['flash_messages'][$type]);
foreach ($filtered as $msg) {
and...
foreach ($_SESSION['flash_messages'] as $type => $msgArray) {
$count = $counter++;
$messages = '';
$filtered = array_unique($msgArray);
foreach ($filtered as $msg) {
Alternatively, you could override the add method with a unique check. For example
public function add($type, $message, $redirect_to = null, $ignoreDuplicates = true) {
// snip...
// wrap the array push in this check
if (!($ignoreDuplicates && in_array($message, $_SESSION['flash_messages'][$type]))) {
$_SESSION['flash_messages'][$type][] = $message; // this is the existing code
}
// snip...
}

Categories