JSON Array convert to CSV - php

I'm trying to convert a JSON response to CSV file but I have a problem about arrays
Here is the conversion code to CSV
$jsonString = file_get_contents("feed.json");
$jsonDecoded = json_decode($jsonString, true);
$json = $jsonDecoded['results'];
jsonToCsv($json, "feed.csv");
function jsonToCsv ($json, $csvFilePath = false, $boolOutputFile = false) {
// See if the string contains something
if (empty($json)) {
die("The JSON string is empty!");
}
// If passed a string, turn it into an array
if (is_array($json) === false) {
$json = json_decode($json, true);
}
// If a path is included, open that file for handling. Otherwise, use a temp file (for echoing CSV string)
if ($csvFilePath !== false) {
$f = fopen($csvFilePath,'w+');
if ($f === false) {
die("Couldn't create the file to store the CSV, or the path is invalid. Make sure you're including the full path, INCLUDING the name of the output file (e.g. '../save/path/csvOutput.csv')");
}
}
else {
$boolEchoCsv = true;
if ($boolOutputFile === true) {
$boolEchoCsv = false;
}
$strTempFile = 'feed' . date("U") . ".csv";
$f = fopen($strTempFile,"w+");
}
$firstLineKeys = false;
foreach ($json as $line) {
if (empty($firstLineKeys)) {
$firstLineKeys = array_keys($line);
fputcsv($f, $firstLineKeys);
$firstLineKeys = array_flip($firstLineKeys);
}
// Using array_merge is important to maintain the order of keys acording to the first element
fputcsv($f, array_merge($firstLineKeys, $line));
}
fclose($f);
// Take the file and put it to a string/file for output (if no save path was included in function arguments)
if ($boolOutputFile === true) {
if ($csvFilePath !== false) {
$file = $csvFilePath;
}
else {
$file = $strTempFile;
}
// Output the file to the browser (for open/save)
if (file_exists($file)) {
header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename='.basename($file));
header('Content-Length: ' . filesize($file));
readfile($file);
}
}
elseif ($boolEchoCsv === true) {
if (($handle = fopen($strTempFile, "r")) !== FALSE) {
while (($data = fgetcsv($handle)) !== FALSE) {
echo implode(",",$data);
echo "<br />";
}
fclose($handle);
}
}
// Delete the temp file
unlink($strTempFile);
}
?>
And here is a example of feed.json content
{
"isError":false,
"messages":[
],
"results":[
{
"part_number_key":"DASDSA",
"buy_button_rank":1,
"category_id":11,
"id":123,
"brand":"brand",
"vendor_category_id":null,
"name":"Name",
"part_number":"part number",
"sale_price":12.31,
"currency":"RON",
"description":"description",
"url":"https://www.url.com",
"warranty":12,
"general_stock":10,
"weight":"0",
"status":1,
"recommended_price":12.19,
"images":[
{
"url":"https://url.com/images.jpg1",
"display_type":1
},
{
"url":"https://url.com/images.jpg2",
"display_type":0
},
{
"url":"https://url.com/images.jpg3",
"display_type":0
}
],
"characteristics":[
{
"id":8937,
"value":"value"
},
{
"id":8930,
"value":"value"
},
{
"id":8927,
"value":"value"
},
{
"id":8928,
"value":"value"
},
{
"id":5537,
"value":"value"
},
{
"id":8932,
"value":"value"
},
{
"id":8934,
"value":"value"
},
{
"id":8929,
"value":"value"
},
{
"id":7235,
"value":"value"
},
{
"id":6556,
"value":"value"
},
{
"id":5401,
"value":"value"
}
],
"attachments":[
],
"vat_id":1,
"family":{
"id":1104,
"name":"NAME",
"family_type_id":244
},
"start_date":[
],
"estimated_stock":10,
"reversible_vat_charging":false,
"min_sale_price":11,
"max_sale_price":200,
"offer_details":{
"id":2484194,
"warranty_type":null,
"supply_lead_time":14
},
"offer_properties":[
],
"product_measurements":[
],
"availability":[
{
"warehouse_id":1,
"id":3
}
],
"stock":[
{
"warehouse_id":1,
"value":10
}
],
"handling_time":[
{
"warehouse_id":1,
"value":0
}
],
"barcode":[
],
"ean":[
],
"commission":{
"value":"20.0000",
"type":"percentage",
"id":"123",
"priority":"4",
"created":"2010-01-16 11:25:02"
},
"validation_status":[
{
"value":9,
"description":"Approved documentation",
"errors":null
}
],
"offer_validation_status":{
"value":1,
"description":"Saleable",
"errors":null
},
"ownership":1,
"best_offer_sale_price":11,
"best_offer_recommended_price":11,
"number_of_offers":1
},
{
"part_number_key":"D3129DKQW",
"buy_button_rank":1,
"category_id":456,
"id":123,
"brand":"Bluedio",
"vendor_category_id":null,
"name":"name",
"part_number":"part number",
"sale_price":123,
"currency":"EUR",
"description":"description",
"url":"https://url.com",
"warranty":24,
"general_stock":1,
"weight":"0",
"status":1,
"recommended_price":12,
"images":[
{
"url":"https://url.com/images.jpg1",
"display_type":1
},
{
"url":"https://url.com/images.jpg2",
"display_type":0
},
{
"url":"https://url.com/images.jpg3",
"display_type":0
}
],
"characteristics":[
{
"id":7774,
"value":"val"
},
{
"id":7947,
"value":"val"
},
{
"id":8001,
"value":"val"
},
{
"id":8937,
"value":"val"
},
{
"id":8930,
"value":"val"
},
{
"id":8927,
"value":"val"
},
{
"id":8928,
"value":"val"
},
{
"id":5537,
"value":"val"
},
{
"id":8932,
"value":"val"
},
{
"id":8934,
"value":"val"
},
{
"id":8929,
"value":"val"
},
{
"id":7235,
"value":"val"
},
{
"id":6556,
"value":"val"
},
{
"id":5401,
"value":"val"
}
],
"attachments":[
],
"vat_id":1,
"family":{
"id":123456,
"name":"Name",
"family_type_id":244
},
"start_date":[
],
"estimated_stock":144,
"reversible_vat_charging":false,
"min_sale_price":11,
"max_sale_price":200,
"offer_details":{
"id":242414,
"warranty_type":null,
"supply_lead_time":14
},
"offer_properties":[
],
"product_measurements":[
],
"availability":[
{
"warehouse_id":1,
"id":3
}
],
"stock":[
{
"warehouse_id":1,
"value":9
}
],
"handling_time":[
{
"warehouse_id":1,
"value":0
}
],
"barcode":[
],
"ean":[
],
"commission":{
"value":"20.0000",
"type":"percentage",
"id":"12492",
"priority":"4",
"created":"2010-01-16 11:25:02"
},
"validation_status":[
{
"value":9,
"description":"Approved documentation",
"errors":null
}
],
"offer_validation_status":{
"value":1,
"description":"Saleable",
"errors":null
},
"ownership":1,
"best_offer_sale_price":77.31,
"best_offer_recommended_price":83.19,
"number_of_offers":1
}
]
}
Here is the output of the conversion
part_number_key,buy_button_rank,category_id,id,brand,vendor_category_id,name,part_number,sale_price,currency,description,url,warranty,general_stock,weight,status,recommended_price,images,characteristics,attachments,vat_id,family,start_date,estimated_stock,reversible_vat_charging,min_sale_price,max_sale_price,offer_details,offer_properties,product_measurements,availability,stock,handling_time,barcode,ean,commission,validation_status,offer_validation_status,ownership,best_offer_sale_price,best_offer_recommended_price,number_of_offers
DASDSA,1,11,123,brand,,Name,"part number",12.31,RON,description,https://www.url.com,12,10,0,1,12.19,Array,Array,Array,1,Array,Array,10,,11,200,Array,Array,Array,Array,Array,Array,Array,Array,Array,Array,Array,1,11,11,1
D3129DKQW,1,456,123,Bluedio,,name,"part number",123,EUR,description,https://url.com,24,1,0,1,12,Array,Array,Array,1,Array,Array,144,,11,200,Array,Array,Array,Array,Array,Array,Array,Array,Array,Array,Array,1,77.31,83.19,1
As you can see, there is array instead of values, can someone tell me why?
I tried reset() function but I get only the value from the first column ( part_number_key )
foreach ($json as $line){
if (empty($firstLineKeys)) {
$firstLineKeys = array_keys($line); print_r("aici3"); fputcsv($f,
$firstLineKeys); print_r("aici3"); $firstLineKeys = array_flip($firstLineKeys);
} fputcsv($f,
array_merge($firstLineKeys,
$line)); $first = reset($line); print_r($first);
}
I want to extract all columns but to show only the first row value from every from every column, for example at "images" column I would like to extract just the first url and same for the rest of columns
And one more thing, what is the best method to ignore few columns?
Thanks in advance!

Related

How to recursively iterate for dynamic data

I've Json data in PHP which is:
{
"module": [
{
"jhooq-webserver-1": [
{
"source": ".//module-1"
}
]
},
{
"jhooq-webserver-2": [
{
"source": ".//module-2"
}
]
}
],
"provider": [
{
"aws": [
{
"access_key": "var.access_key",
"region": "var.web_region",
"secret_key": "var.secret_key"
}
]
}
]
}
All i wanted is to use recursive function to convert it to the below format (output):
provider "aws" {
region = "var.web_region"
access_key = "var.access_key"
secret_key = "var.secret_key"
}
module "jhooq-webserver-1" {
source = ".//module-1"
}
module "jhooq-webserver-2" {
source = ".//module-2"
}
Any help will be greatly appreciated.
I've tried using recursive function in php which i failed unfortunately failed. I'm unclear to use recursive function for my scenario.
You don't really need a recursive function to do this:
foreach($array as $blockName => $level2) {
foreach($level2 as $level3) {
foreach($level3 as $title => $level4) {
echo "$blockName \"$title\" {\n";
foreach($level4 as $level5) {
foreach($level5 as $key => $value) {
echo " $key = \"$value\"\n";
}
}
echo "}\n";
}
}
}

How to get data from JSON after decoding

I am trying to get data from a JSON that's a bit complex for me.
How can I get the value in Amount or Transaction date
I can get the MerchantRequestID using this code
$mpesaResponse = file_get_contents('php://input');
$jsonMpesaResponse = json_decode($mpesaResponse, true);
$MerchantRequestID = $jsonMpesaResponse["Body"]["stkCallback"]["MerchantRequestID"];
// An accepted request
{
"Body":{
"stkCallback":{
"MerchantRequestID":"19465-780693-1",
"CheckoutRequestID":"ws_CO_27072017154747416",
"ResultCode":0,
"ResultDesc":"The service request is processed successfully.",
"CallbackMetadata":{
"Item":[
{
"Name":"Amount",
"Value":1
},
{
"Name":"MpesaReceiptNumber",
"Value":"LGR7OWQX0R"
},
{
"Name":"Balance"
},
{
"Name":"TransactionDate",
"Value":20170727154800
},
{
"Name":"PhoneNumber",
"Value":254721566839
}
]
}
}
}
}
At the moment it comes out blank what ever i try
You need to loop through your Item array.
<?php
$str = '
{
"Body":{
"stkCallback":{
"MerchantRequestID":"19465-780693-1",
"CheckoutRequestID":"ws_CO_27072017154747416",
"ResultCode":0,
"ResultDesc":"The service request is processed successfully.",
"CallbackMetadata":{
"Item":[
{
"Name":"Amount",
"Value":1
},
{
"Name":"MpesaReceiptNumber",
"Value":"LGR7OWQX0R"
},
{
"Name":"Balance"
},
{
"Name":"TransactionDate",
"Value":20170727154800
},
{
"Name":"PhoneNumber",
"Value":254721566839
}
]
}
}
}
}
';
$json = json_decode($str, true);
foreach($json['Body']['stkCallback']['CallbackMetadata']['Item'] as $index => $item_array_element){
if( $item_array_element['Name'] == 'Amount' ){
echo "Found Amount " . $item_array_element['Value'] . "\n";
}
else if( $item_array_element['Name'] == 'TransactionDate' ){
echo "Found TransactionDate " . $item_array_element['Value'] . "\n";
}
}
Output
Found Amount 1
Found TransactionDate 20170727154800

Parsing and echoing JSON with PHP

Ok I have banged my head for the last 24hrs and cannot figure this out after testing and reading SO. I have a JSON file that I need to parse and for some reason I cannot get the right combination of code to echo anything out other than "Array" or "Trying to get property of non-object" or "Invalid argument supplied for foreach()". Here is the full JSON that I am trying to iterate over. I need to print out the name of each 'project', the 'dataset' in each project, and the 'permissions' for each 'dataset'. I am building this JSON myself, so I can change the format if necessary.
$data = json_decode($json, true);
Outputs JSON below:
[
[
{
"projects":[
{
"project":"test-project-1",
"datasets":[
{
"dataset":"testing1",
"permissions":[
{
"role":"READER",
"google_group":"testing1#test.com"
}
]
},
{
"dataset":"testing2",
"permissions":[
{
"role":"OWNER",
"google_group":"testing2#test.com"
}
]
},
{
"dataset":"testing3",
"permissions":[
{
"role":"READER",
"google_group":"testing3#test.com"
}
]
},
{
"dataset":"testing4",
"permissions":[
{
"role":"WRITER",
"google_group":"testing4#test.com"
}
]
}
]
},
{
"project":"test-project-2",
"datasets":[
{
"dataset":"testing1",
"permissions":[
{
"role":"READER",
"google_group":"testing1#test.com"
}
]
},
{
"dataset":"testing2",
"permissions":[
{
"role":"READER",
"google_group":"testing2#test.com"
}
]
},
{
"dataset":"testing3",
"permissions":[
{
"role":"READER",
"google_group":"testing3#test.com"
}
]
},
{
"dataset":"testing4",
"permissions":[
{
"role":"READER",
"google_group":"testing4#test.com"
}
]
}
]
}
]
}
]
]
I have tried things like:
foreach($data->projects as $output)
{
echo $output->project . "\n";
foreach($output->datasets as $datasets)
{
echo $output->dataset . "\n";
}
}
Thank you for the help!
EDIT: Working code that parses the JSON above:
$projects = $json['projects'];
foreach ($projects as $project) {
echo $project['project'] . "<br>";
foreach ($json['projects'][0]['datasets'] as $datasets){
echo $datasets['dataset'] . "<br>";
foreach ($json['projects'][0]['datasets'][0]['permissions'] as $permissions){
echo $permissions['role'] . "<br>";
echo $permissions['google_group'] . "<br>";
}
}
}
Your json contains two nested arrays, which contain the object, you want. So first you could use something like [0][0] to get the associative array, which contains the project.
// ...
$projects = $json['projects'];
foreach ($projects as $project) {
echo "Project: " . $project['project'] . "\n";
foreach ($project['datasets'] as $dataset) {
echo "Dataset: " . $dataset['dataset'];
foreach ($dataset['permissions'][0] as $key => $value) {
// ...
}
}
}

How to get a list of files of subfolders and write them in a JSON using php?

I'm already able to list all the file names and current directory/folder but I don't know how to create JSON entries for subdirectories
Here is my code
<?php
$dir = "office/";
if(is_dir($dir)){
if($dh = opendir($dir)){
while(($file = readdir($dh)) != false){
if($file != "." and $file != ".."){
$files_array[] = array('file' => $file); // Add the file to the array
}
}
}
$return_array =array('dir' => $files_array);
exit (json_encode($return_array));
}
?>
and output is
{
"dir": [
{
"file": "FreeWallpapersApp.zip"
},
{
"file": "20151211_ClipArtForTextView.7z"
},
{
"file": "QRite.7z"
},
{
"file": "CustomDialog_app_contacts.zip"
},
{
"file": "LockScreenBasicApp.apk"
},
{
"file": "ImgViewEffects.zip"
},
]
}
How to show files inside subfolder using php to also generate file names which are in subdirectories .
One way to achieve the recursive listing of a directory is using a recursive function.
/*
* Converts a filesystem tree to a PHP array.
*/
function dir_to_array($dir)
{
if (! is_dir($dir)) {
// If the user supplies a wrong path we inform him.
return null;
}
// Our PHP representation of the filesystem
// for the supplied directory and its descendant.
$data = [];
foreach (new DirectoryIterator($dir) as $f) {
if ($f->isDot()) {
// Dot files like '.' and '..' must be skipped.
continue;
}
$path = $f->getPathname();
$name = $f->getFilename();
if ($f->isFile()) {
$data[] = [ 'file' => $name ];
} else {
// Process the content of the directory.
$files = dir_to_array($path);
$data[] = [ 'dir' => $files,
'name' => $name ];
// A directory has a 'name' attribute
// to be able to retrieve its name.
// In case it is not needed, just delete it.
}
}
// Sorts files and directories if they are not on your system.
\usort($data, function($a, $b) {
$aa = isset($a['file']) ? $a['file'] : $a['name'];
$bb = isset($b['file']) ? $b['file'] : $b['name'];
return \strcmp($aa, $bb);
});
return $data;
}
/*
* Converts a filesystem tree to a JSON representation.
*/
function dir_to_json($dir)
{
$data = dir_to_array($dir);
$data = json_encode($data);
return $data;
}
In your example,
echo dir_to_json('office/');
would output this
{
"dir": [
{
"file": "FreeWallpapersApp.zip"
},
{
"file": "20151211_ClipArtForTextView.7z"
},
// ...
{
"dir": [
{
"file": "App.zip"
},
{
"file": "View.7z"
},
"name": "A Directory With Files",
]
},
]
}
Update 1:
I updated the answer to explicitly sort the files and directories.

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;

Categories