Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Save attachments from imap messages to disk.
<?php
/**
* imap-attachment.php
*
* @author hakre <hakre.wordpress.com>
* @link http://stackoverflow.com/questions/9974334/how-to-download-mails-attachment-to-a-specific-folder-using-imap-and-php
*/
/**
* Utility Class
*/
class IMAP
{
/**
*
* =?x-unknown?B?
* =?iso-8859-1?Q?
* =?windows-1252?B?
*
* @param string $stringQP
* @param string $base (optional) charset (IANA, lowercase)
* @return string UTF-8
*/
public static function decodeToUTF8($stringQP, $base = 'windows-1252')
{
$pairs = array(
'?x-unknown?' => "?$base?"
);
$stringQP = strtr($stringQP, $pairs);
return imap_utf8($stringQP);
}
}
class IMAPMailbox implements IteratorAggregate, Countable
{
private $stream;
public function __construct($hostname, $username, $password)
{
$stream = imap_open($hostname, $username, $password);
if (FALSE === $stream) {
throw new Exception('Connect failed: ' . imap_last_error());
}
$this->stream = $stream;
}
public function getStream()
{
return $this->stream;
}
/**
* @return stdClass
*/
public function check()
{
$info = imap_check($this->stream);
if (FALSE === $info) {
throw new Exception('Check failed: ' . imap_last_error());
}
return $info;
}
/**
* @param string $criteria
* @param int $options
* @param int $charset
* @return IMAPMessage[]
* @throws Exception
*/
public function search($criteria, $options = NULL, $charset = NULL)
{
$emails = imap_search($this->stream, $criteria, $options, $charset);
if (FALSE === $emails) {
throw new Exception('Search failed: ' . imap_last_error());
}
foreach ($emails as &$email) {
$email = $this->getMessageByNumber($email);
}
return $emails;
}
/**
* @param int $number
* @return IMAPMessage
*/
public function getMessageByNumber($number)
{
return new IMAPMessage($this, $number);
}
public function getOverview($sequence = NULL)
{
if (NULL === $sequence) {
$sequence = sprintf('1:%d', count($this));
}
return new IMAPOverview($this, $sequence);
}
/**
* Retrieve an external iterator
* @link http://php.net/manual/en/iteratoraggregate.getiterator.php
* @return Traversable An instance of an object implementing Iterator or
* Traversable
*/
public function getIterator()
{
return $this->getOverview()->getIterator();
}
/**
* @return int
*/
public function count()
{
return $this->check()->Nmsgs;
}
}
class IMAPOverview extends ArrayObject
{
private $mailbox;
public function __construct(IMAPMailbox $mailbox, $sequence)
{
$result = imap_fetch_overview($mailbox->getStream(), $sequence);
if (FALSE === $result) {
throw new Exception('Overview failed: ' . imap_last_error());
}
$this->mailbox = $mailbox;
foreach ($result as $overview)
{
if (!isset($overview->subject)) {
$overview->subject = '';
} else {
$overview->subject = IMAP::decodeToUTF8($overview->subject);
}
}
parent::__construct($result);
}
/**
* @return IMAPMailbox
*/
public function getMailbox()
{
return $this->mailbox;
}
}
class IMAPMessage
{
private $mailbox;
private $number;
private $stream;
public function __construct(IMAPMailbox $mailbox, $number)
{
$this->mailbox = $mailbox;
$this->number = $number;
$this->stream = $mailbox->getStream();
}
public function getNumber()
{
return $this->number;
}
/**
* @param int $number
* @return string
*/
public function fetchBody($number)
{
return imap_fetchbody($this->stream, $this->number, $number);
}
/**
* @return stdClass
* @throws Exception
*/
public function fetchOverview()
{
$result = imap_fetch_overview($this->stream, $this->number);
if (FALSE === $result) {
throw new Exception('FetchOverview failed: ' . imap_last_error());
}
list($result) = $result;
foreach ($result as &$prop) {
$prop = imap_utf8($prop);
}
return $result;
}
public function fetchStructure()
{
$structure = imap_fetchstructure($this->stream, $this->number);
if (FALSE === $structure) {
throw new Exception('FetchStructure failed: ' . imap_last_error());
}
return $structure;
}
/**
* @return IMAPAttachments
*/
public function getAttachments()
{
return new IMAPAttachments($this);
}
public function __toString()
{
return (string)$this->number;
}
}
class IMAPAttachment
{
private $attachment;
private $message;
public function __construct(IMAPMessage $message, $attachment)
{
$this->message = $message;
$this->attachment = $attachment;
}
/**
* @return string;
*/
public function getBody()
{
return $this->message->fetchBody($this->attachment->number);
}
/**
* @return int
*/
public function getSize()
{
return (int)$this->attachment->bytes;
}
/**
* @return string
*/
public function getExtension()
{
return pathinfo($this->getFilename(), PATHINFO_EXTENSION);
}
public function getFilename()
{
$filename = $this->attachment->filename;
NULL === $filename && $filename = $this->attachment->name;
return $filename;
}
public function __toString()
{
$encoding = $this->attachment->encoding;
switch ($encoding) {
case 0: // 7BIT
case 1: // 8BIT
case 2: // BINARY
return $this->getBody();
case 3: // BASE-64
return base64_decode($this->getBody());
case 4: // QUOTED-PRINTABLE
return imap_qprint($this->getBody());
}
throw new Exception(sprintf('Encoding failed: Unknown encoding %s (5: OTHER).', $encoding));
}
}
class IMAPAttachments extends ArrayObject
{
private $message;
public function __construct(IMAPMessage $message)
{
$array = $this->setMessage($message);
parent::__construct($array);
}
private function setMessage(IMAPMessage $message)
{
$this->message = $message;
return $this->parseStructure($message->fetchStructure());
}
private function parseStructure($structure)
{
$attachments = array();
if (!isset($structure->parts)) {
return $attachments;
}
foreach ($structure->parts as $index => $part)
{
if (!$part->ifdisposition) continue;
$attachment = new stdClass;
$attachment->isAttachment = FALSE;
$attachment->number = $index + 1;
$attachment->bytes = $part->bytes;
$attachment->encoding = $part->encoding;
$attachment->filename = NULL;
$attachment->name = NULL;
$part->ifdparameters
&& ($attachment->filename = $this->getAttribute($part->dparameters, 'filename'))
&& $attachment->isAttachment = TRUE;
$part->ifparameters
&& ($attachment->name = $this->getAttribute($part->parameters, 'name'))
&& $attachment->isAttachment = TRUE;
$attachment->isAttachment
&& $attachments[] = new IMAPAttachment($this->message, $attachment);
}
return $attachments;
}
private function getAttribute($params, $name)
{
foreach ($params as $object)
{
if ($object->attribute == $name) {
return IMAP::decodeToUTF8($object->value);
}
}
return NULL;
}
}

very nice clean code!
as a newbie, I can see this code is beautiful :)

mjc169 commented Sep 30, 2015

can you give me a demo code on how to use this class with imap_search "All" that has attachments

kamaroly commented Jan 6, 2016

@mjc169 Please find below sample codes,The code of these classes is more or less wrapping the imap_... functions, but for the attachment classes, it's doing the parsing of the structures as well. Hope this is helpful.

$savedir = __DIR__ . '/imap-dump/';

$inbox = new IMAPMailbox($hostname, $username, $password);

$emails = $inbox->search('ALL');
if ($emails) {
    rsort($emails);
    foreach ($emails as $email) {
        foreach ($email->getAttachments() as $attachment) {
            $savepath = $savedir . $attachment->getFilename();
            file_put_contents($savepath, $attachment);
        }
    }
}

Zip Archive is not working is this correct? How can this be solved?

Omarmaste commented Jun 5, 2017 edited

Hi, please, i need error

Fatal error: Uncaught exception 'Exception' with message 'Search failed: [BADCHARSET]' in path .... 92 Stack trace: #0 C:\xampp\htdocs\testIMAP\downloadFileImapv2.php(359): IMAPMailbox->search('UNSEEN FROM ".....@gmail.com".') #1 {main} thrown in C:\xampp\htdocs\testIMAP\downloadFileImapv2.php on line 92

Notice: Unknown: [BADCHARSET] (errflg=2) in Unknown on line 0

i have put in line 24 $base = 'windows-1252' like this code

Thank

hello, @kamaroly
i want to download email attachment from my hosting server email account then it's working or not?
if yes then which username and password are use hosting or email account

tonykj commented Sep 2, 2017 edited

Great code and works in most circumstances but what can be done when there is no attachment disposition? I have one major supplier who emails bulk orders through in batches and, although the PDF attachment is visible, the structure doesn't have attachment parameter included. With all other suppliers it works fine.

This is the structure for those that work fine >

Array ( [0] => stdClass Object ( [type] => 0 [encoding] => 4 [ifsubtype] => 1 [subtype] => PLAIN [ifdescription] => 1 [description] => The attachment name [ifid] => 0 [ifdisposition] => 1 [disposition] => inline [ifdparameters] => 0 [ifparameters] => 1 [parameters] => Array ( [0] => stdClass Object ( [attribute] => charset [value] => utf-8 ) ) ) [1] => stdClass Object ( [type] => 3 [encoding] => 3 [ifsubtype] => 1 [subtype] => PDF [ifdescription] => 1 [description] => The attachment name [ifid] => 0 [bytes] => 114446 [ifdisposition] => 1 [disposition] => attachment [ifdparameters] => 1 [dparameters] => Array ( [0] => stdClass Object ( [attribute] => filename [value] => The attachment name.pdf ) ) [ifparameters] => 1 [parameters] => Array ( [0] => stdClass Object ( [attribute] => name [value] => The attachment name.pdf ) ) ) )

And this is the structure of the emails coming through which is causing the problem, these are sent directly from SAP overnight >

Array ( [0] => stdClass Object ( [type] => 0 [encoding] => 4 [ifsubtype] => 1 [subtype] => PLAIN [ifdescription] => 0 [ifid] => 0 [lines] => 15 [bytes] => 1163 [ifdisposition] => 0 [ifdparameters] => 0 [ifparameters] => 1 [parameters] => Array ( [0] => stdClass Object ( [attribute] => charset [value] => us-ascii ) ) ) [1] => stdClass Object ( [type] => 2 [encoding] => 0 [ifsubtype] => 1 [subtype] => RFC822 [ifdescription] => 0 [ifid] => 0 [lines] => 326 [bytes] => 23823 [ifdisposition] => 0 [ifdparameters] => 0 [ifparameters] => 0 [parameters] => stdClass Object ( ) [parts] => Array ( [0] => stdClass Object ( [type] => 3 [encoding] => 3 [ifsubtype] => 1 [subtype] => PDF [ifdescription] => 1 [description] => The attachment name [ifid] => 0 [bytes] => 20032 [ifdisposition] => 0 [ifdparameters] => 0 [ifparameters] => 1 [parameters] => Array ( [0] => stdClass Object ( [attribute] => name [value] => The attachment name.pdf ) ) ) ) ) )

Any help would be great.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment