diff --git a/.php_cs.dist b/.php_cs.dist index c2827a2b..8afdc144 100644 --- a/.php_cs.dist +++ b/.php_cs.dist @@ -1,38 +1,43 @@ setRules([ - '@Symfony' => true, - '@Symfony:risky' => true, - 'array_syntax' => [ - 'syntax' => 'short' - ], - 'ordered_imports' => true, - 'phpdoc_to_comment' => false, - 'no_superfluous_phpdoc_tags' => true, - 'declare_strict_types' => true, - 'void_return' => true, - 'ordered_class_elements' => true, - 'global_namespace_import' => [ - 'import_classes' => true, - 'import_constants' => true, - 'import_functions' => false, - ], - 'native_constant_invocation' => true, - 'native_function_invocation' => true, - 'php_unit_test_case_static_method_calls' => [ - 'call_type' => 'this', - ], - 'php_unit_method_casing' => true, - 'php_unit_dedicate_assert' => [ - 'target' => 'newest', - ], - ]) +declare(strict_types=1); + +$finder = PhpCsFixer\Finder::create() + ->in(__DIR__.'/src') + ->in(__DIR__.'/tests') + ->in(__DIR__.'/examples') + ->append([__FILE__]); + +$config = new PhpCsFixer\Config(); +$config->setRules([ + '@Symfony' => true, + '@Symfony:risky' => true, + 'array_syntax' => [ + 'syntax' => 'short', + ], + 'ordered_imports' => true, + 'phpdoc_to_comment' => false, + 'no_superfluous_phpdoc_tags' => true, + 'declare_strict_types' => true, + 'void_return' => true, + 'ordered_class_elements' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => false, + ], + 'native_constant_invocation' => true, + 'native_function_invocation' => true, + 'php_unit_test_case_static_method_calls' => [ + 'call_type' => 'this', + ], + 'php_unit_method_casing' => true, + 'php_unit_dedicate_assert' => [ + 'target' => 'newest', + ], +]) ->setRiskyAllowed(true) - ->setFinder( - PhpCsFixer\Finder::create() - ->in(__DIR__.'/src') - ->in(__DIR__.'/tests') - ->in(__DIR__.'/examples') - ) + ->setFinder($finder) ; + +return $config; diff --git a/README.md b/README.md index d97e108d..66be67d2 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ Initially released in December 2012, the PHP IMAP Mailbox is a powerful and open | 7.2 | 3.x, 4.x | | 7.3 | 3.x, 4.x | | 7.4 | >3.0.33, 4.x | +| 8.0 | >3.0.33, 4.x | * PHP `fileinfo` extension must be present; so make sure this line is active in your php.ini: `extension=php_fileinfo.dll` * PHP `iconv` extension must be present; so make sure this line is active in your php.ini: `extension=php_iconv.dll` @@ -121,12 +122,12 @@ echo "\n\nAttachments:\n"; print_r($mail->getAttachments()); ``` -Method imap() allows to call any imap function in a context of the the instance: +Method `imap()` allows to call any [PHP IMAP function](https://www.php.net/manual/ref.imap.php) in a context of the instance. Example: ```php -// Call imap_check(); -// http://php.net/manual/en/function.imap-check.php -$info = $mailbox->imap('check'); // +// Call imap_check() - see http://php.net/manual/function.imap-check.php +$info = $mailbox->imap('check'); + // Show current time for the mailbox $currentServerTime = isset($info->Date) && $info->Date ? date('Y-m-d H:i:s', strtotime($info->Date)) : 'Unknown'; @@ -156,6 +157,30 @@ foreach($folders as $folder) { print_r($mails_ids); ``` +### Upgrading from 3.x + +Prior to 3.1, `Mailbox` used a "magic" method (`Mailbox::imap()`), with the +class `Imap` now performing it's purpose to call many `imap_*` functions with +automated string encoding/decoding of arguments and return values: + +Before: + +```php + public function checkMailbox() + { + return $this->imap('check'); + } +``` + +After: + +```php + public function checkMailbox(): object + { + return Imap::check($this->getImapStream()); + } +``` + ### Recommended * Google Chrome extension [PHP Console](https://chrome.google.com/webstore/detail/php-console/nfhmhhlpfleoednkpnnnkolmclajemef) diff --git a/composer.json b/composer.json index 14603fe2..32595e2a 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "sort-packages": true }, "require": { - "php": "^7.2", + "php": "^7.2 || ^8.0 <8.1", "ext-fileinfo": "*", "ext-iconv": "*", "ext-imap": "*", @@ -33,13 +33,14 @@ "friendsofphp/php-cs-fixer": "^2.16", "jakub-onderka/php-parallel-lint": "^1.0", "maglnet/composer-require-checker": "^2.0", + "nikic/php-parser": "^4.3,<4.7", "paragonie/hidden-string": "^1.0", "phpunit/phpunit": "^8.5", "povils/phpmnd": "^2.2", "psalm/plugin-phpunit": "^0.10.0", "roave/security-advisories": "dev-master", "sebastian/phpcpd": "^4.1", - "vimeo/psalm": "^3.11.5" + "vimeo/psalm": "^3.12" }, "scripts": { "static-analysis": [ diff --git a/examples/get_and_parse_all_emails_with_matching_subject.php b/examples/get_and_parse_all_emails_with_matching_subject.php index 7cb8e52b..5ad13361 100644 --- a/examples/get_and_parse_all_emails_with_matching_subject.php +++ b/examples/get_and_parse_all_emails_with_matching_subject.php @@ -23,9 +23,9 @@ try { $mail_ids = $mailbox->searchMailbox('SUBJECT "part of the subject"'); } catch (ConnectionException $ex) { - die('IMAP connection failed: '.$ex->getMessage()); + exit('IMAP connection failed: '.$ex->getMessage()); } catch (Exception $ex) { - die('An error occured: '.$ex->getMessage()); + exit('An error occured: '.$ex->getMessage()); } foreach ($mail_ids as $mail_id) { diff --git a/examples/get_and_parse_all_emails_without_saving_attachments.php b/examples/get_and_parse_all_emails_without_saving_attachments.php index 6913edb6..b5db0886 100644 --- a/examples/get_and_parse_all_emails_without_saving_attachments.php +++ b/examples/get_and_parse_all_emails_without_saving_attachments.php @@ -33,9 +33,9 @@ try { $mail_ids = $mailbox->searchMailbox('UNSEEN'); } catch (ConnectionException $ex) { - die('IMAP connection failed: '.$ex->getMessage()); + exit('IMAP connection failed: '.$ex->getMessage()); } catch (Exception $ex) { - die('An error occured: '.$ex->getMessage()); + exit('An error occured: '.$ex->getMessage()); } foreach ($mail_ids as $mail_id) { diff --git a/examples/get_and_parse_unseen_emails.php b/examples/get_and_parse_unseen_emails.php index 76462366..1070ac31 100644 --- a/examples/get_and_parse_unseen_emails.php +++ b/examples/get_and_parse_unseen_emails.php @@ -23,9 +23,9 @@ try { $mail_ids = $mailbox->searchMailbox('UNSEEN'); } catch (ConnectionException $ex) { - die('IMAP connection failed: '.$ex->getMessage()); + exit('IMAP connection failed: '.$ex->getMessage()); } catch (Exception $ex) { - die('An error occured: '.$ex->getMessage()); + exit('An error occured: '.$ex->getMessage()); } foreach ($mail_ids as $mail_id) { diff --git a/examples/get_and_parse_unseen_emails_save_attachments_one_by_one.php b/examples/get_and_parse_unseen_emails_save_attachments_one_by_one.php index 86cd1fca..11192bf6 100644 --- a/examples/get_and_parse_unseen_emails_save_attachments_one_by_one.php +++ b/examples/get_and_parse_unseen_emails_save_attachments_one_by_one.php @@ -21,9 +21,9 @@ try { $mail_ids = $mailbox->searchMailbox('UNSEEN'); } catch (ConnectionException $ex) { - die('IMAP connection failed: '.$ex->getMessage()); + exit('IMAP connection failed: '.$ex->getMessage()); } catch (Exception $ex) { - die('An error occured: '.$ex->getMessage()); + exit('An error occured: '.$ex->getMessage()); } foreach ($mail_ids as $mail_id) { diff --git a/psalm.baseline.xml b/psalm.baseline.xml index 76fee147..12cc8fb8 100644 --- a/psalm.baseline.xml +++ b/psalm.baseline.xml @@ -1,5 +1,5 @@ - + $mailbox @@ -12,25 +12,14 @@ \is_resource($maybe) - - - replaceInternalLinks - - - $this->dataInfo - - \in_array($imapSearchOption, $supported_options, true) \in_array($key, $supported_params, true) - $element->charset $element->charset $element->text - $element->charset - $element->charset $element->text diff --git a/src/PhpImap/DataPartInfo.php b/src/PhpImap/DataPartInfo.php index b02c1c94..df491751 100644 --- a/src/PhpImap/DataPartInfo.php +++ b/src/PhpImap/DataPartInfo.php @@ -107,9 +107,10 @@ protected function decodeAfterFetch(): string protected function convertEncodingAfterFetch(): string { - if (isset($this->charset) and !empty(\trim($this->charset))) { + if (isset($this->charset) && !empty(\trim($this->charset))) { $this->data = $this->mail->decodeMimeStr( (string) $this->data // Data to convert + \trim($this->charset) ); } diff --git a/src/PhpImap/Imap.php b/src/PhpImap/Imap.php index 3762410f..4d1ac296 100644 --- a/src/PhpImap/Imap.php +++ b/src/PhpImap/Imap.php @@ -181,7 +181,6 @@ public static function clearflag_full( * @param false|resource $imap_stream * * @psalm-param value-of $flag - * @psalm-param 0|32768 $flag * * @return true */ @@ -189,6 +188,9 @@ public static function close($imap_stream, int $flag = 0): bool { \imap_errors(); // flush errors + /** @var int */ + $flag = $flag; + $result = \imap_close(self::EnsureConnection($imap_stream, __METHOD__, 1), $flag); if (false === $result) { @@ -699,12 +701,12 @@ public static function open( \imap_errors(); // flush errors - $result = \imap_open($mailbox, $username, $password, $options, $n_retries, $params); + $result = @\imap_open($mailbox, $username, $password, $options, $n_retries, $params); if (!$result) { $lastError = \imap_last_error(); - if ('' !== \trim($lastError)) { + if ((\is_string($lastError)) && ('' !== \trim($lastError))) { throw new UnexpectedValueException('IMAP error:'.$lastError); } @@ -890,7 +892,6 @@ public static function setflag_full( * @param false|resource $imap_stream * * @psalm-param value-of $criteria - * @psalm-param 1|5|0|2|6|3|4 $criteria * * @return int[] * @@ -909,6 +910,9 @@ public static function sort( $imap_stream = self::EnsureConnection($imap_stream, __METHOD__, 1); $reverse = (int) $reverse; + /** @var int */ + $criteria = $criteria; + if (null !== $search_criteria && null !== $charset) { $result = \imap_sort( $imap_stream, @@ -935,7 +939,7 @@ public static function sort( ); } - if (!$result) { + if (false === $result) { throw new UnexpectedValueException('Could not sort messages!', 0, self::HandleErrors(\imap_errors(), 'imap_sort')); } @@ -990,7 +994,6 @@ public static function subscribe( /** * @psalm-param value-of $timeout_type - * @psalm-param 4|1|2|3 $timeout_type * * @return true|int */ @@ -1000,6 +1003,9 @@ public static function timeout( ) { \imap_errors(); // flush errors + /** @var int */ + $timeout_type = $timeout_type; + $result = \imap_timeout( $timeout_type, $timeout diff --git a/src/PhpImap/IncomingMail.php b/src/PhpImap/IncomingMail.php index 02188357..1dae0593 100644 --- a/src/PhpImap/IncomingMail.php +++ b/src/PhpImap/IncomingMail.php @@ -219,7 +219,11 @@ public function embedImageAttachments(): void $cid = \str_replace('cid:', '', $match); foreach ($attachments as $attachment) { - if ($attachment->contentId == $cid && 'inline' == $attachment->disposition) { + /** + * Inline images can contain a "Content-Disposition: inline", but only a "Content-ID" is also enough. + * See https://github.com/barbushin/php-imap/issues/569 + */ + if ($attachment->contentId == $cid || 'inline' == \mb_strtolower((string) $attachment->disposition)) { $contents = $attachment->getContents(); $contentType = (string) $attachment->getFileInfo(FILEINFO_MIME); diff --git a/src/PhpImap/IncomingMailAttachment.php b/src/PhpImap/IncomingMailAttachment.php index 66a60784..a15783a2 100644 --- a/src/PhpImap/IncomingMailAttachment.php +++ b/src/PhpImap/IncomingMailAttachment.php @@ -14,7 +14,7 @@ * * @author Barbushin Sergey http://linkedin.com/in/barbushin * - * @property string $filePath lazy attachment data file + * @property string|false|null $filePath lazy attachment data file * * @psalm-type fileinfoconst = 0|2|16|1024|1040|8|32|128|256|16777216 */ @@ -129,9 +129,9 @@ public function addDataPartInfo(DataPartInfo $dataInfo): void * * @psalm-param fileinfoconst $fileinfo_const */ - public function getFileInfo($fileinfo_const = FILEINFO_NONE): string + public function getFileInfo(int $fileinfo_const = FILEINFO_NONE): string { - if ((FILEINFO_MIME == $fileinfo_const) and (false != $this->mimeType)) { + if ((FILEINFO_MIME == $fileinfo_const) && (false != $this->mimeType)) { return $this->mimeType; } diff --git a/src/PhpImap/Mailbox.php b/src/PhpImap/Mailbox.php index a50befbf..5e1f2753 100644 --- a/src/PhpImap/Mailbox.php +++ b/src/PhpImap/Mailbox.php @@ -363,7 +363,7 @@ public function setConnectionArgs(int $options = 0, int $retriesNum = 0, array $ $this->imapRetriesNum = $retriesNum; } - if (\is_array($params) and \count($params) > 0) { + if (\is_array($params) && \count($params) > 0) { $supported_params = ['DISABLE_AUTHENTICATOR']; foreach (\array_keys($params) as $key) { @@ -875,16 +875,16 @@ public function getMailsInfo(array $mailsIds): array throw new UnexpectedValueException('to property at index '.(string) $index.' of argument 1 passed to '.__METHOD__.'() was not a string!'); } - if (isset($mail->subject) and !empty(\trim($mail->subject))) { + if (isset($mail->subject) && !empty(\trim($mail->subject))) { $mail->subject = $this->decodeMimeStr($mail->subject); } - if (isset($mail->from) and !empty(\trim($mail->from))) { + if (isset($mail->from) && !empty(\trim($mail->from))) { $mail->from = $this->decodeMimeStr($mail->from); } - if (isset($mail->sender) and !empty(\trim($mail->sender))) { + if (isset($mail->sender) && !empty(\trim($mail->sender))) { $mail->sender = $this->decodeMimeStr($mail->sender); } - if (isset($mail->to) and !empty(\trim($mail->to))) { + if (isset($mail->to) && !empty(\trim($mail->to))) { $mail->to = $this->decodeMimeStr($mail->to); } } @@ -945,14 +945,13 @@ public function getMailboxInfo(): object * @param string|null $searchCriteria See http://php.net/imap_search for a complete list of available criteria * * @psalm-param value-of $criteria - * @psalm-param 1|5|0|2|6|3|4 $criteria * * @return array Mails ids */ public function sortMails( int $criteria = SORTARRIVAL, bool $reverse = true, - ? string $searchCriteria = 'ALL', + ?string $searchCriteria = 'ALL', string $charset = null ): array { return Imap::sort( @@ -1101,22 +1100,22 @@ public function getMailHeader(int $mailId): IncomingMailHeader $header->precedence = (\preg_match("/Precedence\:(.*)/i", $headersRaw, $matches)) ? \trim($matches[1]) : ''; $header->failedRecipients = (\preg_match("/Failed-Recipients\:(.*)/i", $headersRaw, $matches)) ? \trim($matches[1]) : ''; - if (isset($head->date) and !empty(\trim($head->date))) { + if (isset($head->date) && !empty(\trim($head->date))) { $header->date = self::parseDateTime($head->date); - } elseif (isset($head->Date) and !empty(\trim($head->Date))) { + } elseif (isset($head->Date) && !empty(\trim($head->Date))) { $header->date = self::parseDateTime($head->Date); } else { $now = new DateTime(); $header->date = self::parseDateTime($now->format('Y-m-d H:i:s')); } - $header->subject = (isset($head->subject) and !empty(\trim($head->subject))) ? $this->decodeMimeStr($head->subject) : null; - if (isset($head->from) and !empty($head->from)) { + $header->subject = (isset($head->subject) && !empty(\trim($head->subject))) ? $this->decodeMimeStr($head->subject) : null; + if (isset($head->from) && !empty($head->from)) { list($header->fromHost, $header->fromName, $header->fromAddress) = $this->possiblyGetHostNameAndAddress($head->from); } elseif (\preg_match('/smtp.mailfrom=[-0-9a-zA-Z.+_]+@[-0-9a-zA-Z.+_]+.[a-zA-Z]{2,4}/', $headersRaw, $matches)) { $header->fromAddress = \substr($matches[0], 14); } - if (isset($head->sender) and !empty($head->sender)) { + if (isset($head->sender) && !empty($head->sender)) { list($header->senderHost, $header->senderName, $header->senderAddress) = $this->possiblyGetHostNameAndAddress($head->sender); } if (isset($head->to)) { @@ -1256,11 +1255,11 @@ public function downloadAttachment(DataPartInfo $dataInfo, array $params, object $fileName = \strtolower($partStructure->subtype).'.eml'; } elseif ('ALTERNATIVE' == $partStructure->subtype) { $fileName = \strtolower($partStructure->subtype).'.eml'; - } elseif ((!isset($params['filename']) or empty(\trim($params['filename']))) && (!isset($params['name']) or empty(\trim($params['name'])))) { + } elseif ((!isset($params['filename']) || empty(\trim($params['filename']))) && (!isset($params['name']) || empty(\trim($params['name'])))) { $fileName = \strtolower($partStructure->subtype); } else { - $fileName = (isset($params['filename']) and !empty(\trim($params['filename']))) ? $params['filename'] : $params['name']; - $fileName = $this->decodeMimeStr($fileName); + $fileName = (isset($params['filename']) && !empty(\trim($params['filename']))) ? $params['filename'] : $params['name']; + $fileName = $this->decodeMimeStr($fileName, $this->serverEncoding); $fileName = $this->decodeRFC2231($fileName); } @@ -1301,7 +1300,7 @@ public function downloadAttachment(DataPartInfo $dataInfo, array $params, object if (isset($charset) && !\is_string($charset)) { throw new InvalidArgumentException('Argument 2 passed to '.__METHOD__.'() must specify charset as a string when specified!'); } - $attachment->charset = (isset($charset) and !empty(\trim($charset))) ? $charset : null; + $attachment->charset = (isset($charset) && !empty(\trim($charset))) ? $charset : null; $attachment->emlOrigin = $emlOrigin; $attachment->addDataPartInfo($dataInfo); @@ -1329,6 +1328,49 @@ public function downloadAttachment(DataPartInfo $dataInfo, array $params, object return $attachment; } + /** + * Converts a string to UTF-8 + * + * @param string $string MIME string to decode + * @param string $fromCharset Charset to convert from + * + * @return string Converted string if conversion was successful, or the original string if not + */ + public function convertToUtf8(string $string, string $fromCharset): string + { + $fromCharset = mb_strtolower($fromCharset); + $newString = ''; + + if ('default' === $fromCharset) { + $fromCharset = $this->decodeMimeStrDefaultCharset; + } + + switch ($fromCharset) { + case 'default': // Charset default is already ASCII (not encoded) + case 'utf-8': // Charset UTF-8 is OK + $newString .= $string; + break; + default: + // If charset exists in mb_list_encodings(), convert using mb_convert function + if (\in_array($fromCharset, $this->lowercase_mb_list_encodings(), true)) { + $newString .= \mb_convert_encoding($string, 'UTF-8', $fromCharset); + } else { + // Fallback: Try to convert with iconv() + $iconv_converted_string = @\iconv($fromCharset, 'UTF-8', $string); + if (!$iconv_converted_string) { + // If iconv() could also not convert, return string as it is + // (unknown charset) + $newString .= $string; + } else { + $newString .= $iconv_converted_string; + } + } + break; + } + + return $newString; + } + /** * Decodes a mime string. * @@ -1351,34 +1393,7 @@ public function decodeMimeStr(string $string): string } foreach ($elements as $element) { - $charset = \strtolower($element->charset); - - if ('default' === $charset) { - $charset = $this->decodeMimeStrDefaultCharset; - } - - switch ($charset) { - case 'default': // Charset default is already ASCII (not encoded) - case 'utf-8': // Charset UTF-8 is OK - $newString .= $element->text; - break; - default: - // If charset exists in mb_list_encodings(), convert using mb_convert function - if (\in_array($charset, $this->lowercase_mb_list_encodings())) { - $newString .= \mb_convert_encoding($element->text, 'UTF-8', $charset); - } else { - // Fallback: Try to convert with iconv() - $iconv_converted_string = @\iconv($charset, 'UTF-8', $element->text); - if (!$iconv_converted_string) { - // If iconv() could also not convert, return string as it is - // (unknown charset) - $newString .= $element->text; - } else { - $newString .= $iconv_converted_string; - } - } - break; - } + $newString .= $this->convertToUtf8($element->text, $element->charset); } return $newString; @@ -1637,7 +1652,7 @@ protected function initMailPart(IncomingMail $mail, object $partStructure, $part } } - $isAttachment = isset($params['filename']) || isset($params['name']); + $isAttachment = isset($params['filename']) || isset($params['name']) || isset($partStructure->id); $dispositionAttachment = ( isset($partStructure->disposition) && @@ -1792,7 +1807,7 @@ protected function possiblyGetEmailAndNameFromRecipient(object $recipient): ?arr if ('' !== \trim($recipientMailbox) && '' !== \trim($recipientHost)) { $recipientEmail = \strtolower($recipientMailbox.'@'.$recipientHost); - $recipientName = (\is_string($recipientPersonal) and '' !== \trim($recipientPersonal)) ? $this->decodeMimeStr($recipientPersonal) : null; + $recipientName = (\is_string($recipientPersonal) && '' !== \trim($recipientPersonal)) ? $this->decodeMimeStr($recipientPersonal) : null; return [ $recipientEmail, diff --git a/tests/unit/AbstractLiveMailboxTest.php b/tests/unit/AbstractLiveMailboxTest.php index cae4f8a3..9f7bf316 100644 --- a/tests/unit/AbstractLiveMailboxTest.php +++ b/tests/unit/AbstractLiveMailboxTest.php @@ -41,25 +41,7 @@ */ abstract class AbstractLiveMailboxTest extends TestCase { - /** - * Provides constructor arguments for a live mailbox. - * - * @psalm-return MAILBOX_ARGS[] - */ - public function MailBoxProvider(): array - { - $sets = []; - - $imapPath = \getenv('PHPIMAP_IMAP_PATH'); - $login = \getenv('PHPIMAP_LOGIN'); - $password = \getenv('PHPIMAP_PASSWORD'); - - if (\is_string($imapPath) && \is_string($login) && \is_string($password)) { - $sets['CI ENV'] = [new HiddenString($imapPath), new HiddenString($login), new HiddenString($password, true, true), \sys_get_temp_dir()]; - } - - return $sets; - } + use LiveMailboxTestingTrait; /** * @psalm-return Generator @@ -193,49 +175,6 @@ public function testAppend( } } - /** - * Get instance of Mailbox, pre-set to a random mailbox. - * - * @param string $attachmentsDir - * @param string $serverEncoding - * - * @return mixed[] - * - * @psalm-return array{0:Mailbox, 1:string, 2:HiddenString} - */ - protected function getMailbox(HiddenString $imapPath, HiddenString $login, HiddenString $password, $attachmentsDir, $serverEncoding = 'UTF-8') - { - $mailbox = new Mailbox($imapPath->getString(), $login->getString(), $password->getString(), $attachmentsDir, $serverEncoding); - - $random = 'test-box-'.\date('c').\bin2hex(\random_bytes(4)); - - $mailbox->createMailbox($random); - - $mailbox->switchMailbox($random, false); - - return [$mailbox, $random, $imapPath]; - } - - /** - * @psalm-param MAILBOX_ARGS $mailbox_args - * - * @return mixed[] - * - * @psalm-return array{0:Mailbox, 1:string, 2:HiddenString} - */ - protected function getMailboxFromArgs(array $mailbox_args): array - { - list($path, $username, $password, $attachments_dir) = $mailbox_args; - - return $this->getMailbox( - $path, - $username, - $password, - $attachments_dir, - isset($mailbox_args[4]) ? $mailbox_args[4] : 'UTF-8' - ); - } - /** * Get subject search criteria and subject. * diff --git a/tests/unit/Fixtures/IncomingMailAttachment.php b/tests/unit/Fixtures/IncomingMailAttachment.php new file mode 100644 index 00000000..2791354b --- /dev/null +++ b/tests/unit/Fixtures/IncomingMailAttachment.php @@ -0,0 +1,27 @@ +override_getFileInfo_mime) + ) { + return $this->override_getFileInfo_mime; + } + + return parent::getFileInfo($fileinfo_const); + } +} diff --git a/tests/unit/ImapTest.php b/tests/unit/ImapTest.php new file mode 100644 index 00000000..e7355820 --- /dev/null +++ b/tests/unit/ImapTest.php @@ -0,0 +1,157 @@ + + * } $args + */ +class ImapTest extends Base +{ + use LiveMailboxTestingTrait; + + /** + * @psalm-return Generator, + * 1:string, + * 2:PSALM_OPEN_ARGS, + * 3?:bool + * }> + */ + public function OpenFailure(): Generator + { + yield 'empty mailbox/username/password' => [ + UnexpectedValueException::class, + 'IMAP error:Can\'t open mailbox : no such mailbox', + [ + new HiddenString(''), + new HiddenString(''), + new HiddenString(''), + 0, + 0, + [], + ], + ]; + + $imapPath = \getenv('PHPIMAP_IMAP_PATH'); + $login = \getenv('PHPIMAP_LOGIN'); + $password = \getenv('PHPIMAP_PASSWORD'); + + if (\is_string($imapPath) && \is_string($login) && \is_string($password)) { + yield 'CI ENV with invalid password' => [ + UnexpectedValueException::class, + '/^IMAP error:.*\[AUTHENTICATIONFAILED\].*/', + [ + new HiddenString($imapPath, true, true), + new HiddenString($login, true, true), + new HiddenString(\strrev($password), true, true), + 0, + 0, + [], + ], + true, + ]; + } + } + + /** + * @dataProvider OpenFailure + * + * @psalm-param class-string $exception + * @psalm-param PSALM_OPEN_ARGS $args + */ + public function testOpenFailure( + string $exception, + string $message, + array $args, + bool $message_as_regex = false + ): void { + $this->expectException($exception); + + if ($message_as_regex) { + $this->expectExceptionMessageMatches($message); + } else { + $this->expectExceptionMessage($message); + } + + Imap::open( + $args[0]->getString(), + $args[1]->getString(), + $args[2]->getString(), + $args[3], + $args[4], + $args[5] + ); + } + + /** + * @dataProvider MailBoxProvider + * + * @group live + */ + public function testSortEmpty( + HiddenString $path, + HiddenString $login, + HiddenString $password + ): void { + list($mailbox, $remove_mailbox, $path) = $this->getMailboxFromArgs([ + $path, + $login, + $password, + \sys_get_temp_dir(), + ]); + + /** @var Throwable|null */ + $exception = null; + + $mailboxDeleted = false; + + try { + $this->assertSame( + [], + Imap::sort( + $mailbox->getImapStream(), + SORTARRIVAL, + false, + 0 + ) + ); + } catch (Throwable $ex) { + $exception = $ex; + } finally { + $mailbox->switchMailbox($path->getString()); + if (!$mailboxDeleted) { + $mailbox->deleteMailbox($remove_mailbox); + } + $mailbox->disconnect(); + } + + if (null !== $exception) { + throw $exception; + } + } +} diff --git a/tests/unit/Issue509.php b/tests/unit/Issue509Test.php similarity index 98% rename from tests/unit/Issue509.php rename to tests/unit/Issue509Test.php index 89c2e1e2..739b8a40 100644 --- a/tests/unit/Issue509.php +++ b/tests/unit/Issue509Test.php @@ -12,7 +12,7 @@ use PHPUnit\Framework\TestCase; -class Issue509 extends TestCase +class Issue509Test extends TestCase { const base64 = 'vsiz58fPvcq0z7HuLiC05MDlx9jB1rzFvK0gsKi758fVtM+02S4NCsDMt7EgwM/AuiC16b7uILq7 diff --git a/tests/unit/Issue519Test.php b/tests/unit/Issue519Test.php new file mode 100644 index 00000000..f79caf1f --- /dev/null +++ b/tests/unit/Issue519Test.php @@ -0,0 +1,182 @@ +'; + + const MIME = 'image/jpeg'; + + const EXPECTED_ATTACHMENT_COUNT = 1; + + const EXPECTED_ATTACHMENT_COUNT_AFTER_EMBED = 0; + + /** + * @psalm-return array + */ + public function provider(): array + { + $out = []; + + foreach (self::HEADER_VALUES as $value) { + $out[$value] = [$value]; + } + + return $out; + } + + /** + * @dataProvider provider + */ + public function test(string $header_value): void + { + $mailbox = new Mailbox('', '', ''); + $mail = new IncomingMail(); + $attachment = new Fixtures\IncomingMailAttachment(); + $part = new Fixtures\DataPartInfo( + $mailbox, + 0, + 0, + ENCBASE64, + 0 + ); + + $html = new Fixtures\DataPartInfo( + $mailbox, + 0, + 0, + ENC8BIT, + 0 + ); + + $html_string = ''; + + $html->setData($html_string); + $part->setData(''); + + $attachment->id = self::ID; + $attachment->contentId = self::ID; + $attachment->type = TYPEIMAGE; + $attachment->encoding = ENCBASE64; + $attachment->subtype = self::SUBTYPE; + $attachment->description = self::ID; + $attachment->name = self::ID; + $attachment->sizeInBytes = self::SIZE_IN_BYTES; + $attachment->disposition = $header_value; + $attachment->override_getFileInfo_mime = self::MIME; + + $attachment->addDataPartInfo($part); + + $mail->addDataPartInfo($html, DataPartInfo::TEXT_HTML); + $mail->addAttachment($attachment); + + $this->assertTrue($mail->hasAttachments()); + + $this->assertCount( + self::EXPECTED_ATTACHMENT_COUNT, + $mail->getAttachments() + ); + + $this->assertSame($html_string, $mail->textHtml); + + $mail->embedImageAttachments(); + + $this->assertCount( + self::EXPECTED_ATTACHMENT_COUNT_AFTER_EMBED, + $mail->getAttachments() + ); + + $this->assertSame(self::HTML_EMBED, $mail->textHtml); + } +} diff --git a/tests/unit/LiveMailboxTestingTrait.php b/tests/unit/LiveMailboxTestingTrait.php new file mode 100644 index 00000000..6f55d533 --- /dev/null +++ b/tests/unit/LiveMailboxTestingTrait.php @@ -0,0 +1,88 @@ +getString(), $login->getString(), $password->getString(), $attachmentsDir, $serverEncoding); + + $random = 'test-box-'.\date('c').\bin2hex(\random_bytes(4)); + + $mailbox->createMailbox($random); + + $mailbox->switchMailbox($random, false); + + return [$mailbox, $random, $imapPath]; + } + + /** + * @psalm-param MAILBOX_ARGS $mailbox_args + * + * @return mixed[] + * + * @psalm-return array{0:Mailbox, 1:string, 2:HiddenString} + */ + protected function getMailboxFromArgs(array $mailbox_args): array + { + list($path, $username, $password, $attachments_dir) = $mailbox_args; + + return $this->getMailbox( + $path, + $username, + $password, + $attachments_dir, + isset($mailbox_args[4]) ? $mailbox_args[4] : 'UTF-8' + ); + } +}