3
votes

I'm trying to get a list of all the attachments in my mailbox using Gmail API PHP. I'm getting stuck at some point but I can't really figure where. Here's the code I'm using to get the message content, when I try to get the attachments it somehow fails.

I would like to get the filename and Attachment ID for all the messages corresponding to my "q" query. Here's my code:


    function atta($service, $userId, $expeditor)
          {
            try
            {
              unset($optParamsamz);
              $optParamsamz = [];
              $optParamsamz['maxResults'] = 10; // Return Only 5 Messages
              $optParamsamz['q'] = "has:attachment larger_than:10000000 "; // Only show messages in Inbox
              $messagesamz = $service->users_messages->listUsersMessages('me',$optParamsamz);
              $listamz = $messagesamz->getMessages();
              $idlist = array();
              foreach ($listamz as $key => $value) {
                $message = $service->users_messages->get('me', $value['id']);
                $array_message = $message->getPayload();
                print_r($array_message);
                echo "<br><br>";
              }

            }
            catch (Exception $e)
            {
              print 'An error occurred: ' . $e->getMessage();
            }

          }


2
I'm not that good at php, but I wrote a lightweight solution in JavaScript a while back. Maybe you can get some inspiration there until a php-pro sees the question :)Tholle
Thanks a lot! It doesn't really help me as I s_ck in JS but I'll try to find some clues there. If anyone has some guidelines in PHP :)justberare
The biggest problem I had in parsing attachment info in Java was that the payload parts can be nested. In the end I had to create a recursive method that iterates over MessageParts, and check each body before I could get the filename and attachment ID.Geir Thomas Jakobsen

2 Answers

5
votes

The answer by @trajchevska above is helpful but unfortunately, iterating over the payload parts will not catch some attachments like images in the message body which are parts within parts. To get these you need to iterate over parts recursively. I'm doing it like this:

function getAttachments($message_id, $parts) {
    $attachments = [];
    foreach ($parts as $part) {
        if (!empty($part->body->attachmentId)) {
            $attachment = $this->service->users_messages_attachments->get('me', $message_id, $part->body->attachmentId);
            $attachments[] = [
                'filename' => $part->filename,
                'mimeType' => $part->mimeType,
                'data'     => strtr($attachment->data, '-_', '+/')
            ];
        } else if (!empty($part->parts)) {
            $attachments = array_merge($attachments, $this->getAttachments($message_id, $part->parts));
        }
    }
    return $attachments;
}

In the example above, $this->service is an authenticated Google_Service_Gmail() instance (same as in the question). The above methods get called within your class as:

$attachments = $this->getAttachments($message->id, $message->getPayload()->parts);

This gets you the filename, mime type, and base64 encoded data in an array.

2
votes

I assume you've solved this until now, but I was struggling with Google's API for a while and, of course, looked for help here, so I guess this answer could help others.

Your code looks fine, so I assume the problem is after getting the message details, when trying to retrieve the attachments. A weird thing I noticed when dealing with the attachments is that the attachment_id is not consistent and changes with different calls to the message details, so instead of using that as identifier, I decided to go with the part_id, which doesn't change. So, after getting the payload, I do this:

$messageDetails = $message->getPayload();
foreach ($messageDetails['parts'] as $key => $value) {
  if (!isset($value['body']['data'])) {
      array_push($files, $value['partId']);
  }
}

Now, you have the ids of the parts including the attachments in your message. Next, you want to get all possible details for the attachments, so you can reconstruct the files and enable download. You'd need the filename and other details that are only returned with the message details and the attachment data, that is returned as part of the attachment details.

I'm pasting the getAttachment function as a whole.

public function getAttachment($messageId, $partId)
{
    try {
        $files = [];
        $gmail = new Google_Service_Gmail($this->authenticate->getClient());
        $attachmentDetails = $this->getAttachmentDetailsFromMessage($messageId, $partId);
        $attachment = $gmail->users_messages_attachments->get($this->authenticate->getUserId(), $messageId, $attachmentDetails['attachmentId']);
        if (!$attachmentDetails['status']) {
            return $attachmentDetails;
        }
        $attachmentDetails['data'] = $this->base64UrlDecode($attachment->data);
        return ['status' => true, 'data' => $attachmentDetails];
    } catch (\Google_Service_Exception $e) {
        return ['status' => false, 'message' => $e->getMessage()];
    }
}

The list of attachments (you already have the $messageId and $files is the array of partIds we got above):

if(!empty($files)) {
    foreach ($files as $key => $value) {
        echo '<a target="_blank" href="attachment.php?messageId='.$messageId.'&part_id='.$value.'">Attachment '.($key+1).'</a><br/>';
    }
}

And when the user opens the attachment.php:

$attachment = $msgs->getAttachment($_GET['messageId'],$_GET['part_id']);
foreach ($attachment['data']['headers'] as $key => $value) {
    header($key.':'.$value);
}
echo $attachment['data']['data'];

This will set the headers based on the attachment details and download the file in the right format.

Their API is pretty straightforward but they don't provide enough examples, so it's easy to get stuck with something. I know I did :). Working on this, I created a wrapper for Gmail's API, so in case you find it useful, here - https://packagist.org/packages/adevait/gmail-wrapper.