How to extract email attachments using Horde IMAP Client in PHP
Asked Answered
B

1

6

I am using Horde IMAP Client to fetch emails from an IMAP server. So far so good, I can authenticate, connect to a mailbox, download and parse emails.

The problem now is I need to parse emails with attachments, and I am finding it a bit tricky figuring out how to set the fetch query, then fetch and get access to email attachments.

I have tried searching the web but not much info on how to use Horde IMAP Client is available on the net, and their documentation is not helping at all.

How can I fetch the attachments from an email?

Here is the code I am using to fetch the envelope details, and plain-text emails from a mailbox (using Composer, rather than PEAR, hence the autoload):

<?php

    // Init
    require_once('./vendor/autoload.php');
    echo '<pre>';


    // Function:  Returns a comma-separated list of names and email addresses from a Horde_Mail_Rfc822_List object
    function get_parties($addresses) {
        $parties = [];

        foreach($addresses as $address){
            $name = trim($address->personal, "'") ;
            $email = $address->bare_address;
            $parties[] = $name ? "$name ($email)" : $email;
        }

        return implode(', ', $parties);
    }


    // Connect to IMAP
    try {
        $client = new Horde_Imap_Client_Socket(array(
            'username'  => '[email protected]',
            'password'  => 'S0m3PASS',
            'hostspec'  => 'mygreat.webhost.com',
            'port'      => '143',
            'secure'    => 'tls'
        ));
    }

    // Failed to connect
    catch (Horde_Imap_Client_Exception $e) {
        echo "<H1>ERROR!!</H1>";
    }


    // Search for messages in the Inbox
    $query = new Horde_Imap_Client_Search_Query();
    $results = $client->search('INBOX', $query);

    // Loop over each email found
    foreach($results['match'] as $match) {
        $query = new Horde_Imap_Client_Fetch_Query();
        $query->envelope();
        $query->structure();

        $uid =          new Horde_Imap_Client_Ids($match);
        $list =         $client->fetch('INBOX', $query, array('ids' => $uid));
        $envelope =     $list->first()->getEnvelope();
        $subject =      $envelope->subject;
        $from =         get_parties($envelope->from);
        $to =           get_parties($envelope->to);
        $cc =           get_parties($envelope->cc);
        $timestamp =    $envelope->date->getTimestamp();
        $bst =          date('I', $timestamp) * 3600;
        $date =         gmdate('d/m/Y H:i', $timestamp + $bst);     // If no date is specified in the email header, it will default to now
        $part =         $list->first()->getStructure();
        $id =           $part->findBody();
        $body =         $part->getPart($id);

        // Output the envelope details
        echo    "<h1>$date</h1>" .
                "<h1>From: $from</h1>" .
                "<h1>To: $to</h1>" .
                ($cc ? "<h1>CC: $cc</h1>" : '') .
                "<h1>$subject</h1>";

        // Get the message
        $query2 = new Horde_Imap_Client_Fetch_Query();
        $query2->bodyPart($id, array(
            'decode' => true,
            'peek' => true
        ));

        $list2 = $client->fetch('INBOX', $query2, array(
            'ids' => $uid
        ));

        $message2 = $list2->first();
        $text = $message2->getBodyPart($id);
        $body->setContents($text);
        echo $text = $body->getContents();

        echo '<br><br>';
        echo '<H1>ENVELOPE</H1>';
        print_r($envelope);

        echo '<br><br>';
        echo '<H1>STRUCTURE</H1>';
        print_r($part);

        echo '<hr>';
    }

    echo '</pre>';

?>
Brooking answered 20/11, 2017 at 14:51 Comment(1)
After reviewing Horde IMAP Client files, I find the available query under: horde/checkout/horde.git/framework/Imap_Client/lib/Horde/Imap/Client/Data/Fetch/Query.php, I don't see any attachment function. But IMP is using there own custom attachment.php, maybe try to replicate them?Inesita
B
2

Basing on this example, I've modified your code as follows:

<!DOCTYPE html>
<html>
<body>
<pre>
<?php

require_once './vendor/autoload.php';

class ImapClient {
    protected $mailbox = '';
    protected $client  = null;
    protected $params  = array();

    public function __construct(array $params) {
        $this->params  = $params;
    }

    public function login() {
        $this->client = new Horde_Imap_Client_Socket($this->params);
    }

    public function logout() {
        if ($this->client) {
            $this->client->close();
        }
        $this->client = null;
    }

    public function get_messages($mailbox) {
        $this->mailbox = $mailbox;

        $query = new Horde_Imap_Client_Search_Query();
        $results = $this->client->search($this->mailbox, $query);

        $query = new Horde_Imap_Client_Fetch_Query();
        $query->envelope();
        $query->structure();

        return $this->client->fetch($this->mailbox, $query, array('ids' => $results['match']));
    }

    public function parse_message_envelope(Horde_Imap_Client_Data_Fetch $message) {
        $envelope = $message->getEnvelope();

        $msghdr = new StdClass;
        $msghdr->recipients = $envelope->to->bare_addresses;
        $msghdr->senders    = $envelope->from->bare_addresses;
        $msghdr->cc         = $envelope->cc->bare_addresses;
        $msghdr->subject    = $envelope->subject;
        $msghdr->timestamp  = $envelope->date->getTimestamp();
        return $msghdr;
    }

    public function parse_message_parts(Horde_Imap_Client_Data_Fetch $message) {
        // We need the structure at various points below.
        $structure = $message->getStructure();

        // Now fetch the rest of the message content.
        $query = new Horde_Imap_Client_Fetch_Query();
        $query->fullText();

        // Fetch all of the message parts too.
        $typemap = $structure->contentTypeMap();
        foreach ($typemap as $part => $type) {
            // The body of the part - attempt to decode it on the server.
            $query->bodyPart($part, array(
                'decode' => true,
                'peek' => true,
            ));
            $query->bodyPartSize($part);
        }

        $id = new Horde_Imap_Client_Ids($message->getUid());
        $messagedata = $this->client->fetch($this->mailbox, $query, array('ids' => $id))->first();

        // Store the data for this message.
        $msgdata = new StdClass;
        $msgdata->id = $message->getUid();
        $msgdata->contentplain = '';
        $msgdata->contenthtml  = '';
        $msgdata->attachments  = array(
            'inline'     => array(),
            'attachment' => array(),
        );

        $plainpartid = $structure->findBody('plain');
        $htmlpartid  = $structure->findBody('html');

        foreach ($typemap as $part => $type) {
            // Get the message data from the body part, and combine it with the structure to give a fully-formed output.
            $stream = $messagedata->getBodyPart($part, true);
            $partdata = $structure->getPart($part);
            $partdata->setContents($stream, array('usestream' => true));
            if ($part == $plainpartid) {
                $msgdata->contentplain = $partdata->getContents();
            } else if ($part == $htmlpartid) {
                $msgdata->contenthtml = $partdata->getContents();
            } else if ($filename = $partdata->getName($part)) {
                $disposition = $partdata->getDisposition();
                $disposition = ($disposition == 'inline') ? 'inline' : 'attachment';
                $attachment = new StdClass;
                $attachment->name    = $filename;
                $attachment->type    = $partdata->getType();
                $attachment->content = $partdata->getContents();
                $attachment->size    = strlen($attachment->content);
                $msgdata->attachments[$disposition][] = $attachment;
            }
        }
        return $msgdata;
    }
}


$params = array(
    'username'  => '[email protected]',
    'password'  => 'S0m3PASS',
    'hostspec'  => 'mygreat.webhost.com',
    'port'      => '143',
    'secure'    => 'tls'    
);
$client = new ImapClient($params);

try {
    $client->login();
} catch (Exception $e) {
    die($e->getMessage());
}

// Retrieve the messages
$messages = $client->get_messages('INBOX');
printf("Found %d messages\n", $messages->count());

foreach ($messages as $message) {
    $hdr  = $client->parse_message_envelope($message);

    printf("\nDate: %s\n", date('d/m/Y H:i:s', $hdr->timestamp));
    printf("From: %s\n", implode(',', $hdr->senders));
    printf("To: %s\n", implode(',', $hdr->recipients));
    if (count($hdr->cc)) printf("Cc: %s\n", implode(',', $hdr->cc));
    printf("Subject: %s\n", $hdr->subject);

    $body = $client->parse_message_parts($message);

    //printf("Plain Body: %s\n", $body->contentplain);
    //printf("HTML Body: %s\n", $body->contenthtml);

    foreach ($body->attachments as $type => $attachments) {
        if (count($attachments)) {
            foreach ($attachments as $nr => $attachment) {
                printf("\n** %s #%d **\n", UCFirst($type), $nr);
                printf("\tName: %s\n", $attachment->name);
                printf("\tType: %s\n", $attachment->type);
                printf("\tSize: %d\n", $attachment->size);
              //printf("\tContent: %s\n", base64_encode($attachment->content));
            }
        }
    }
}

$client->logout();

?>
</pre>
</body>
</html>
Bochum answered 31/8, 2018 at 11:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.