Skip to content

Instantly share code, notes, and snippets.

@hakre
Created April 11, 2012 22:50
Show Gist options
  • Save hakre/2363305 to your computer and use it in GitHub Desktop.
Save hakre/2363305 to your computer and use it in GitHub Desktop.
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;
}
}
@ppotulski
Copy link

Fatal error: Uncaught Error: Call to undefined function imap_open()
@SerenellaWonder U must install imap extension for php
like this:
sudo apt-get update -y
sudo apt-get install -y php7.2-imap

@rzunigacr
Copy link

Hi :)

Can somebody help to get out the Email date, subject and email from using this code, sorry for bothering you guys I am just starting with PHP and not to much into objects, help will be appreciated

Regards.

@rzunigacr
Copy link

I use the code and it works perfect for me, but I cant get the subject, email date, email can somebody help me

Regards.

@Den2016
Copy link

Den2016 commented Nov 10, 2022

throw new Exception(sprintf('Encoding failed: Unknown encoding %s (5: OTHER).', $encoding)); in IMAPAttachment->__toString() cause error
Throwing an exception from ''__toString'' is only possible since PHP 7.4
))))))) i`m have 7.3 )))))))')

@hakre
Copy link
Author

hakre commented Jan 17, 2023

@Den2016: if you're bound to PHP 7.3, change the throw into a trigger_error() with E_USER_ERROR which also fatals and/or E_USER_WARNING and return some fall-through string, depending on your workflow. It's not completely the same, but may give you the wiggle room.

@twoconcepts
Copy link

@hakre Many thanks for the source code.
Unfortunately, I have the following problem: If the e-mails are S/MIME signed, I only receive the smime.p7s certificate as an attachment. All other attachments are unfortunately missing.

@hakre
Copy link
Author

hakre commented May 26, 2024

@twoconcepts: Many thanks for your comment. That's related to S/MIME I'd guess. Are you seeing the email message in plain text or only the attachment? Perhaps the test for $part->ifdisposition (line 304) is now bogus or for the attributes as they all are falsy (e.g. empty string or null, lines 313 and 316).

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