From fce4a9d21c139480244901949f834b2a5b0dee42 Mon Sep 17 00:00:00 2001 From: Arthur Aivazov Date: Tue, 5 May 2020 23:22:27 +0300 Subject: [PATCH 01/12] Add composer require ext-imap. --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 23370ab..f45ddcb 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,8 @@ ] }, "require": { - "php": ">= 5.4.0" + "php": ">= 5.4.0", + "ext-imap": "*" }, "require-dev": { "nette/tester": "@dev" From 575c565e52da00e154f52e628d5a4227bfbe90e3 Mon Sep 17 00:00:00 2001 From: Arthur Aivazov Date: Tue, 5 May 2020 23:22:44 +0300 Subject: [PATCH 02/12] Add composer require ext-mbstring. --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index f45ddcb..813e489 100644 --- a/composer.json +++ b/composer.json @@ -16,7 +16,8 @@ }, "require": { "php": ">= 5.4.0", - "ext-imap": "*" + "ext-imap": "*", + "ext-mbstring": "*" }, "require-dev": { "nette/tester": "@dev" From 36a8ee5b9bf86676c9f3e6721123ec46e332ea3c Mon Sep 17 00:00:00 2001 From: Arthur Aivazov Date: Tue, 5 May 2020 23:23:46 +0300 Subject: [PATCH 03/12] Add composer require ext-iconv. --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 813e489..48ae6c1 100644 --- a/composer.json +++ b/composer.json @@ -17,7 +17,8 @@ "require": { "php": ">= 5.4.0", "ext-imap": "*", - "ext-mbstring": "*" + "ext-mbstring": "*", + "ext-iconv": "*" }, "require-dev": { "nette/tester": "@dev" From 8720d5f598b3630a2ad771e227e30c2bd36b82ec Mon Sep 17 00:00:00 2001 From: Arthur Aivazov Date: Tue, 5 May 2020 23:27:03 +0300 Subject: [PATCH 04/12] ImapDriver::getHeaders() mimeDecode --- MailLibrary/Drivers/ImapDriver.php | 123 ++++++++++++++++------------- 1 file changed, 67 insertions(+), 56 deletions(-) diff --git a/MailLibrary/Drivers/ImapDriver.php b/MailLibrary/Drivers/ImapDriver.php index eb059e7..5e8f160 100644 --- a/MailLibrary/Drivers/ImapDriver.php +++ b/MailLibrary/Drivers/ImapDriver.php @@ -224,62 +224,52 @@ public function checkFilter($key, $value = NULL) { * @param int $mailId * @return array of name => value */ - public function getHeaders($mailId) - { - $raw = imap_fetchheader($this->resource, $mailId, FT_UID); - $lines = explode("\n", $raw); - $headers = array(); - $lastHeader = NULL; - foreach($lines as $line) { - if(mb_substr($line, 0, 1, 'UTF-8') === " ") { - $headers[$lastHeader] .= $line; - } else { - $parts = explode(':', $line); - $name = $parts[0]; - unset($parts[0]); - - $headers[$name] = implode(':', $parts); - $lastHeader = $name; - } - } - - foreach($headers as $key => $header) { - if(trim($key) === '') { - unset($headers[$key]); - continue; - } - if(strtolower($key) === 'subject') { - $decoded = imap_mime_header_decode($header); - - $text = ''; - foreach($decoded as $part) { - if($part->charset !== 'UTF-8' && $part->charset !== 'default') { - $text .= mb_convert_encoding($part->text, 'UTF-8', $part->charset); - } else { - $text .= $part->text; - } - } - - $headers[$key] = trim($text); - } else if(in_array(strtolower($key), self::$contactHeaders)) { - $contacts = imap_rfc822_parse_adrlist(imap_utf8(trim($header)), 'UNKNOWN_HOST'); - $list = new ContactList(); - foreach($contacts as $contact) { - $list->addContact( - isset($contact->mailbox) ? $contact->mailbox : NULL, - isset($contact->host) ? $contact->host : NULL, - isset($contact->personal) ? $contact->personal : NULL, - isset($contact->adl) ? $contact->adl : NULL - ); - } - $list->build(); - $headers[$key] = $list; - } else { - $headers[$key] = trim(imap_utf8($header)); - } - } - return $headers; - } + public function getHeaders($mailId) + { + $raw = imap_fetchheader($this->resource, $mailId, FT_UID); + preg_match_all('/([-\w]+):\s(.+)(?=\s[-\w]+:|$)/sU', $raw, $matches); + $headers = array(); + + foreach($matches[1] as $i => $key) { + $headers[$key] = trim($matches[2][$i]); + } + + foreach($headers as $key => $header) { + + if(trim($key) === '') { + unset($headers[$key]); + continue; + } + if(strtolower($key) === 'subject') { + $headers[$key] = trim($this->mimeDecode($header)); + } else if(in_array(strtolower($key), self::$contactHeaders)) { + $contacts = imap_rfc822_parse_adrlist(trim($this->mimeDecode($header)), 'UNKNOWN_HOST'); + $list = new ContactList(); + foreach($contacts as $contact) { + $list->addContact( + isset($contact->mailbox) ? $contact->mailbox : NULL, + isset($contact->host) ? $contact->host : NULL, + isset($contact->personal) ? $contact->personal : NULL, + isset($contact->adl) ? $contact->adl : NULL + ); + } + $list->build(); + $headers[$key] = $list; + } else { + $headers[$key] = trim(imap_utf8($header)); + } + } + return $headers; + } + + public function mimeDecode($str) + { + // Remove spaces between two encoded lines. + $str = preg_replace('/(=\?[^?]+\?[A-Z]\?[^?]+\?=)\s+(?=(=\?[^?]+\?[A-Z]\?[^?]+\?=))/', '$1', $str); + + // =?UTF-8?Q?=D0=9A=D0=BE=D0=BC=D0=B0=D0=BD=D0=B4=D0=B0?= + return preg_replace_callback('/=\?[^?]+\?[A-Z]\?[^?]+\?=/', array($this, 'mimeDecodeReplaceCallback'), $str); + } /** * Creates structure for mail @@ -466,4 +456,25 @@ protected function encodeMailboxName($name) return mb_convert_encoding($name, 'UTF7-IMAP', 'UTF-8'); } + /** + * @param array $m + * @return string + */ + private function mimeDecodeReplaceCallback($m) + { + $str = $m[0]; + + if($mime = strval(@iconv_mime_decode($str, 1, 'utf-8'))) { + return $mime; + } + + foreach(imap_mime_header_decode($str) as $header) { + if(strtolower($header->charset) != 'utf-8') { + $header->text = mb_convert_encoding($header->text, 'utf-8', $header->charset); + } + $mime .= $header->text; + } + + return $mime; + } } From 6a9c60ccfdb00d45ea020e4f3af9519432027426 Mon Sep 17 00:00:00 2001 From: Arthur Aivazov Date: Wed, 6 May 2020 00:38:38 +0300 Subject: [PATCH 05/12] Attache file iconv_mime_decode --- MailLibrary/Structures/ImapStructure.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/MailLibrary/Structures/ImapStructure.php b/MailLibrary/Structures/ImapStructure.php index ec9ed2e..8795cc2 100644 --- a/MailLibrary/Structures/ImapStructure.php +++ b/MailLibrary/Structures/ImapStructure.php @@ -152,12 +152,13 @@ protected function addStructurePart($structure, $partId) } if(isset($parameters['filename']) || isset($parameters['name'])) { - $this->attachmentsIds[] = array( - 'id' => $partId, - 'encoding' => $encoding, - 'name' => isset($parameters['filename']) ? $parameters['filename'] : $parameters['name'], - 'type' => self::$typeTable[$type]. '/' . $subtype, - ); + $name = isset($parameters['filename']) ? $parameters['filename'] : $parameters['name']; + $this->attachmentsIds[] = array( + 'id' => $partId, + 'encoding' => $encoding, + 'name' => iconv_mime_decode($name, 0, 'utf-8'), + 'type' => self::$typeTable[$type]. '/' . $subtype, + ); } else if($type === self::TYPE_TEXT) { if($subtype === 'HTML') { $this->htmlBodyIds[] = array('id' => $partId, 'encoding' => $encoding); From 88aba7c3a71c80696a8a8bd293579f47b4266713 Mon Sep 17 00:00:00 2001 From: Arthur Aivazov Date: Wed, 6 May 2020 00:43:32 +0300 Subject: [PATCH 06/12] Attache file iconv_mime_decode --- MailLibrary/Structures/ImapStructure.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MailLibrary/Structures/ImapStructure.php b/MailLibrary/Structures/ImapStructure.php index 8795cc2..090c35f 100644 --- a/MailLibrary/Structures/ImapStructure.php +++ b/MailLibrary/Structures/ImapStructure.php @@ -156,7 +156,7 @@ protected function addStructurePart($structure, $partId) $this->attachmentsIds[] = array( 'id' => $partId, 'encoding' => $encoding, - 'name' => iconv_mime_decode($name, 0, 'utf-8'), + 'name' => iconv_mime_decode($name, 0, $encoding), 'type' => self::$typeTable[$type]. '/' . $subtype, ); } else if($type === self::TYPE_TEXT) { From fc5915f18dbb0ceb42d9a5009afd1f1acd39d58c Mon Sep 17 00:00:00 2001 From: Arthur Aivazov Date: Wed, 6 May 2020 00:57:43 +0300 Subject: [PATCH 07/12] Attache file iconv_mime_decode --- MailLibrary/Structures/ImapStructure.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MailLibrary/Structures/ImapStructure.php b/MailLibrary/Structures/ImapStructure.php index 090c35f..8795cc2 100644 --- a/MailLibrary/Structures/ImapStructure.php +++ b/MailLibrary/Structures/ImapStructure.php @@ -156,7 +156,7 @@ protected function addStructurePart($structure, $partId) $this->attachmentsIds[] = array( 'id' => $partId, 'encoding' => $encoding, - 'name' => iconv_mime_decode($name, 0, $encoding), + 'name' => iconv_mime_decode($name, 0, 'utf-8'), 'type' => self::$typeTable[$type]. '/' . $subtype, ); } else if($type === self::TYPE_TEXT) { From 0889fc0690c3070d3ed6e0e7c646d50bf294db34 Mon Sep 17 00:00:00 2001 From: Arthur Aivazov Date: Wed, 6 May 2020 01:24:37 +0300 Subject: [PATCH 08/12] Driver getOverview --- MailLibrary/Drivers/IDriver.php | 266 +++++----- MailLibrary/Drivers/ImapDriver.php | 791 +++++++++++++++-------------- MailLibrary/Mail.php | 519 ++++++++++--------- 3 files changed, 801 insertions(+), 775 deletions(-) diff --git a/MailLibrary/Drivers/IDriver.php b/MailLibrary/Drivers/IDriver.php index 6ee2345..6b7420e 100644 --- a/MailLibrary/Drivers/IDriver.php +++ b/MailLibrary/Drivers/IDriver.php @@ -11,133 +11,139 @@ use greeny\MailLibrary\Structures\IStructure; interface IDriver { - /** - * Connects to server - * @throws DriverException - */ - function connect(); - - /** - * Flushes changes to server - * @throws DriverException - */ - function flush(); - - /** - * Gets all mailboxes - * @return array of string - * @throws DriverException - */ - function getMailboxes(); - - /** - * Creates new mailbox - * @param string $name - * @throws DriverException - */ - function createMailbox($name); - - /** - * Renames mailbox - * @param string $from - * @param string $to - * @throws DriverException - */ - function renameMailbox($from, $to); - - /** - * Deletes mailbox - * @param string $name - * @throws DriverException - */ - function deleteMailbox($name); - - /** - * Switches current mailbox - * @param string $name - * @throws DriverException - */ - function switchMailbox($name); - - /** - * Finds UIDs of mails by filter - * - * @param array $filters - * @param int $limit - * @param int $offset - * @param int $orderBy - * @param string $orderType - * @return array of UIDs - */ - function getMailIds(array $filters, $limit = 0, $offset = 0, $orderBy = Mail::ORDER_DATE, $orderType = 'ASC'); - - /** - * Checks if filter is applicable for this driver - * @param string $key - * @param mixed $value - * @throws DriverException - */ - function checkFilter($key, $value = NULL); - - /** - * Gets mail headers - * @param int $mailId - * @return array of name => value - */ - function getHeaders($mailId); - - /** - * Creates structure for mail - * @param int $mailId - * @param Mailbox $mailbox - * @return IStructure - */ - function getStructure($mailId, Mailbox $mailbox); - - /** - * Gets part of body - * @param int $mailId - * @param array $data - * @return string - */ - function getBody($mailId, array $data); - - /** - * Gets flags for mail - * @param int $mailId - * @return array - */ - function getFlags($mailId); - - /** - * Sets one flag for mail - * @param int $mailId - * @param string $flag - * @param bool $value - * @throws DriverException - */ - function setFlag($mailId, $flag, $value); - - /** - * Copies mail to another mailbox - * @param int $mailId - * @param string $toMailbox - * @throws DriverException - */ - function copyMail($mailId, $toMailbox); - - /** - * Moves mail to another mailbox - * @param int $mailId - * @param string $toMailbox - * @throws DriverException - */ - function moveMail($mailId, $toMailbox); - - /** - * Deletes mail - * @param int $mailId - * @throws DriverException - */ - function deleteMail($mailId); -} \ No newline at end of file + /** + * Connects to server + * @throws DriverException + */ + function connect(); + + /** + * Flushes changes to server + * @throws DriverException + */ + function flush(); + + /** + * Gets all mailboxes + * @return array of string + * @throws DriverException + */ + function getMailboxes(); + + /** + * Creates new mailbox + * @param string $name + * @throws DriverException + */ + function createMailbox($name); + + /** + * Renames mailbox + * @param string $from + * @param string $to + * @throws DriverException + */ + function renameMailbox($from, $to); + + /** + * Deletes mailbox + * @param string $name + * @throws DriverException + */ + function deleteMailbox($name); + + /** + * Switches current mailbox + * @param string $name + * @throws DriverException + */ + function switchMailbox($name); + + /** + * Finds UIDs of mails by filter + * + * @param array $filters + * @param int $limit + * @param int $offset + * @param int $orderBy + * @param string $orderType + * @return array of UIDs + */ + function getMailIds(array $filters, $limit = 0, $offset = 0, $orderBy = Mail::ORDER_DATE, $orderType = 'ASC'); + + /** + * Checks if filter is applicable for this driver + * @param string $key + * @param mixed $value + * @throws DriverException + */ + function checkFilter($key, $value = NULL); + + /** + * Gets mail headers + * @param int $mailId + * @return array of name => value + */ + function getHeaders($mailId); + + /** + * Creates structure for mail + * @param int $mailId + * @param Mailbox $mailbox + * @return IStructure + */ + function getStructure($mailId, Mailbox $mailbox); + + /** + * Gets part of body + * @param int $mailId + * @param array $data + * @return string + */ + function getBody($mailId, array $data); + + /** + * Gets flags for mail + * @param int $mailId + * @return array + */ + function getFlags($mailId); + + /** + * Sets one flag for mail + * @param int $mailId + * @param string $flag + * @param bool $value + * @throws DriverException + */ + function setFlag($mailId, $flag, $value); + + /** + * Copies mail to another mailbox + * @param int $mailId + * @param string $toMailbox + * @throws DriverException + */ + function copyMail($mailId, $toMailbox); + + /** + * Moves mail to another mailbox + * @param int $mailId + * @param string $toMailbox + * @throws DriverException + */ + function moveMail($mailId, $toMailbox); + + /** + * Deletes mail + * @param int $mailId + * @throws DriverException + */ + function deleteMail($mailId); + + /** + * @param $mailId + * @return array + */ + function getOverview($mailId); +} \ No newline at end of file diff --git a/MailLibrary/Drivers/ImapDriver.php b/MailLibrary/Drivers/ImapDriver.php index 5e8f160..81d3b00 100644 --- a/MailLibrary/Drivers/ImapDriver.php +++ b/MailLibrary/Drivers/ImapDriver.php @@ -15,215 +15,215 @@ class ImapDriver implements IDriver { - /** @var string */ - protected $username; - - /** @var string */ - protected $password; - - /** @var resource */ - protected $resource; - - /** @var string */ - protected $server; - - /** @var string */ - protected $currentMailbox = NULL; - - protected static $filterTable = array( - Mail::ANSWERED => '%bANSWERED', - Mail::BCC => 'BCC "%s"', - Mail::BEFORE => 'BEFORE "%d"', - Mail::BODY => 'BODY "%s"', - Mail::CC => 'CC "%s"', - Mail::DELETED => '%bDELETED', - Mail::FLAGGED => '%bFLAGGED', - Mail::FROM => 'FROM "%s"', - Mail::KEYWORD => 'KEYWORD "%s"', - Mail::NEW_MESSAGES => 'NEW', - Mail::NOT_KEYWORD => 'UNKEYWORD "%s"', - Mail::OLD_MESSAGES => 'OLD', - Mail::ON => 'ON "%d"', - Mail::RECENT => 'RECENT', - Mail::SEEN => '%bSEEN', - Mail::SINCE => 'SINCE "%d"', - Mail::SUBJECT => 'SUBJECT "%s"', - Mail::TEXT => 'TEXT "%s"', - Mail::TO => 'TO "%s"', - ); - - protected static $contactHeaders = array( - 'to', - 'from', - 'cc', - 'bcc', - ); - - public function __construct($username, $password, $host, $port = 993, $ssl = TRUE) - { - $ssl = $ssl ? '/ssl' : '/novalidate-cert'; - $this->server = '{'.$host.':'.$port.'/imap'.$ssl.'}'; - $this->username = $username; - $this->password = $password; - } - - /** - * Connects to server - * - * @throws DriverException if connecting fails - */ - public function connect() - { - if(!$this->resource = @imap_open($this->server, $this->username, $this->password, CL_EXPUNGE)) { // @ - to allow throwing exceptions - throw new DriverException("Cannot connect to IMAP server: " . imap_last_error()); - } - } - - /** - * Flushes changes to server - * - * @throws DriverException if flushing fails - */ - public function flush() - { - imap_expunge($this->resource); - } - - /** - * Gets all mailboxes - * - * @return array of string - * @throws DriverException - */ - public function getMailboxes() - { - $mailboxes = array(); - $foo = imap_list($this->resource, $this->server, '*'); - if(!$foo) { - throw new DriverException("Cannot get mailboxes from server: " . imap_last_error()); - } - foreach($foo as $mailbox) { - $mailboxes[] = mb_convert_encoding(str_replace($this->server, '', $mailbox), 'UTF8', 'UTF7-IMAP'); - } - return $mailboxes; - } - - /** - * Creates new mailbox - * - * @param string $name - * @throws DriverException - */ - public function createMailbox($name) - { - if(!imap_createmailbox($this->resource, $this->server . $name)) { - throw new DriverException("Cannot create mailbox '$name': " . imap_last_error()); - } - } - - /** - * Renames mailbox - * - * @param string $from - * @param string $to - * @throws DriverException - */ - public function renameMailbox($from, $to) - { - if(!imap_renamemailbox($this->resource, $this->server . $from, $this->server . $to)) { - throw new DriverException("Cannot rename mailbox from '$from' to '$to': " . imap_last_error()); - } - } - - /** - * Deletes mailbox - * - * @param string $name - * @throws DriverException - */ - public function deleteMailbox($name) - { - if(!imap_deletemailbox($this->resource, $this->server . $name)) { - throw new DriverException("Cannot delete mailbox '$name': " . imap_last_error()); - } - } - - /** - * Switches current mailbox - * - * @param string $name - * @throws DriverException - */ - public function switchMailbox($name) - { - if($name !== $this->currentMailbox) { - $this->flush(); - if(!imap_reopen($this->resource, $this->server . $name)) { - throw new DriverException("Cannot switch to mailbox '$name': " . imap_last_error()); - } - $this->currentMailbox = $name; - } - } - - /** - * Finds UIDs of mails by filter - * - * @param array $filters - * @param int $limit - * @param int $offset - * @param int $orderBy - * @param string $orderType - * @throws \greeny\MailLibrary\DriverException - * @return array of UIDs - */ - public function getMailIds(array $filters, $limit = 0, $offset = 0, $orderBy = Mail::ORDER_DATE, $orderType = 'ASC') - { - $filter = $this->buildFilters($filters); - - $orderType = $orderType === 'ASC' ? 1 : 0; - - if(!is_array($ids = imap_sort($this->resource, $orderBy, $orderType, SE_UID | SE_NOPREFETCH, $filter, 'UTF-8'))) { - throw new DriverException("Cannot get mails: " . imap_last_error()); - } - - return $limit === 0 ? $ids : array_slice($ids, $offset, $limit); - } - - /** - * Checks if filter is applicable for this driver - * - * @param string $key - * @param mixed $value - * @throws DriverException - */ - public function checkFilter($key, $value = NULL) { - if(!in_array($key, array_keys(self::$filterTable))) { - throw new DriverException("Invalid filter key '$key'."); - } - $filtered = self::$filterTable[$key]; - if(strpos($filtered, '%s') !== FALSE) { - if(!is_string($value)) { - throw new DriverException("Invalid value type for filter '$key', expected string, got ".gettype($value)."."); - } - } else if(strpos($filtered, '%d') !== FALSE) { - if(!($value instanceof DateTime) && !is_int($value) && !strtotime($value)) { - throw new DriverException("Invalid value type for filter '$key', expected DateTime or timestamp, or textual representation of date, got ".gettype($value)."."); - } - } else if(strpos($filtered, '%b') !== FALSE) { - if(!is_bool($value)) { - throw new DriverException("Invalid value type for filter '$key', expected bool, got ".gettype($value)."."); - } - } else if($value !== NULL) { - throw new DriverException("Cannot assign value to filter '$key'."); - } - } - - /** - * Gets mail headers - * - * @param int $mailId - * @return array of name => value - */ + /** @var string */ + protected $username; + + /** @var string */ + protected $password; + + /** @var resource */ + protected $resource; + + /** @var string */ + protected $server; + + /** @var string */ + protected $currentMailbox = NULL; + + protected static $filterTable = array( + Mail::ANSWERED => '%bANSWERED', + Mail::BCC => 'BCC "%s"', + Mail::BEFORE => 'BEFORE "%d"', + Mail::BODY => 'BODY "%s"', + Mail::CC => 'CC "%s"', + Mail::DELETED => '%bDELETED', + Mail::FLAGGED => '%bFLAGGED', + Mail::FROM => 'FROM "%s"', + Mail::KEYWORD => 'KEYWORD "%s"', + Mail::NEW_MESSAGES => 'NEW', + Mail::NOT_KEYWORD => 'UNKEYWORD "%s"', + Mail::OLD_MESSAGES => 'OLD', + Mail::ON => 'ON "%d"', + Mail::RECENT => 'RECENT', + Mail::SEEN => '%bSEEN', + Mail::SINCE => 'SINCE "%d"', + Mail::SUBJECT => 'SUBJECT "%s"', + Mail::TEXT => 'TEXT "%s"', + Mail::TO => 'TO "%s"', + ); + + protected static $contactHeaders = array( + 'to', + 'from', + 'cc', + 'bcc', + ); + + public function __construct($username, $password, $host, $port = 993, $ssl = TRUE) + { + $ssl = $ssl ? '/ssl' : '/novalidate-cert'; + $this->server = '{'.$host.':'.$port.'/imap'.$ssl.'}'; + $this->username = $username; + $this->password = $password; + } + + /** + * Connects to server + * + * @throws DriverException if connecting fails + */ + public function connect() + { + if(!$this->resource = @imap_open($this->server, $this->username, $this->password, CL_EXPUNGE)) { // @ - to allow throwing exceptions + throw new DriverException("Cannot connect to IMAP server: " . imap_last_error()); + } + } + + /** + * Flushes changes to server + * + * @throws DriverException if flushing fails + */ + public function flush() + { + imap_expunge($this->resource); + } + + /** + * Gets all mailboxes + * + * @return array of string + * @throws DriverException + */ + public function getMailboxes() + { + $mailboxes = array(); + $foo = imap_list($this->resource, $this->server, '*'); + if(!$foo) { + throw new DriverException("Cannot get mailboxes from server: " . imap_last_error()); + } + foreach($foo as $mailbox) { + $mailboxes[] = mb_convert_encoding(str_replace($this->server, '', $mailbox), 'UTF8', 'UTF7-IMAP'); + } + return $mailboxes; + } + + /** + * Creates new mailbox + * + * @param string $name + * @throws DriverException + */ + public function createMailbox($name) + { + if(!imap_createmailbox($this->resource, $this->server . $name)) { + throw new DriverException("Cannot create mailbox '$name': " . imap_last_error()); + } + } + + /** + * Renames mailbox + * + * @param string $from + * @param string $to + * @throws DriverException + */ + public function renameMailbox($from, $to) + { + if(!imap_renamemailbox($this->resource, $this->server . $from, $this->server . $to)) { + throw new DriverException("Cannot rename mailbox from '$from' to '$to': " . imap_last_error()); + } + } + + /** + * Deletes mailbox + * + * @param string $name + * @throws DriverException + */ + public function deleteMailbox($name) + { + if(!imap_deletemailbox($this->resource, $this->server . $name)) { + throw new DriverException("Cannot delete mailbox '$name': " . imap_last_error()); + } + } + + /** + * Switches current mailbox + * + * @param string $name + * @throws DriverException + */ + public function switchMailbox($name) + { + if($name !== $this->currentMailbox) { + $this->flush(); + if(!imap_reopen($this->resource, $this->server . $name)) { + throw new DriverException("Cannot switch to mailbox '$name': " . imap_last_error()); + } + $this->currentMailbox = $name; + } + } + + /** + * Finds UIDs of mails by filter + * + * @param array $filters + * @param int $limit + * @param int $offset + * @param int $orderBy + * @param string $orderType + * @throws \greeny\MailLibrary\DriverException + * @return array of UIDs + */ + public function getMailIds(array $filters, $limit = 0, $offset = 0, $orderBy = Mail::ORDER_DATE, $orderType = 'ASC') + { + $filter = $this->buildFilters($filters); + + $orderType = $orderType === 'ASC' ? 1 : 0; + + if(!is_array($ids = imap_sort($this->resource, $orderBy, $orderType, SE_UID | SE_NOPREFETCH, $filter, 'UTF-8'))) { + throw new DriverException("Cannot get mails: " . imap_last_error()); + } + + return $limit === 0 ? $ids : array_slice($ids, $offset, $limit); + } + + /** + * Checks if filter is applicable for this driver + * + * @param string $key + * @param mixed $value + * @throws DriverException + */ + public function checkFilter($key, $value = NULL) { + if(!in_array($key, array_keys(self::$filterTable))) { + throw new DriverException("Invalid filter key '$key'."); + } + $filtered = self::$filterTable[$key]; + if(strpos($filtered, '%s') !== FALSE) { + if(!is_string($value)) { + throw new DriverException("Invalid value type for filter '$key', expected string, got ".gettype($value)."."); + } + } else if(strpos($filtered, '%d') !== FALSE) { + if(!($value instanceof DateTime) && !is_int($value) && !strtotime($value)) { + throw new DriverException("Invalid value type for filter '$key', expected DateTime or timestamp, or textual representation of date, got ".gettype($value)."."); + } + } else if(strpos($filtered, '%b') !== FALSE) { + if(!is_bool($value)) { + throw new DriverException("Invalid value type for filter '$key', expected bool, got ".gettype($value)."."); + } + } else if($value !== NULL) { + throw new DriverException("Cannot assign value to filter '$key'."); + } + } + + /** + * Gets mail headers + * + * @param int $mailId + * @return array of name => value + */ public function getHeaders($mailId) { $raw = imap_fetchheader($this->resource, $mailId, FT_UID); @@ -271,190 +271,195 @@ public function mimeDecode($str) return preg_replace_callback('/=\?[^?]+\?[A-Z]\?[^?]+\?=/', array($this, 'mimeDecodeReplaceCallback'), $str); } - /** - * Creates structure for mail - * - * @param int $mailId - * @param Mailbox $mailbox - * @return IStructure - */ - public function getStructure($mailId, Mailbox $mailbox) - { - return new ImapStructure($this, imap_fetchstructure($this->resource, $mailId, FT_UID), $mailId, $mailbox); - } - - /** - * Gets part of body - * - * @param int $mailId - * @param array $data - * @return string - */ - public function getBody($mailId, array $data) - { - $body = array(); - foreach($data as $part) { - $data = ($part['id'] == 0) ? imap_body($this->resource, $mailId, FT_UID | FT_PEEK) : imap_fetchbody($this->resource, $mailId, $part['id'], FT_UID | FT_PEEK); - $encoding = $part['encoding']; - if($encoding === ImapStructure::ENCODING_BASE64) { - $data = base64_decode($data); - } else if($encoding === ImapStructure::ENCODING_QUOTED_PRINTABLE) { - $data = quoted_printable_decode($data); - } - - $body[] = $data; - } - return implode('\n\n', $body); - } - - /** - * Gets flags for mail - * - * @param $mailId - * @return array - */ - public function getFlags($mailId) - { - $data = imap_fetch_overview($this->resource, (string)$mailId, FT_UID); - reset($data); - $data = current($data); - $return = array( - Mail::FLAG_ANSWERED => FALSE, - Mail::FLAG_DELETED => FALSE, - Mail::FLAG_DRAFT => FALSE, - Mail::FLAG_FLAGGED => FALSE, - Mail::FLAG_SEEN => FALSE, - ); - if($data->answered) { - $return[Mail::FLAG_ANSWERED] = TRUE; - } - if($data->deleted) { - $return[Mail::FLAG_DELETED] = TRUE; - } - if($data->draft) { - $return[Mail::FLAG_DRAFT] = TRUE; - } - if($data->flagged) { - $return[Mail::FLAG_FLAGGED] = TRUE; - } - if($data->seen) { - $return[Mail::FLAG_SEEN] = TRUE; - } - return $return; - } - - /** - * Sets one flag for mail - * - * @param int $mailId - * @param string $flag - * @param bool $value - * @throws DriverException - */ - public function setFlag($mailId, $flag, $value) - { - if($value) { - if(!imap_setflag_full($this->resource, $mailId, $flag, ST_UID)) { - throw new DriverException("Cannot set flag '$flag': ".imap_last_error()); - } - } else { - if(!imap_clearflag_full($this->resource, $mailId, $flag, ST_UID)) { - throw new DriverException("Cannot unset flag '$flag': ".imap_last_error()); - } - } - } - - /** - * Copies mail to another mailbox - * @param int $mailId - * @param string $toMailbox - * @throws DriverException - */ - public function copyMail($mailId, $toMailbox) { - if(!imap_mail_copy($this->resource, $mailId, $this->server . $this->encodeMailboxName($toMailbox), CP_UID)) { - throw new DriverException("Cannot copy mail to mailbox '$toMailbox': ".imap_last_error()); - } - } - - /** - * Moves mail to another mailbox - * @param int $mailId - * @param string $toMailbox - * @throws DriverException - */ - public function moveMail($mailId, $toMailbox) { - if(!imap_mail_move($this->resource, $mailId, $this->server . $this->encodeMailboxName($toMailbox), CP_UID)) { - throw new DriverException("Cannot copy mail to mailbox '$toMailbox': ".imap_last_error()); - } - } - - /** - * Deletes mail - * @param int $mailId - * @throws DriverException - */ - public function deleteMail($mailId) { - if(!imap_delete($this->resource, $mailId, FT_UID)) { - throw new DriverException("Cannot delete mail: ".imap_last_error()); - } - } - - /** - * Builds filter string from filters - * - * @param array $filters - * @return string - */ - protected function buildFilters(array $filters) - { - $return = array(); - foreach($filters as $filter) { - $key = self::$filterTable[$filter['key']]; - $value = $filter['value']; - - if(strpos($key, '%s') !== FALSE) { - $data = str_replace('%s', str_replace('"', '', (string)$value), $key); - } else if(strpos($key, '%d') !== FALSE) { - if($value instanceof DateTime) { - $timestamp = $value->getTimestamp(); - } else if(is_string($value)) { - $timestamp = strtotime($value) ?: Time(); - } else { - $timestamp = (int)$value; - } - $data = str_replace('%d', date("d M Y", $timestamp), $key); - } else if(strpos($key, '%b') !== FALSE) { - $data = str_replace('%b', ((bool)$value ? '' : 'UN'), $key); - } else { - $data = $key; - } - $return[] = $data; - } - return implode(' ', $return); - } - - /** - * Builds list from ids array - * - * @param array $ids - * @return string - */ - protected function buildIdList(array $ids) - { - sort($ids); - return implode(',', $ids); - } - - /** - * Converts mailbox name encoding as defined in IMAP RFC 2060. - * - * @param $name - * @return string - */ - protected function encodeMailboxName($name) - { - return mb_convert_encoding($name, 'UTF7-IMAP', 'UTF-8'); - } + /** + * Creates structure for mail + * + * @param int $mailId + * @param Mailbox $mailbox + * @return IStructure + */ + public function getStructure($mailId, Mailbox $mailbox) + { + return new ImapStructure($this, imap_fetchstructure($this->resource, $mailId, FT_UID), $mailId, $mailbox); + } + + /** + * Gets part of body + * + * @param int $mailId + * @param array $data + * @return string + */ + public function getBody($mailId, array $data) + { + $body = array(); + foreach($data as $part) { + $data = ($part['id'] == 0) ? imap_body($this->resource, $mailId, FT_UID | FT_PEEK) : imap_fetchbody($this->resource, $mailId, $part['id'], FT_UID | FT_PEEK); + $encoding = $part['encoding']; + if($encoding === ImapStructure::ENCODING_BASE64) { + $data = base64_decode($data); + } else if($encoding === ImapStructure::ENCODING_QUOTED_PRINTABLE) { + $data = quoted_printable_decode($data); + } + + $body[] = $data; + } + return implode('\n\n', $body); + } + + /** + * Gets flags for mail + * + * @param $mailId + * @return array + */ + public function getFlags($mailId) + { + $data = imap_fetch_overview($this->resource, (string)$mailId, FT_UID); + reset($data); + $data = current($data); + $return = array( + Mail::FLAG_ANSWERED => FALSE, + Mail::FLAG_DELETED => FALSE, + Mail::FLAG_DRAFT => FALSE, + Mail::FLAG_FLAGGED => FALSE, + Mail::FLAG_SEEN => FALSE, + ); + if($data->answered) { + $return[Mail::FLAG_ANSWERED] = TRUE; + } + if($data->deleted) { + $return[Mail::FLAG_DELETED] = TRUE; + } + if($data->draft) { + $return[Mail::FLAG_DRAFT] = TRUE; + } + if($data->flagged) { + $return[Mail::FLAG_FLAGGED] = TRUE; + } + if($data->seen) { + $return[Mail::FLAG_SEEN] = TRUE; + } + return $return; + } + + /** + * Sets one flag for mail + * + * @param int $mailId + * @param string $flag + * @param bool $value + * @throws DriverException + */ + public function setFlag($mailId, $flag, $value) + { + if($value) { + if(!imap_setflag_full($this->resource, $mailId, $flag, ST_UID)) { + throw new DriverException("Cannot set flag '$flag': ".imap_last_error()); + } + } else { + if(!imap_clearflag_full($this->resource, $mailId, $flag, ST_UID)) { + throw new DriverException("Cannot unset flag '$flag': ".imap_last_error()); + } + } + } + + /** + * Copies mail to another mailbox + * @param int $mailId + * @param string $toMailbox + * @throws DriverException + */ + public function copyMail($mailId, $toMailbox) { + if(!imap_mail_copy($this->resource, $mailId, $this->server . $this->encodeMailboxName($toMailbox), CP_UID)) { + throw new DriverException("Cannot copy mail to mailbox '$toMailbox': ".imap_last_error()); + } + } + + /** + * Moves mail to another mailbox + * @param int $mailId + * @param string $toMailbox + * @throws DriverException + */ + public function moveMail($mailId, $toMailbox) { + if(!imap_mail_move($this->resource, $mailId, $this->server . $this->encodeMailboxName($toMailbox), CP_UID)) { + throw new DriverException("Cannot copy mail to mailbox '$toMailbox': ".imap_last_error()); + } + } + + /** + * Deletes mail + * @param int $mailId + * @throws DriverException + */ + public function deleteMail($mailId) { + if(!imap_delete($this->resource, $mailId, FT_UID)) { + throw new DriverException("Cannot delete mail: ".imap_last_error()); + } + } + + public function getOverview($mailId) + { + return current(imap_fetch_overview($this->resource, $mailId, FT_UID)); + } + + /** + * Builds filter string from filters + * + * @param array $filters + * @return string + */ + protected function buildFilters(array $filters) + { + $return = array(); + foreach($filters as $filter) { + $key = self::$filterTable[$filter['key']]; + $value = $filter['value']; + + if(strpos($key, '%s') !== FALSE) { + $data = str_replace('%s', str_replace('"', '', (string)$value), $key); + } else if(strpos($key, '%d') !== FALSE) { + if($value instanceof DateTime) { + $timestamp = $value->getTimestamp(); + } else if(is_string($value)) { + $timestamp = strtotime($value) ?: Time(); + } else { + $timestamp = (int)$value; + } + $data = str_replace('%d', date("d M Y", $timestamp), $key); + } else if(strpos($key, '%b') !== FALSE) { + $data = str_replace('%b', ((bool)$value ? '' : 'UN'), $key); + } else { + $data = $key; + } + $return[] = $data; + } + return implode(' ', $return); + } + + /** + * Builds list from ids array + * + * @param array $ids + * @return string + */ + protected function buildIdList(array $ids) + { + sort($ids); + return implode(',', $ids); + } + + /** + * Converts mailbox name encoding as defined in IMAP RFC 2060. + * + * @param $name + * @return string + */ + protected function encodeMailboxName($name) + { + return mb_convert_encoding($name, 'UTF7-IMAP', 'UTF-8'); + } /** * @param array $m diff --git a/MailLibrary/Mail.php b/MailLibrary/Mail.php index 0431948..6683556 100644 --- a/MailLibrary/Mail.php +++ b/MailLibrary/Mail.php @@ -6,256 +6,271 @@ namespace greeny\MailLibrary; class Mail { - const ANSWERED = 'ANSWERED'; - const BCC = 'BCC'; - const BEFORE = 'BEFORE'; - const BODY = 'BODY'; - const CC = 'CC'; - const DELETED = 'DELETED'; - const FLAGGED = 'FLAGGED'; - const FROM = 'FROM'; - const KEYWORD = 'KEYWORD'; - const NEW_MESSAGES = 'NEW'; - const NOT_KEYWORD = 'UNKEYWORD'; - const OLD_MESSAGES = 'OLD'; - const ON = 'ON'; - const RECENT = 'RECENT'; - const SEEN = 'SEEN'; - const SINCE = 'SINCE'; - const SUBJECT = 'SUBJECT'; - const TEXT = 'TEXT'; - const TO = 'TO'; - - const FLAG_ANSWERED = "\\ANSWERED"; - const FLAG_DELETED = "\\DELETED"; - const FLAG_DRAFT = "\\DRAFT"; - const FLAG_FLAGGED = "\\FLAGGED"; - const FLAG_SEEN = "\\SEEN"; - - const ORDER_DATE = SORTARRIVAL; - const ORDER_FROM = SORTFROM; - const ORDER_SUBJECT = SORTSUBJECT; - const ORDER_TO = SORTTO; - const ORDER_CC = SORTCC; - const ORDER_SIZE = SORTSIZE; - - /** @var \greeny\MailLibrary\Connection */ - protected $connection; - - /** @var \greeny\MailLibrary\Mailbox */ - protected $mailbox; - - /** @var int */ - protected $id; - - /** @var array */ - protected $headers = NULL; - - /** @var \greeny\MailLibrary\Structures\IStructure */ - protected $structure = NULL; - - /** @var array */ - protected $flags = NULL; - - /** - * @param Connection $connection - * @param Mailbox $mailbox - * @param int $id - */ - public function __construct(Connection $connection, Mailbox $mailbox, $id) - { - $this->connection = $connection; - $this->mailbox = $mailbox; - $this->id = $id; - } - - /** - * Header checker - * - * @param $name - * @return bool - */ - public function __isset($name) - { - $this->headers !== NULL || $this->initializeHeaders(); - return isset($this->headers[$this->formatHeaderName($name)]); - } - - /** - * Header getter - * - * @param string $name - * @return mixed - */ - public function __get($name) - { - return $this->getHeader($name); - } - - /** - * @return int - */ - public function getId() - { - return $this->id; - } - - /** - * @return Mailbox - */ - public function getMailbox() - { - return $this->mailbox; - } - - /** - * @return string[] - */ - public function getHeaders() - { - $this->headers !== NULL || $this->initializeHeaders(); - return $this->headers; - } - - /** - * @param string $name - * @return string - */ - public function getHeader($name) - { - $this->headers !== NULL || $this->initializeHeaders(); - return $this->headers[$this->formatHeaderName($name)]; - } - - /** - * @return Contact|null - */ - public function getSender() { - $from = $this->getHeader('from'); - if($from) { - $contacts = $from->getContactsObjects(); - return (count($contacts) ? $contacts[0] : NULL); - } else { - return NULL; - } - } - - /** - * @return string - */ - public function getBody() - { - $this->structure !== NULL || $this->initializeStructure(); - return $this->structure->getBody(); - } - - /** - * @return string - */ - public function getHtmlBody() - { - $this->structure !== NULL || $this->initializeStructure(); - return $this->structure->getHtmlBody(); - } - - /** - * @return string - */ - public function getTextBody() - { - $this->structure !== NULL || $this->initializeStructure(); - return $this->structure->getTextBody(); - } - - /** - * @return Attachment[] - */ - public function getAttachments() - { - $this->structure !== NULL || $this->initializeStructure(); - return $this->structure->getAttachments(); - } - - /** - * @return array - */ - public function getFlags() - { - $this->flags !== NULL || $this->initializeFlags(); - return $this->flags; - } - - public function setFlags(array $flags, $autoFlush = FALSE) - { - $this->connection->getDriver()->switchMailbox($this->mailbox->getName()); - foreach(array( - Mail::FLAG_ANSWERED, - Mail::FLAG_DELETED, - Mail::FLAG_DELETED, - Mail::FLAG_FLAGGED, - Mail::FLAG_SEEN, - ) as $flag) { - if(isset($flags[$flag])) { - $this->connection->getDriver()->setFlag($this->id, $flag, $flags[$flag]); - } - } - if($autoFlush) { - $this->connection->getDriver()->flush(); - } - } - - public function move($toMailbox) - { - $this->connection->getDriver()->switchMailbox($this->mailbox->getName()); - $this->connection->getDriver()->moveMail($this->id, $toMailbox); - } - - public function copy($toMailbox) - { - $this->connection->getDriver()->switchMailbox($this->mailbox->getName()); - $this->connection->getDriver()->copyMail($this->id, $toMailbox); - } - - public function delete() - { - $this->connection->getDriver()->switchMailbox($this->mailbox->getName()); - $this->connection->getDriver()->deleteMail($this->id); - } - - /** - * Initializes headers - */ - protected function initializeHeaders() - { - $this->headers = array(); - $this->connection->getDriver()->switchMailbox($this->mailbox->getName()); - foreach($this->connection->getDriver()->getHeaders($this->id) as $key => $value) { - $this->headers[$this->formatHeaderName($key)] = $value; - } - } - - protected function initializeStructure() - { - $this->connection->getDriver()->switchMailbox($this->mailbox->getName()); - $this->structure = $this->connection->getDriver()->getStructure($this->id, $this->mailbox); - } - - protected function initializeFlags() - { - $this->connection->getDriver()->switchMailbox($this->mailbox->getName()); - $this->flags = $this->connection->getDriver()->getFlags($this->id); - } - - /** - * Formats header name (X-Received-From => xReceivedFrom) - * - * @param string $name - * @return string - */ - protected function formatHeaderName($name) - { - return lcfirst(preg_replace_callback("~-.~", function($matches){ - return ucfirst(substr($matches[0], 1)); - }, $name)); - } + const ANSWERED = 'ANSWERED'; + const BCC = 'BCC'; + const BEFORE = 'BEFORE'; + const BODY = 'BODY'; + const CC = 'CC'; + const DELETED = 'DELETED'; + const FLAGGED = 'FLAGGED'; + const FROM = 'FROM'; + const KEYWORD = 'KEYWORD'; + const NEW_MESSAGES = 'NEW'; + const NOT_KEYWORD = 'UNKEYWORD'; + const OLD_MESSAGES = 'OLD'; + const ON = 'ON'; + const RECENT = 'RECENT'; + const SEEN = 'SEEN'; + const SINCE = 'SINCE'; + const SUBJECT = 'SUBJECT'; + const TEXT = 'TEXT'; + const TO = 'TO'; + + const FLAG_ANSWERED = "\\ANSWERED"; + const FLAG_DELETED = "\\DELETED"; + const FLAG_DRAFT = "\\DRAFT"; + const FLAG_FLAGGED = "\\FLAGGED"; + const FLAG_SEEN = "\\SEEN"; + + const ORDER_DATE = SORTARRIVAL; + const ORDER_FROM = SORTFROM; + const ORDER_SUBJECT = SORTSUBJECT; + const ORDER_TO = SORTTO; + const ORDER_CC = SORTCC; + const ORDER_SIZE = SORTSIZE; + + /** @var \greeny\MailLibrary\Connection */ + protected $connection; + + /** @var \greeny\MailLibrary\Mailbox */ + protected $mailbox; + + /** @var int */ + protected $id; + + /** @var array */ + protected $headers = NULL; + + /** @var \greeny\MailLibrary\Structures\IStructure */ + protected $structure = NULL; + + /** @var array */ + protected $flags = NULL; + + /** + * @var array + */ + private $overview; + + /** + * @param Connection $connection + * @param Mailbox $mailbox + * @param int $id + */ + public function __construct(Connection $connection, Mailbox $mailbox, $id) + { + $this->connection = $connection; + $this->mailbox = $mailbox; + $this->id = $id; + + $this->overview = $this->connection->getDriver()->getOverview($id); + } + + /** + * @return \DateTimeImmutable|false + */ + public function getDate() + { + return (new \DateTimeImmutable())->setTimestamp($this->overview->udate); + } + + /** + * Header checker + * + * @param $name + * @return bool + */ + public function __isset($name) + { + $this->headers !== NULL || $this->initializeHeaders(); + return isset($this->headers[$this->formatHeaderName($name)]); + } + + /** + * Header getter + * + * @param string $name + * @return mixed + */ + public function __get($name) + { + return $this->getHeader($name); + } + + /** + * @return int + */ + public function getId() + { + return $this->id; + } + + /** + * @return Mailbox + */ + public function getMailbox() + { + return $this->mailbox; + } + + /** + * @return string[] + */ + public function getHeaders() + { + $this->headers !== NULL || $this->initializeHeaders(); + return $this->headers; + } + + /** + * @param string $name + * @return string + */ + public function getHeader($name) + { + $this->headers !== NULL || $this->initializeHeaders(); + return $this->headers[$this->formatHeaderName($name)]; + } + + /** + * @return Contact|null + */ + public function getSender() { + $from = $this->getHeader('from'); + if($from) { + $contacts = $from->getContactsObjects(); + return (count($contacts) ? $contacts[0] : NULL); + } else { + return NULL; + } + } + + /** + * @return string + */ + public function getBody() + { + $this->structure !== NULL || $this->initializeStructure(); + return $this->structure->getBody(); + } + + /** + * @return string + */ + public function getHtmlBody() + { + $this->structure !== NULL || $this->initializeStructure(); + return $this->structure->getHtmlBody(); + } + + /** + * @return string + */ + public function getTextBody() + { + $this->structure !== NULL || $this->initializeStructure(); + return $this->structure->getTextBody(); + } + + /** + * @return Attachment[] + */ + public function getAttachments() + { + $this->structure !== NULL || $this->initializeStructure(); + return $this->structure->getAttachments(); + } + + /** + * @return array + */ + public function getFlags() + { + $this->flags !== NULL || $this->initializeFlags(); + return $this->flags; + } + + public function setFlags(array $flags, $autoFlush = FALSE) + { + $this->connection->getDriver()->switchMailbox($this->mailbox->getName()); + foreach(array( + Mail::FLAG_ANSWERED, + Mail::FLAG_DELETED, + Mail::FLAG_DELETED, + Mail::FLAG_FLAGGED, + Mail::FLAG_SEEN, + ) as $flag) { + if(isset($flags[$flag])) { + $this->connection->getDriver()->setFlag($this->id, $flag, $flags[$flag]); + } + } + if($autoFlush) { + $this->connection->getDriver()->flush(); + } + } + + public function move($toMailbox) + { + $this->connection->getDriver()->switchMailbox($this->mailbox->getName()); + $this->connection->getDriver()->moveMail($this->id, $toMailbox); + } + + public function copy($toMailbox) + { + $this->connection->getDriver()->switchMailbox($this->mailbox->getName()); + $this->connection->getDriver()->copyMail($this->id, $toMailbox); + } + + public function delete() + { + $this->connection->getDriver()->switchMailbox($this->mailbox->getName()); + $this->connection->getDriver()->deleteMail($this->id); + } + + /** + * Initializes headers + */ + protected function initializeHeaders() + { + $this->headers = array(); + $this->connection->getDriver()->switchMailbox($this->mailbox->getName()); + foreach($this->connection->getDriver()->getHeaders($this->id) as $key => $value) { + $this->headers[$this->formatHeaderName($key)] = $value; + } + } + + protected function initializeStructure() + { + $this->connection->getDriver()->switchMailbox($this->mailbox->getName()); + $this->structure = $this->connection->getDriver()->getStructure($this->id, $this->mailbox); + } + + protected function initializeFlags() + { + $this->connection->getDriver()->switchMailbox($this->mailbox->getName()); + $this->flags = $this->connection->getDriver()->getFlags($this->id); + } + + /** + * Formats header name (X-Received-From => xReceivedFrom) + * + * @param string $name + * @return string + */ + protected function formatHeaderName($name) + { + return lcfirst(preg_replace_callback("~-.~", function($matches){ + return ucfirst(substr($matches[0], 1)); + }, $name)); + } } From aa681e9e04403e14fca888a4cd505585f56c7e5b Mon Sep 17 00:00:00 2001 From: Arthur Aivazov Date: Wed, 6 May 2020 21:33:50 +0300 Subject: [PATCH 09/12] Add method Mail::getSubject() --- MailLibrary/Drivers/IDriver.php | 6 ++++++ MailLibrary/Mail.php | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/MailLibrary/Drivers/IDriver.php b/MailLibrary/Drivers/IDriver.php index 6b7420e..4b8fb3b 100644 --- a/MailLibrary/Drivers/IDriver.php +++ b/MailLibrary/Drivers/IDriver.php @@ -146,4 +146,10 @@ function deleteMail($mailId); * @return array */ function getOverview($mailId); + + /** + * @param $str + * @return string + */ + function mimeDecode($str); } \ No newline at end of file diff --git a/MailLibrary/Mail.php b/MailLibrary/Mail.php index 6683556..e11fdb7 100644 --- a/MailLibrary/Mail.php +++ b/MailLibrary/Mail.php @@ -84,6 +84,18 @@ public function getDate() return (new \DateTimeImmutable())->setTimestamp($this->overview->udate); } + /** + * @return string|null + */ + public function getSubject() + { + if(isset($this->overview->subject)) { + return $this->connection->getDriver()->mimeDecode($this->overview->subject); + } + + return null; + } + /** * Header checker * From 5014c49bd67492faf8176771fd61e92ce4a71406 Mon Sep 17 00:00:00 2001 From: Arthur Aivazov Date: Thu, 7 May 2020 21:36:37 +0300 Subject: [PATCH 10/12] BugFix ImapDriver::mimeDecode() --- MailLibrary/Drivers/ImapDriver.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MailLibrary/Drivers/ImapDriver.php b/MailLibrary/Drivers/ImapDriver.php index 81d3b00..13593f8 100644 --- a/MailLibrary/Drivers/ImapDriver.php +++ b/MailLibrary/Drivers/ImapDriver.php @@ -268,7 +268,7 @@ public function mimeDecode($str) $str = preg_replace('/(=\?[^?]+\?[A-Z]\?[^?]+\?=)\s+(?=(=\?[^?]+\?[A-Z]\?[^?]+\?=))/', '$1', $str); // =?UTF-8?Q?=D0=9A=D0=BE=D0=BC=D0=B0=D0=BD=D0=B4=D0=B0?= - return preg_replace_callback('/=\?[^?]+\?[A-Z]\?[^?]+\?=/', array($this, 'mimeDecodeReplaceCallback'), $str); + return preg_replace_callback('/=\?[^?]+\?[A-z]\?[^?]+\?=/', array($this, 'mimeDecodeReplaceCallback'), $str); } /** From 3f51000a8b7ffb73f3adf9b609fc53aa0e363f01 Mon Sep 17 00:00:00 2001 From: Arthur Aivazov Date: Thu, 7 May 2020 21:43:18 +0300 Subject: [PATCH 11/12] Change package name --- composer.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 48ae6c1..ae18057 100644 --- a/composer.json +++ b/composer.json @@ -1,5 +1,5 @@ { - "name": "php-mail-client/client", + "name": "aivazoff/php-mail-client", "description": "Full featured PHP mail client. Create, edit, delete, move and set flags to messages with ease! Drivers for IMAP4 and Microsoft Exchange Web Services available.", "license": "MIT", "authors": [ @@ -7,6 +7,10 @@ "name": "Tomáš Blatný", "email": "blatny.tomas@seznam.cz", "homepage": "http://tomasblatny.eu" + }, { + "name": "Arthur Aivazov", + "email": "arthur.aivazoff@gmail.com", + "homepage": "https://github.com/aivazoff" } ], "autoload": { From 2404fd3126b4ab6364366bc8aa90d84bdb867ee2 Mon Sep 17 00:00:00 2001 From: Arthur Aivazov Date: Fri, 8 May 2020 19:22:08 +0300 Subject: [PATCH 12/12] ImapDriver::__destruct() --- MailLibrary/Connection.php | 5 ++-- MailLibrary/Drivers/ImapDriver.php | 37 ++++++++++++++++++++++++------ MailLibrary/Mail.php | 9 +++++--- 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/MailLibrary/Connection.php b/MailLibrary/Connection.php index 190cb18..22ac3d4 100644 --- a/MailLibrary/Connection.php +++ b/MailLibrary/Connection.php @@ -7,8 +7,9 @@ use greeny\MailLibrary\Drivers\IDriver; -class Connection { - /** @var \greeny\MailLibrary\Drivers\IDriver */ +class Connection +{ + /** @var IDriver */ protected $driver; /** @var bool */ diff --git a/MailLibrary/Drivers/ImapDriver.php b/MailLibrary/Drivers/ImapDriver.php index 13593f8..64520de 100644 --- a/MailLibrary/Drivers/ImapDriver.php +++ b/MailLibrary/Drivers/ImapDriver.php @@ -173,7 +173,7 @@ public function switchMailbox($name) * @param int $offset * @param int $orderBy * @param string $orderType - * @throws \greeny\MailLibrary\DriverException + * @throws DriverException * @return array of UIDs */ public function getMailIds(array $filters, $limit = 0, $offset = 0, $orderBy = Mail::ORDER_DATE, $orderType = 'ASC') @@ -294,12 +294,29 @@ public function getBody($mailId, array $data) { $body = array(); foreach($data as $part) { - $data = ($part['id'] == 0) ? imap_body($this->resource, $mailId, FT_UID | FT_PEEK) : imap_fetchbody($this->resource, $mailId, $part['id'], FT_UID | FT_PEEK); - $encoding = $part['encoding']; - if($encoding === ImapStructure::ENCODING_BASE64) { - $data = base64_decode($data); - } else if($encoding === ImapStructure::ENCODING_QUOTED_PRINTABLE) { - $data = quoted_printable_decode($data); + $data = ($part['id'] == 0) + ? imap_body($this->resource, $mailId, FT_UID | FT_PEEK) + : imap_fetchbody($this->resource, $mailId, $part['id'], FT_UID | FT_PEEK); + + switch($part['encoding']) + { + case(1): + if(preg_match_all('/=[\dA-Z]{2}=/', $data) > 10) { + $data = quoted_printable_decode($data); + } + break; + + case(2): + $data = imap_binary($data); + break; + + case(ImapStructure::ENCODING_BASE64): + $data = base64_decode($data); + break; + + case(ImapStructure::ENCODING_QUOTED_PRINTABLE): + $data = quoted_printable_decode($data); + break; } $body[] = $data; @@ -404,6 +421,12 @@ public function getOverview($mailId) return current(imap_fetch_overview($this->resource, $mailId, FT_UID)); } + public function __destruct() + { + imap_errors(); + } + + /** * Builds filter string from filters * diff --git a/MailLibrary/Mail.php b/MailLibrary/Mail.php index e11fdb7..50e4d68 100644 --- a/MailLibrary/Mail.php +++ b/MailLibrary/Mail.php @@ -5,6 +5,8 @@ namespace greeny\MailLibrary; +use greeny\MailLibrary\Structures\IStructure; + class Mail { const ANSWERED = 'ANSWERED'; const BCC = 'BCC'; @@ -39,10 +41,10 @@ class Mail { const ORDER_CC = SORTCC; const ORDER_SIZE = SORTSIZE; - /** @var \greeny\MailLibrary\Connection */ + /** @var Connection */ protected $connection; - /** @var \greeny\MailLibrary\Mailbox */ + /** @var Mailbox */ protected $mailbox; /** @var int */ @@ -51,7 +53,7 @@ class Mail { /** @var array */ protected $headers = NULL; - /** @var \greeny\MailLibrary\Structures\IStructure */ + /** @var IStructure */ protected $structure = NULL; /** @var array */ @@ -158,6 +160,7 @@ public function getHeader($name) * @return Contact|null */ public function getSender() { + /** @var ContactList $from */ $from = $this->getHeader('from'); if($from) { $contacts = $from->getContactsObjects();