-
-
Save hakre/2363305 to your computer and use it in GitHub Desktop.
<?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; | |
} | |
} |
@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.
@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.
@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).
throw new Exception(sprintf('Encoding failed: Unknown encoding %s (5: OTHER).', $encoding));
inIMAPAttachment->__toString()
cause errorThrowing an exception from ''__toString'' is only possible since PHP 7.4
))))))) i`m have 7.3 )))))))')