diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml new file mode 100644 index 00000000..92f26317 --- /dev/null +++ b/.github/workflows/php.yml @@ -0,0 +1,137 @@ +name: PHP Static Analysis & Tests + +on: + push: + branches: [ develop, 'issue/592' ] + pull_request: + branches: [ develop ] + +jobs: + static-analysis: + name: Static Analysis PHP ${{ matrix.php-versions }} + runs-on: ${{ matrix.operating-system }} + strategy: + matrix: + operating-system: ['ubuntu-20.04'] + php-versions: ['7.2', '7.3', '7.4', '8.0', '8.1'] + + steps: + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + coverage: none + + - uses: actions/checkout@v2 + + - name: Validate composer.json and composer.lock + run: composer validate + + - name: Get Composer Cache Directory + id: composer-cache + run: | + echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache Files + uses: actions/cache@v2 + with: + path: | + ${{ steps.composer-cache.outputs.dir }} + **/.php_cs.cache + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-composer- + + - name: Install dependencies + if: steps.composer-cache.outputs.cache-hit != 'true' + run: composer install --prefer-dist --no-progress --no-suggest + + - name: Run static analysis + run: composer run static-analysis + + phpunit: + name: PHP ${{ matrix.php-versions }} Unit Tests + runs-on: ${{ matrix.operating-system }} + strategy: + matrix: + operating-system: ['ubuntu-20.04'] + php-versions: ['7.2', '7.3', '7.4', '8.0', '8.1'] + + steps: + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + coverage: none + + - uses: actions/checkout@v2 + + - name: Validate composer.json and composer.lock + run: composer validate + + - name: Get Composer Cache Directory + id: composer-cache + run: | + echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache Files + uses: actions/cache@v2 + with: + path: | + ${{ steps.composer-cache.outputs.dir }} + **/.php_cs.cache + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-composer- + + - name: Install dependencies + if: steps.composer-cache.outputs.cache-hit != 'true' + run: composer install --prefer-dist --no-progress --no-suggest + + - name: Run tests + run: ./vendor/bin/phpunit --testdox --stop-on-failure + + coverage: + name: Coverage + runs-on: ${{ matrix.operating-system }} + strategy: + matrix: + operating-system: ['ubuntu-20.04'] + php-versions: ['8.0'] + + steps: + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + coverage: xdebug + + - uses: actions/checkout@v2 + + - name: Validate composer.json and composer.lock + run: composer validate + + - name: Get Composer Cache Directory + id: composer-cache + run: | + echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache Files + uses: actions/cache@v2 + with: + path: | + ${{ steps.composer-cache.outputs.dir }} + **/.php_cs.cache + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-composer- + - name: Install dependencies + if: steps.composer-cache.outputs.cache-hit != 'true' + run: composer install --prefer-dist --no-progress --no-suggest + + - name: Run tests + uses: paambaati/codeclimate-action@v2.7.2 + env: + CC_TEST_REPORTER_ID: "945dfb58a832d233a3caeb84e3e6d3be212e8c7abcb48117fce63b9adcb43647" + with: + coverageCommand: ./vendor/bin/phpunit --testdox --stop-on-failure --coverage-clover=clover.xml diff --git a/.gitignore b/.gitignore index 62045583..fb9818ce 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ vendor/ composer.lock *.phar -.php_cs.cache +.php-cs-fixer.cache coverage/ psalm/cache/ diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 00000000..924761ff --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,39 @@ +setRiskyAllowed(true) + ->setRules([ + '@Symfony' => true, + '@Symfony:risky' => true, + '@PHP71Migration' => true, // @PHP72Migration does not exist + '@PHP71Migration:risky' => true, // @PHP72Migration:risky does not exist + 'array_syntax' => ['syntax' => 'short'], + 'declare_strict_types' => true, + 'global_namespace_import' => [ + 'import_classes' => true, + 'import_constants' => true, + 'import_functions' => false, + ], + 'native_constant_invocation' => true, + 'native_function_invocation' => [ + 'strict' => false, + 'include' => ['@compiler_optimized'], + ], + 'no_superfluous_phpdoc_tags' => true, + 'ordered_class_elements' => true, + 'ordered_imports' => true, + 'php_unit_dedicate_assert' => ['target' => 'newest'], + 'php_unit_method_casing' => true, + 'php_unit_test_case_static_method_calls' => ['call_type' => 'this'], + 'phpdoc_to_comment' => false, + 'void_return' => true, + ]) + ->setFinder(PhpCsFixer\Finder::create() + ->exclude('vendor') + ->in(__DIR__) + ) +; diff --git a/.php_cs.dist b/.php_cs.dist deleted file mode 100644 index 8afdc144..00000000 --- a/.php_cs.dist +++ /dev/null @@ -1,43 +0,0 @@ -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($finder) -; - -return $config; diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 4855f448..00000000 --- a/.travis.yml +++ /dev/null @@ -1,57 +0,0 @@ -env: - global: - - CC_TEST_REPORTER_ID=4eed0d135b9e1a1668a68e5e29cc71faf872937d13c42efa4faf42dad5ed3375 - -language: php - -php: - - 7.2.0 - - 7.2 - - 7.3 - -env: - - phpunitflags="--stop-on-failure --testdox --exclude-group=live" - -matrix: - fast_finish: true - include: - - php: 7.2.0 - dist: xenial - env: - - lint="yes" - - phpunitflags="do not run" - - php: 7.4 - dist: bionic - env: - - analysis=yes - - phpunitflags="do not run" - - php: 7.4 - dist: bionic - env: - - lint=no - - coverage=yes - - phpunitflags="--stop-on-failure --testdox --coverage-clover=clover.xml" - -cache: - directories: - - $HOME/.composer/cache - - ./psalm/cache - -before_script: - - if [[ "$coverage" != "yes" ]]; then phpenv config-rm xdebug.ini; fi - - composer install --no-interaction - - if [[ "$coverage" = "yes" ]]; then curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter; fi - - if [[ "$coverage" = "yes" ]]; then chmod +x ./cc-test-reporter; fi - - if [[ "$coverage" = "yes" ]]; then ./cc-test-reporter before-build; fi - -script: - - if [[ "$lint" != "no" ]]; then vendor/bin/parallel-lint .php_cs.dist src tests examples; fi - - if [[ "$phpunitflags" != "do not run" ]]; then vendor/bin/phpunit $phpunitflags; fi - - if [[ "$analysis" = "yes" ]]; then vendor/bin/composer-require-checker check ./composer.json; fi - - if [[ "$analysis" = "yes" ]]; then vendor/bin/phpmnd ./ --exclude=./.github/ --exclude=./examples/ --exclude=./vendor/ --exclude=./psalm/cache/ --non-zero-exit-on-violation --hint; fi - - if [[ "$analysis" = "yes" ]]; then vendor/bin/psalm --show-info=false --shepherd --diff --diff-methods; fi - - if [[ "$lint" = "yes" ]]; then vendor/bin/php-cs-fixer fix --allow-risky=yes --no-interaction --dry-run --diff-format=udiff -v; fi - - if [[ "$lint" = "yes" ]]; then vendor/bin/phpcpd src tests; fi - -after_script: - - if [[ "$coverage" = "yes" ]]; then ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT -t clover; fi diff --git a/README.md b/README.md index 66be67d2..16e4d745 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) [![Packagist](https://img.shields.io/packagist/dt/php-imap/php-imap.svg?style=flat-square)](https://packagist.org/packages/php-imap/php-imap) [![Build Status](https://travis-ci.org/barbushin/php-imap.svg?branch=master)](https://travis-ci.org/barbushin/php-imap) +[![CI](https://github.com/barbushin/php-imap/actions/workflows/php.yml/badge.svg)](https://github.com/barbushin/php-imap/actions/workflows/php.yml) [![Supported PHP Version](https://img.shields.io/packagist/php-v/php-imap/php-imap.svg)](README.md) [![Maintainability](https://api.codeclimate.com/v1/badges/02f72a4fd695cb7e2976/maintainability)](https://codeclimate.com/github/barbushin/php-imap/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/02f72a4fd695cb7e2976/test_coverage)](https://codeclimate.com/github/barbushin/php-imap/test_coverage) @@ -71,6 +72,8 @@ You can run all PHPUnit tests by running the following command (inside of the in Below, you'll find an example code how you can use this library. For further information and other examples, you may take a look at the [wiki](https://github.com/barbushin/php-imap/wiki). +By default, this library uses random filenames for attachments as identical file names from other emails would overwrite other attachments. If you want to keep the original file name, you can set the attachment filename mode to ``true``, but then you also need to ensure, that those files don't get overwritten by other emails for example. + ```php // Create PhpImap\Mailbox instance for all further actions $mailbox = new PhpImap\Mailbox( @@ -78,7 +81,9 @@ $mailbox = new PhpImap\Mailbox( 'some@gmail.com', // Username for the before configured mailbox '*********', // Password for the before configured username __DIR__, // Directory, where attachments will be saved (optional) - 'UTF-8' // Server encoding (optional) + 'UTF-8', // Server encoding (optional) + true, // Trim leading/ending whitespaces of IMAP path (optional) + false // Attachment filename mode (optional; false = random filename; true = original filename) ); // set some connection arguments (if appropriate) diff --git a/composer-require-checker.config.json b/composer-require-checker.config.json new file mode 100644 index 00000000..9020d1d1 --- /dev/null +++ b/composer-require-checker.config.json @@ -0,0 +1,18 @@ +{ + "symbol-whitelist" : [ + "null", "true", "false", + "static", "self", "parent", + "array", "string", "int", "float", "bool", "iterable", "callable", "void", "object", "mixed", "never", + "IMAP\\Connection" + ], + "php-core-extensions" : [ + "Core", + "date", + "pcre", + "Phar", + "Reflection", + "SPL", + "standard" + ], + "scan-files" : [] +} diff --git a/composer.json b/composer.json index 32595e2a..5e897814 100644 --- a/composer.json +++ b/composer.json @@ -23,32 +23,31 @@ "sort-packages": true }, "require": { - "php": "^7.2 || ^8.0 <8.1", + "php": "^7.2 || ^8.0", "ext-fileinfo": "*", "ext-iconv": "*", "ext-imap": "*", "ext-mbstring": "*" }, "require-dev": { - "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", + "friendsofphp/php-cs-fixer": "^3.4", + "maglnet/composer-require-checker": "^2.0|^3.2", + "nikic/php-parser": "^4.3,<4.7|^4.10", "paragonie/hidden-string": "^1.0", - "phpunit/phpunit": "^8.5", + "php-parallel-lint/php-parallel-lint": "^1.3", + "phpunit/phpunit": "^8.5|^9.5", "povils/phpmnd": "^2.2", - "psalm/plugin-phpunit": "^0.10.0", + "psalm/plugin-phpunit": "^0.10.0|^0.15.1", "roave/security-advisories": "dev-master", - "sebastian/phpcpd": "^4.1", - "vimeo/psalm": "^3.12" + "sebastian/phpcpd": "^4.1|^6.0" }, "scripts": { "static-analysis": [ - "parallel-lint .php_cs.dist src tests examples", + "parallel-lint .php-cs-fixer.dist.php src tests examples", "phpcpd src tests", - "composer-require-checker check ./composer.json", + "composer-require-checker check --config-file=composer-require-checker.config.json ./composer.json", "phpmnd ./ --exclude=./.github/ --exclude=./examples/ --exclude=./vendor/ --non-zero-exit-on-violation --hint", - "php-cs-fixer fix --allow-risky=yes --no-interaction --dry-run --diff-format=udiff -v", + "php-cs-fixer fix --allow-risky=yes --no-interaction --dry-run -v", "psalm --show-info=false" ], "tests": [ 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 5ad13361..ccd3d3d7 100644 --- a/examples/get_and_parse_all_emails_with_matching_subject.php +++ b/examples/get_and_parse_all_emails_with_matching_subject.php @@ -36,7 +36,7 @@ false // Do NOT mark emails as seen (optional) ); - echo 'from-name: '.(string) (isset($email->fromName) ? $email->fromName : $email->fromAddress)."\n"; + echo 'from-name: '.(string) ($email->fromName ?? $email->fromAddress)."\n"; echo 'from-email: '.(string) $email->fromAddress."\n"; echo 'to: '.(string) $email->toString."\n"; echo 'subject: '.(string) $email->subject."\n"; 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 b5db0886..258129e0 100644 --- a/examples/get_and_parse_all_emails_without_saving_attachments.php +++ b/examples/get_and_parse_all_emails_without_saving_attachments.php @@ -46,7 +46,7 @@ false // Do NOT mark emails as seen (optional) ); - echo 'from-name: '.(string) (isset($email->fromName) ? $email->fromName : $email->fromAddress)."\n"; + echo 'from-name: '.(string) ($email->fromName ?? $email->fromAddress)."\n"; echo 'from-email: '.(string) $email->fromAddress."\n"; echo 'to: '.(string) $email->toString."\n"; echo 'subject: '.(string) $email->subject."\n"; diff --git a/examples/get_and_parse_unseen_emails.php b/examples/get_and_parse_unseen_emails.php index 1070ac31..800bb5ac 100644 --- a/examples/get_and_parse_unseen_emails.php +++ b/examples/get_and_parse_unseen_emails.php @@ -36,7 +36,7 @@ false // Do NOT mark emails as seen (optional) ); - echo 'from-name: '.(string) (isset($email->fromName) ? $email->fromName : $email->fromAddress)."\n"; + echo 'from-name: '.(string) ($email->fromName ?? $email->fromAddress)."\n"; echo 'from-email: '.(string) $email->fromAddress."\n"; echo 'to: '.(string) $email->toString."\n"; echo 'subject: '.(string) $email->subject."\n"; 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 11192bf6..54a48995 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 @@ -34,7 +34,7 @@ false // Do NOT mark emails as seen (optional) ); - echo 'from-name: '.(string) (isset($email->fromName) ? $email->fromName : $email->fromAddress)."\n"; + echo 'from-name: '.(string) ($email->fromName ?? $email->fromAddress)."\n"; echo 'from-email: '.(string) $email->fromAddress."\n"; echo 'to: '.(string) $email->toString."\n"; echo 'subject: '.(string) $email->subject."\n"; diff --git a/phpunit.xml b/phpunit.xml index 4035771b..6e985a52 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,25 +1,27 @@ - - - tests - - - - - src - - + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + bootstrap="vendor/autoload.php" + verbose="true" + stopOnError="false" + stopOnFailure="false" + stopOnIncomplete="false" + stopOnSkipped="false" + convertErrorsToExceptions="true" + convertNoticesToExceptions="true" + convertWarningsToExceptions="true" + colors="true" + forceCoversAnnotation="false" + processIsolation="false" + xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"> + + + src + + + + + tests + + diff --git a/psalm.xml b/psalm.xml index a544ed84..dc5084cb 100644 --- a/psalm.xml +++ b/psalm.xml @@ -1,21 +1,30 @@ - + - - + + diff --git a/src/PhpImap/DataPartInfo.php b/src/PhpImap/DataPartInfo.php index 4a10dd9b..fc2ddcd0 100644 --- a/src/PhpImap/DataPartInfo.php +++ b/src/PhpImap/DataPartInfo.php @@ -16,9 +16,9 @@ */ class DataPartInfo { - const TEXT_PLAIN = 0; + public const TEXT_PLAIN = 0; - const TEXT_HTML = 1; + public const TEXT_HTML = 1; /** * @var int @@ -109,8 +109,7 @@ protected function convertEncodingAfterFetch(): string { if (isset($this->charset) && !empty(\trim($this->charset))) { $this->data = $this->mail->decodeMimeStr( - (string) $this->data, // Data to convert - \trim($this->charset) + (string) $this->data // Data to convert ); } diff --git a/src/PhpImap/Imap.php b/src/PhpImap/Imap.php index 4d1ac296..d6c9475d 100644 --- a/src/PhpImap/Imap.php +++ b/src/PhpImap/Imap.php @@ -22,6 +22,7 @@ use const SORTSIZE; use const SORTSUBJECT; use const SORTTO; +use stdClass; use Throwable; use UnexpectedValueException; @@ -41,7 +42,7 @@ final class Imap { /** @psalm-var list */ - const SORT_CRITERIA = [ + public const SORT_CRITERIA = [ SORTARRIVAL, SORTCC, SORTDATE, @@ -52,7 +53,7 @@ final class Imap ]; /** @psalm-var list */ - const TIMEOUT_TYPES = [ + public const TIMEOUT_TYPES = [ IMAP_CLOSETIMEOUT, IMAP_OPENTIMEOUT, IMAP_READTIMEOUT, @@ -60,7 +61,7 @@ final class Imap ]; /** @psalm-var list */ - const CLOSE_FLAGS = [ + public const CLOSE_FLAGS = [ 0, CL_EXPUNGE, ]; @@ -572,6 +573,8 @@ static function (string $folder): string { * }> $body An indexed array of bodies (docblock is not complete) * * @todo flesh out array shape pending resolution of https://github.com/vimeo/psalm/issues/1518 + * + * @psalm-pure */ public static function mail_compose(array $envelope, array $body): string { @@ -647,7 +650,7 @@ public static function mail_move( /** * @param false|resource $imap_stream */ - public static function mailboxmsginfo($imap_stream): object + public static function mailboxmsginfo($imap_stream): stdClass { \imap_errors(); // flush errors @@ -692,7 +695,7 @@ public static function open( array $params = [] ) { if (\preg_match("/^\{.*\}(.*)$/", $mailbox, $matches)) { - $mailbox_name = $matches[1]; + $mailbox_name = $matches[1] ?? ''; if (!\mb_detect_encoding($mailbox_name, 'ASCII', true)) { $mailbox = static::encodeStringToUtf7Imap($mailbox); @@ -718,6 +721,8 @@ public static function open( /** * @param resource|false $imap_stream + * + * @psalm-pure */ public static function ping($imap_stream): bool { @@ -892,6 +897,9 @@ public static function setflag_full( * @param false|resource $imap_stream * * @psalm-param value-of $criteria + * @psalm-suppress InvalidArgument + * + * @todo InvalidArgument, although it's correct: Argument 3 of imap_sort expects int, bool provided https://www.php.net/manual/de/function.imap-sort.php * * @return int[] * @@ -908,7 +916,7 @@ public static function sort( \imap_errors(); // flush errors $imap_stream = self::EnsureConnection($imap_stream, __METHOD__, 1); - $reverse = (int) $reverse; + $reverse = $reverse; /** @var int */ $criteria = $criteria; @@ -952,11 +960,8 @@ public static function sort( * * @psalm-param SA_MESSAGES|SA_RECENT|SA_UNSEEN|SA_UIDNEXT|SA_UIDVALIDITY|SA_ALL $flags */ - public static function status( - $imap_stream, - string $mailbox, - int $options - ): object { + public static function status($imap_stream, string $mailbox, int $options): stdClass + { $imap_stream = self::EnsureConnection($imap_stream, __METHOD__, 1); $mailbox = static::encodeStringToUtf7Imap($mailbox); @@ -975,10 +980,8 @@ public static function status( /** * @param false|resource $imap_stream */ - public static function subscribe( - $imap_stream, - string $mailbox - ): void { + public static function subscribe($imap_stream, string $mailbox): void + { $imap_stream = self::EnsureConnection($imap_stream, __METHOD__, 1); $mailbox = static::encodeStringToUtf7Imap($mailbox); @@ -1042,6 +1045,8 @@ public static function unsubscribe( * Returns the provided string in UTF7-IMAP encoded format. * * @return string $str UTF-7 encoded string + * + * @psalm-pure */ public static function encodeStringToUtf7Imap(string $str): string { @@ -1058,6 +1063,8 @@ public static function encodeStringToUtf7Imap(string $str): string * Returns the provided string in UTF-8 encoded format. * * @return string $str, but UTF-8 encoded + * + * @psalm-pure */ public static function decodeStringFromUtf7ImapToUtf8(string $str): string { @@ -1076,10 +1083,12 @@ public static function decodeStringFromUtf7ImapToUtf8(string $str): string * @throws InvalidArgumentException if $maybe is not a valid resource * * @return resource + * + * @psalm-pure */ private static function EnsureResource($maybe, string $method, int $argument) { - if (!$maybe || !\is_resource($maybe)) { + if (!$maybe || (!\is_resource($maybe) && !$maybe instanceof \IMAP\Connection)) { throw new InvalidArgumentException('Argument '.(string) $argument.' passed to '.$method.' must be a valid resource!'); } @@ -1105,6 +1114,8 @@ private static function EnsureConnection($maybe, string $method, int $argument) /** * @param array|false $errors + * + * @psalm-pure */ private static function HandleErrors($errors, string $method): UnexpectedValueException { @@ -1117,6 +1128,8 @@ private static function HandleErrors($errors, string $method): UnexpectedValueEx /** * @param scalar $msg_number + * + * @psalm-pure */ private static function EnsureRange( $msg_number, diff --git a/src/PhpImap/IncomingMail.php b/src/PhpImap/IncomingMail.php index 1dae0593..d9552615 100644 --- a/src/PhpImap/IncomingMail.php +++ b/src/PhpImap/IncomingMail.php @@ -4,7 +4,7 @@ namespace PhpImap; -use const FILEINFO_MIME; +use const FILEINFO_MIME_TYPE; use InvalidArgumentException; /** @@ -169,7 +169,7 @@ public function removeAttachment(string $id): bool */ public function getInternalLinksPlaceholders(): array { - $fetchedHtml = (string) $this->__get('textHtml'); + $fetchedHtml = $this->__get('textHtml'); $match = \preg_match_all('/=["\'](ci?d:([\w\.%*@-]+))["\']/i', $fetchedHtml, $matches); @@ -207,7 +207,7 @@ public function replaceInternalLinks(string $baseUri): string */ public function embedImageAttachments(): void { - $fetchedHtml = (string) $this->__get('textHtml'); + $fetchedHtml = $this->__get('textHtml'); \preg_match_all("/\bcid:[^'\"\s]{1,256}/mi", $fetchedHtml, $matches); @@ -221,11 +221,11 @@ public function embedImageAttachments(): void foreach ($attachments as $attachment) { /** * 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 + * 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); + $contentType = $attachment->getFileInfo(FILEINFO_MIME_TYPE); if (!\strstr($contentType, 'image')) { continue; diff --git a/src/PhpImap/IncomingMailAttachment.php b/src/PhpImap/IncomingMailAttachment.php index a15783a2..2e29ce83 100644 --- a/src/PhpImap/IncomingMailAttachment.php +++ b/src/PhpImap/IncomingMailAttachment.php @@ -4,7 +4,6 @@ namespace PhpImap; -use const FILEINFO_MIME; use const FILEINFO_NONE; use finfo; use UnexpectedValueException; @@ -68,20 +67,20 @@ class IncomingMailAttachment /** @var string|null */ public $fileExtension; + /** @var string|null */ + public $mimeType; + /** @var string|null */ private $file_path; /** @var DataPartInfo|null */ private $dataInfo; - /** @var string|null */ - private $mimeType; - /** @var string|null */ private $filePath; /** - * @return string|false|null + * @return false|string */ public function __get(string $name) { @@ -131,10 +130,6 @@ public function addDataPartInfo(DataPartInfo $dataInfo): void */ public function getFileInfo(int $fileinfo_const = FILEINFO_NONE): string { - if ((FILEINFO_MIME == $fileinfo_const) && (false != $this->mimeType)) { - return $this->mimeType; - } - $finfo = new finfo($fileinfo_const); return $finfo->buffer($this->getContents()); diff --git a/src/PhpImap/IncomingMailHeader.php b/src/PhpImap/IncomingMailHeader.php index 6f03a40d..54f09ef1 100644 --- a/src/PhpImap/IncomingMailHeader.php +++ b/src/PhpImap/IncomingMailHeader.php @@ -92,6 +92,9 @@ class IncomingMailHeader /** @var string|null */ public $senderAddress; + /** @var string|null */ + public $xOriginalTo; + /** * @var (string|null)[] * @@ -109,6 +112,9 @@ class IncomingMailHeader */ public $cc = []; + /** @var string|null */ + public $ccString; + /** * @var (string|null)[] * diff --git a/src/PhpImap/Mailbox.php b/src/PhpImap/Mailbox.php index 5e1f2753..a05857d2 100644 --- a/src/PhpImap/Mailbox.php +++ b/src/PhpImap/Mailbox.php @@ -14,6 +14,7 @@ use const FILEINFO_EXTENSION; use const FILEINFO_MIME; use const FILEINFO_MIME_ENCODING; +use const FILEINFO_MIME_TYPE; use const FILEINFO_NONE; use const FILEINFO_RAW; use const FT_PEEK; @@ -82,22 +83,22 @@ */ class Mailbox { - const EXPECTED_SIZE_OF_MESSAGE_AS_ARRAY = 2; + public const EXPECTED_SIZE_OF_MESSAGE_AS_ARRAY = 2; - const MAX_LENGTH_FILEPATH = 255; + public const MAX_LENGTH_FILEPATH = 255; - const PART_TYPE_TWO = 2; + public const PART_TYPE_TWO = 2; - const IMAP_OPTIONS_SUPPORTED_VALUES = + public const IMAP_OPTIONS_SUPPORTED_VALUES = OP_READONLY // 2 - | OP_ANONYMOUS // 4 - | OP_HALFOPEN // 64 - | CL_EXPUNGE // 32768 - | OP_DEBUG // 1 - | OP_SHORTCACHE // 8 - | OP_SILENT // 16 - | OP_PROTOTYPE // 32 - | OP_SECURE // 256 + | OP_ANONYMOUS // 4 + | OP_HALFOPEN // 64 + | CL_EXPUNGE // 32768 + | OP_DEBUG // 1 + | OP_SHORTCACHE // 8 + | OP_SILENT // 16 + | OP_PROTOTYPE // 32 + | OP_SECURE // 256 ; /** @var string */ @@ -155,21 +156,25 @@ class Mailbox /** @var string */ protected $mailboxFolder; + /** @var bool|false */ + protected $attachmentFilenameMode = false; + /** @var resource|null */ private $imapStream; /** * @throws InvalidParameterException */ - public function __construct(string $imapPath, string $login, string $password, string $attachmentsDir = null, string $serverEncoding = 'UTF-8') + public function __construct(string $imapPath, string $login, string $password, string $attachmentsDir = null, string $serverEncoding = 'UTF-8', bool $trimImapPath = true, bool $attachmentFilenameMode = false) { - $this->imapPath = \trim($imapPath); + $this->imapPath = (true == $trimImapPath) ? \trim($imapPath) : $imapPath; $this->imapLogin = \trim($login); $this->imapPassword = $password; $this->setServerEncoding($serverEncoding); if (null != $attachmentsDir) { $this->setAttachmentsDir($attachmentsDir); } + $this->setAttachmentFilenameMode($attachmentFilenameMode); $this->setMailboxFolder(); } @@ -214,6 +219,8 @@ public function getPathDelimiter(): string * @param string $delimiter Path delimiter * * @return bool true (supported) or false (unsupported) + * + * @psalm-pure */ public function validatePathDelimiter(string $delimiter): bool { @@ -256,6 +263,32 @@ public function setServerEncoding(string $serverEncoding): void $this->serverEncoding = $serverEncoding; } + /** + * Returns the current set attachment filename mode. + * + * @return bool Attachment filename mode (e.g. true) + */ + public function getAttachmentFilenameMode(): bool + { + return $this->attachmentFilenameMode; + } + + /** + * Sets / Changes the attachment filename mode. + * + * @param bool $attachmentFilenameMode Attachment filename mode (e.g. false) + * + * @throws InvalidParameterException + */ + public function setAttachmentFilenameMode(bool $attachmentFilenameMode): void + { + if (!\is_bool($attachmentFilenameMode)) { + throw new InvalidParameterException('"'.$attachmentFilenameMode.'" is not supported by setOriginalAttachmentFilename(). Only boolean values are allowed: true (use original filename), false (use random generated filename)'); + } + + $this->attachmentFilenameMode = $attachmentFilenameMode; + } + /** * Returns the current set IMAP search option. * @@ -450,26 +483,24 @@ public function hasImapStream(): bool * Returns the provided string in UTF7-IMAP encoded format. * * @return string $str UTF-7 encoded string + * + * @psalm-pure */ public function encodeStringToUtf7Imap(string $str): string { - $out = \mb_convert_encoding($str, 'UTF7-IMAP', \mb_detect_encoding($str, 'UTF-8, ISO-8859-1, ISO-8859-15', true)); - - if (!\is_string($out)) { - throw new UnexpectedValueException('mb_convert_encoding($str, \'UTF-8\', {detected}) could not convert $str'); - } - - return $out; + return imap_utf7_encode($str); } /** * Returns the provided string in UTF-8 encoded format. * * @return string $str UTF-7 encoded string or same as before, when it's no string + * + * @psalm-pure */ public function decodeStringFromUtf7ImapToUtf8(string $str): string { - $out = \mb_convert_encoding($str, 'UTF-8', 'UTF7-IMAP'); + $out = imap_utf7_decode($str); if (!\is_string($out)) { throw new UnexpectedValueException('mb_convert_encoding($str, \'UTF-8\', \'UTF7-IMAP\') could not convert $str'); @@ -581,7 +612,7 @@ public function renameMailbox(string $oldName, string $newName): void * This function returns an object containing status information. * The object has the following properties: messages, recent, unseen, uidnext, and uidvalidity. */ - public function statusMailbox(): object + public function statusMailbox(): stdClass { return Imap::status($this->getImapStream(), $this->imapPath, SA_ALL); } @@ -592,7 +623,9 @@ public function statusMailbox(): object * This function returns an object containing listing the folders. * The object has the following properties: messages, recent, unseen, uidnext, and uidvalidity. * - * @return array listing the folders + * @return string[] listing the folders + * + * @psalm-return list */ public function getListingFolders(string $pattern = '*'): array { @@ -919,11 +952,11 @@ public function getMailboxHeaders(): array * Deleted - number of deleted messages * Size - mailbox size * - * @return object Object with info + * @return stdClass Object with info * * @see mailboxmsginfo */ - public function getMailboxInfo(): object + public function getMailboxInfo(): stdClass { return Imap::mailboxmsginfo($this->getImapStream()); } @@ -946,7 +979,9 @@ public function getMailboxInfo(): object * * @psalm-param value-of $criteria * - * @return array Mails ids + * @return int[] Mails ids + * + * @psalm-return list */ public function sortMails( int $criteria = SORTARRIVAL, @@ -984,7 +1019,7 @@ public function getQuotaLimit(string $quota_root = 'INBOX'): int $quota = $this->getQuota($quota_root); /** @var int */ - return isset($quota['STORAGE']['limit']) ? $quota['STORAGE']['limit'] : 0; + return $quota['STORAGE']['limit'] ?? 0; } /** @@ -999,7 +1034,7 @@ public function getQuotaUsage(string $quota_root = 'INBOX') $quota = $this->getQuota($quota_root); /** @var int|false */ - return isset($quota['STORAGE']['usage']) ? $quota['STORAGE']['usage'] : 0; + return $quota['STORAGE']['usage'] ?? 0; } /** @@ -1020,6 +1055,27 @@ public function getRawMail(int $msgId, bool $markAsSeen = true): string return Imap::fetchbody($this->getImapStream(), $msgId, '', $options); } + /** + * Get mail header field value. + * + * @param string $headersRaw RAW headers as single string + * @param string $header_field_name Name of the required header field + * + * @return string Value of the header field + */ + public function getMailHeaderFieldValue(string $headersRaw, string $header_field_name): string + { + $header_field_value = ''; + + if (\preg_match("/$header_field_name\:(.*)/i", $headersRaw, $matches)) { + if (isset($matches[1])) { + return \trim($matches[1]); + } + } + + return $header_field_value; + } + /** * Get mail header. * @@ -1086,19 +1142,20 @@ public function getMailHeader(int $mailId): IncomingMailHeader $header->imapPath = $this->imapPath; $header->mailboxFolder = $this->mailboxFolder; $header->isDraft = (!isset($head->date)) ? true : false; - $header->mimeVersion = (\preg_match("/MIME-Version\:(.*)/i", $headersRaw, $matches)) ? \trim($matches[1]) : ''; - $header->xVirusScanned = (\preg_match("/X-Virus-Scanned\:(.*)/i", $headersRaw, $matches)) ? \trim($matches[1]) : ''; - $header->organization = (\preg_match("/Organization\:(.*)/i", $headersRaw, $matches)) ? \trim($matches[1]) : ''; - $header->contentType = (\preg_match("/Content-Type\:(.*)/i", $headersRaw, $matches)) ? \trim($matches[1]) : ''; - $header->xMailer = (\preg_match("/X-Mailer\:(.*)/i", $headersRaw, $matches)) ? \trim($matches[1]) : ''; - $header->contentLanguage = (\preg_match("/Content-Language\:(.*)/i", $headersRaw, $matches)) ? \trim($matches[1]) : ''; - $header->xSenderIp = (\preg_match("/X-Sender-IP\:(.*)/i", $headersRaw, $matches)) ? \trim($matches[1]) : ''; - $header->priority = (\preg_match("/Priority\:(.*)/i", $headersRaw, $matches)) ? \trim($matches[1]) : ''; - $header->importance = (\preg_match("/Importance\:(.*)/i", $headersRaw, $matches)) ? \trim($matches[1]) : ''; - $header->sensitivity = (\preg_match("/Sensitivity\:(.*)/i", $headersRaw, $matches)) ? \trim($matches[1]) : ''; - $header->autoSubmitted = (\preg_match("/Auto-Submitted\:(.*)/i", $headersRaw, $matches)) ? \trim($matches[1]) : ''; - $header->precedence = (\preg_match("/Precedence\:(.*)/i", $headersRaw, $matches)) ? \trim($matches[1]) : ''; - $header->failedRecipients = (\preg_match("/Failed-Recipients\:(.*)/i", $headersRaw, $matches)) ? \trim($matches[1]) : ''; + $header->mimeVersion = $this->getMailHeaderFieldValue($headersRaw, 'MIME-Version'); + $header->xVirusScanned = $this->getMailHeaderFieldValue($headersRaw, 'X-Virus-Scanned'); + $header->organization = $this->getMailHeaderFieldValue($headersRaw, 'Organization'); + $header->contentType = $this->getMailHeaderFieldValue($headersRaw, 'Content-Type'); + $header->xMailer = $this->getMailHeaderFieldValue($headersRaw, 'X-Mailer'); + $header->contentLanguage = $this->getMailHeaderFieldValue($headersRaw, 'Content-Language'); + $header->xSenderIp = $this->getMailHeaderFieldValue($headersRaw, 'X-Sender-IP'); + $header->priority = $this->getMailHeaderFieldValue($headersRaw, 'Priority'); + $header->importance = $this->getMailHeaderFieldValue($headersRaw, 'Importance'); + $header->sensitivity = $this->getMailHeaderFieldValue($headersRaw, 'Sensitivity'); + $header->autoSubmitted = $this->getMailHeaderFieldValue($headersRaw, 'Auto-Submitted'); + $header->precedence = $this->getMailHeaderFieldValue($headersRaw, 'Precedence'); + $header->failedRecipients = $this->getMailHeaderFieldValue($headersRaw, 'Failed-Recipients'); + $header->xOriginalTo = $this->getMailHeaderFieldValue($headersRaw, 'X-Original-To'); if (isset($head->date) && !empty(\trim($head->date))) { $header->date = self::parseDateTime($head->date); @@ -1111,19 +1168,19 @@ public function getMailHeader(int $mailId): IncomingMailHeader $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); + [$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) && !empty($head->sender)) { - list($header->senderHost, $header->senderName, $header->senderAddress) = $this->possiblyGetHostNameAndAddress($head->sender); + [$header->senderHost, $header->senderName, $header->senderAddress] = $this->possiblyGetHostNameAndAddress($head->sender); } if (isset($head->to)) { $toStrings = []; foreach ($head->to as $to) { $to_parsed = $this->possiblyGetEmailAndNameFromRecipient($to); if ($to_parsed) { - list($toEmail, $toName) = $to_parsed; + [$toEmail, $toName] = $to_parsed; $toStrings[] = $toName ? "$toName <$toEmail>" : $toEmail; $header->to[$toEmail] = $toName; } @@ -1132,12 +1189,16 @@ public function getMailHeader(int $mailId): IncomingMailHeader } if (isset($head->cc)) { + $ccStrings = []; foreach ($head->cc as $cc) { $cc_parsed = $this->possiblyGetEmailAndNameFromRecipient($cc); if ($cc_parsed) { - $header->cc[$cc_parsed[0]] = $cc_parsed[1]; + [$ccEmail, $ccName] = $cc_parsed; + $ccStrings[] = $ccName ? "$ccName <$ccEmail>" : $ccEmail; + $header->cc[$ccEmail] = $ccName; } } + $header->ccString = \implode(', ', $ccStrings); } if (isset($head->bcc)) { @@ -1178,7 +1239,7 @@ public function getMailHeader(int $mailId): IncomingMailHeader * * @return stdClass[] * - * @psalm-return array + * @psalm-return array */ public function flattenParts(array $messageParts, array $flattenedParts = [], string $prefix = '', int $index = 1, bool $fullPrefix = true): array { @@ -1189,13 +1250,10 @@ public function flattenParts(array $messageParts, array $flattenedParts = [], st $part_parts = $part->parts; if (self::PART_TYPE_TWO == $part->type) { - /** @var array */ $flattenedParts = $this->flattenParts($part_parts, $flattenedParts, $prefix.$index.'.', 0, false); } elseif ($fullPrefix) { - /** @var array */ $flattenedParts = $this->flattenParts($part_parts, $flattenedParts, $prefix.$index.'.'); } else { - /** @var array */ $flattenedParts = $this->flattenParts($part_parts, $flattenedParts, $prefix); } unset($flattenedParts[$prefix.$index]->parts); @@ -1259,15 +1317,15 @@ public function downloadAttachment(DataPartInfo $dataInfo, array $params, object $fileName = \strtolower($partStructure->subtype); } else { $fileName = (isset($params['filename']) && !empty(\trim($params['filename']))) ? $params['filename'] : $params['name']; - $fileName = $this->decodeMimeStr($fileName, $this->serverEncoding); + $fileName = $this->decodeMimeStr($fileName); $fileName = $this->decodeRFC2231($fileName); } /** @var scalar|array|object|null */ - $sizeInBytes = isset($partStructure->bytes) ? $partStructure->bytes : null; + $sizeInBytes = $partStructure->bytes ?? null; /** @var scalar|array|object|null */ - $encoding = isset($partStructure->encoding) ? $partStructure->encoding : null; + $encoding = $partStructure->encoding ?? null; if (null !== $sizeInBytes && !\is_int($sizeInBytes)) { throw new UnexpectedValueException('Supplied part structure specifies a non-integer, non-null bytes header!'); @@ -1295,7 +1353,7 @@ public function downloadAttachment(DataPartInfo $dataInfo, array $params, object $attachment->disposition = (isset($partStructure->disposition) && \is_string($partStructure->disposition)) ? $partStructure->disposition : null; /** @var scalar|array|object|resource|null */ - $charset = isset($params['charset']) ? $params['charset'] : null; + $charset = $params['charset'] ?? null; if (isset($charset) && !\is_string($charset)) { throw new InvalidArgumentException('Argument 2 passed to '.__METHOD__.'() must specify charset as a string when specified!'); @@ -1308,19 +1366,26 @@ public function downloadAttachment(DataPartInfo $dataInfo, array $params, object $attachment->fileInfoRaw = $attachment->getFileInfo(FILEINFO_RAW); $attachment->fileInfo = $attachment->getFileInfo(FILEINFO_NONE); $attachment->mime = $attachment->getFileInfo(FILEINFO_MIME); + $attachment->mimeType = $attachment->getFileInfo(FILEINFO_MIME_TYPE); $attachment->mimeEncoding = $attachment->getFileInfo(FILEINFO_MIME_ENCODING); $attachment->fileExtension = $attachment->getFileInfo(FILEINFO_EXTENSION); $attachmentsDir = $this->getAttachmentsDir(); if (null != $attachmentsDir) { - $fileSysName = \bin2hex(\random_bytes(16)).'.bin'; + if (true == $this->getAttachmentFilenameMode()) { + $fileSysName = $attachment->name; + } else { + $fileSysName = \bin2hex(\random_bytes(16)).'.bin'; + } + $filePath = $attachmentsDir.DIRECTORY_SEPARATOR.$fileSysName; if (\strlen($filePath) > self::MAX_LENGTH_FILEPATH) { $ext = \pathinfo($filePath, PATHINFO_EXTENSION); $filePath = \substr($filePath, 0, self::MAX_LENGTH_FILEPATH - 1 - \strlen($ext)).'.'.$ext; } + $attachment->setFilePath($filePath); $attachment->saveToDisk(); } @@ -1329,7 +1394,7 @@ public function downloadAttachment(DataPartInfo $dataInfo, array $params, object } /** - * Converts a string to UTF-8 + * Converts a string to UTF-8. * * @param string $string MIME string to decode * @param string $fromCharset Charset to convert from @@ -1338,35 +1403,35 @@ public function downloadAttachment(DataPartInfo $dataInfo, array $params, object */ 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; - } + $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; } @@ -1399,6 +1464,9 @@ public function decodeMimeStr(string $string): string return $newString; } + /** + * @psalm-pure + */ public function isUrlEncoded(string $string): bool { $hasInvalidChars = \preg_match('#[^%a-zA-Z0-9\-_\.\+]#', $string); @@ -1414,6 +1482,8 @@ public function isUrlEncoded(string $string): bool * * @return string RFC 3339 compliant format or original (unchanged) format, * if conversation is not possible + * + * @psalm-pure */ public function parseDateTime(string $dateHeader): string { @@ -1458,6 +1528,10 @@ public function getMailMboxFormat(int $mailId): string /** * Get folders list. + * + * @return (false|mixed|string)[][] + * + * @psalm-return list */ public function getMailboxes(string $search = '*'): array { @@ -1469,6 +1543,10 @@ public function getMailboxes(string $search = '*'): array /** * Get folders list. + * + * @return (false|mixed|string)[][] + * + * @psalm-return list */ public function getSubscribedMailboxes(string $search = '*'): array { @@ -1545,9 +1623,11 @@ public function appendMessageToMailbox( /** * Returns the list of available encodings in lower case. * - * @return array mb_list_encodings() in lower case + * @return string[] + * + * @psalm-return list */ - protected function lowercase_mb_list_encodings() + protected function lowercase_mb_list_encodings(): array { $lowercase_encodings = []; $encodings = \mb_list_encodings(); @@ -1614,6 +1694,7 @@ protected function initImapStream() * @param string|0 $partNum * * @psalm-param PARTSTRUCTURE $partStructure + * @psalm-suppress InvalidArgument * * @todo refactor type checking pending resolution of https://github.com/vimeo/psalm/issues/2619 */ @@ -1635,7 +1716,7 @@ protected function initMailPart(IncomingMail $mail, object $partStructure, $part if (!empty($partStructure->parameters)) { foreach ($partStructure->parameters as $param) { $params[\strtolower($param->attribute)] = ''; - $value = isset($param->value) ? $param->value : null; + $value = $param->value ?? null; if (isset($value) && '' !== \trim($value)) { $params[\strtolower($param->attribute)] = $this->decodeMimeStr($value); } @@ -1643,7 +1724,7 @@ protected function initMailPart(IncomingMail $mail, object $partStructure, $part } if (!empty($partStructure->dparameters)) { foreach ($partStructure->dparameters as $param) { - $paramName = \strtolower(\preg_match('~^(.*?)\*~', $param->attribute, $matches) ? $matches[1] : $param->attribute); + $paramName = \strtolower(\preg_match('~^(.*?)\*~', $param->attribute, $matches) ? (!isset($matches[1]) ?: $matches[1]) : $param->attribute); if (isset($params[$paramName])) { $params[$paramName] .= $param->value; } else { @@ -1654,11 +1735,9 @@ protected function initMailPart(IncomingMail $mail, object $partStructure, $part $isAttachment = isset($params['filename']) || isset($params['name']) || isset($partStructure->id); - $dispositionAttachment = ( - isset($partStructure->disposition) && + $dispositionAttachment = (isset($partStructure->disposition) && \is_string($partStructure->disposition) && - 'attachment' === \mb_strtolower($partStructure->disposition) - ); + 'attachment' === \mb_strtolower($partStructure->disposition)); // ignore contentId on body when mail isn't multipart (https://github.com/barbushin/php-imap/issues/71) if ( @@ -1687,9 +1766,10 @@ protected function initMailPart(IncomingMail $mail, object $partStructure, $part } // Do NOT parse attachments, when getAttachmentsIgnore() is true - if ($this->getAttachmentsIgnore() + if ( + $this->getAttachmentsIgnore() && (TYPEMULTIPART !== $partStructure->type - && (TYPETEXT !== $partStructure->type || !\in_array(\mb_strtolower($partStructure->subtype), ['plain', 'html'], true))) + && (TYPETEXT !== $partStructure->type || !\in_array(\mb_strtolower($partStructure->subtype), ['plain', 'html'], true))) ) { return; } @@ -1743,7 +1823,7 @@ protected function initMailPart(IncomingMail $mail, object $partStructure, $part protected function decodeRFC2231(string $string): string { if (\preg_match("/^(.*?)'.*?'(.*?)$/", $string, $matches)) { - $data = $matches[2]; + $data = $matches[2] ?? ''; if ($this->isUrlEncoded($data)) { $string = $this->decodeMimeStr(\urldecode($data)); } @@ -1783,19 +1863,16 @@ protected function getCombinedPath(string $folder, bool $absolute = false): stri } /** - * @psalm-return array{0:string, 1:string|null}|null + * @psalm-return array{0: string, 1: null|string}|null + * + * @return (null|string)[]|null */ protected function possiblyGetEmailAndNameFromRecipient(object $recipient): ?array { if (isset($recipient->mailbox, $recipient->host)) { - /** @var mixed */ $recipientMailbox = $recipient->mailbox; - - /** @var mixed */ $recipientHost = $recipient->host; - - /** @var mixed */ - $recipientPersonal = isset($recipient->personal) ? $recipient->personal : null; + $recipientPersonal = $recipient->personal ?? null; if (!\is_string($recipientMailbox)) { throw new UnexpectedValueException('mailbox was present on argument 1 passed to '.__METHOD__.'() but was not a string!'); @@ -1823,6 +1900,10 @@ protected function possiblyGetEmailAndNameFromRecipient(object $recipient): ?arr * @psalm-param array $t * * @todo revisit implementation pending resolution of https://github.com/vimeo/psalm/issues/2619 + * + * @return (false|mixed|string)[][] + * + * @psalm-return list */ protected function possiblyGetMailboxes(array $t): array { @@ -1833,7 +1914,7 @@ protected function possiblyGetMailboxes(array $t): array throw new UnexpectedValueException('Index '.(string) $index.' of argument 1 passed to '.__METHOD__.'() corresponds to a non-object value, '.\gettype($item).' given!'); } /** @var scalar|array|object|resource|null */ - $item_name = isset($item->name) ? $item->name : null; + $item_name = $item->name ?? null; if (!isset($item->name, $item->attributes, $item->delimiter)) { throw new UnexpectedValueException('The object at index '.(string) $index.' of argument 1 passed to '.__METHOD__.'() was missing one or more of the required properties "name", "attributes", "delimiter"!'); @@ -1867,7 +1948,7 @@ protected function possiblyGetMailboxes(array $t): array protected function possiblyGetHostNameAndAddress(array $t): array { $out = [ - isset($t[0]->host) ? $t[0]->host : (isset($t[1], $t[1]->host) ? $t[1]->host : null), + $t[0]->host ?? (isset($t[1], $t[1]->host) ? $t[1]->host : null), 1 => null, ]; foreach ([0, 1] as $index) { @@ -1916,7 +1997,7 @@ protected function searchMailboxFromWithOrWithoutDisablingServerEncoding(string * * @return string */ - static function ($sender) use ($criteria) { + static function ($sender) use ($criteria): string { return $criteria.' FROM '.\mb_strtolower($sender); }, $senders diff --git a/tests/unit/AbstractLiveMailboxTest.php b/tests/unit/AbstractLiveMailboxTest.php index 9f7bf316..24e05023 100644 --- a/tests/unit/AbstractLiveMailboxTest.php +++ b/tests/unit/AbstractLiveMailboxTest.php @@ -44,7 +44,9 @@ abstract class AbstractLiveMailboxTest extends TestCase use LiveMailboxTestingTrait; /** - * @psalm-return Generator + * @psalm-return Generator + * + * @return Generator */ public function ComposeProvider(): Generator { @@ -100,9 +102,9 @@ public function testAppend( return; } - list($search_criteria) = $this->SubjectSearchCriteriaAndSubject($envelope); + [$search_criteria] = $this->SubjectSearchCriteriaAndSubject($envelope); - list($mailbox, $remove_mailbox, $path) = $this->getMailboxFromArgs( + [$mailbox, $remove_mailbox, $path] = $this->getMailboxFromArgs( $mailbox_args ); @@ -185,14 +187,14 @@ public function testAppend( protected function SubjectSearchCriteriaAndSubject(array $envelope): array { /** @var string|null */ - $subject = isset($envelope['subject']) ? $envelope['subject'] : null; + $subject = $envelope['subject'] ?? null; $this->assertIsString($subject); - $search_criteria = \sprintf('SUBJECT "%s"', (string) $subject); + $search_criteria = \sprintf('SUBJECT "%s"', $subject); /** @psalm-var array{0:string, 1:string} */ - return [$search_criteria, (string) $subject]; + return [$search_criteria, $subject]; } protected function MaybeSkipAppendTest(array $envelope): bool diff --git a/tests/unit/Fixtures/IncomingMailAttachment.php b/tests/unit/Fixtures/IncomingMailAttachment.php index 2791354b..65c31ac7 100644 --- a/tests/unit/Fixtures/IncomingMailAttachment.php +++ b/tests/unit/Fixtures/IncomingMailAttachment.php @@ -4,22 +4,22 @@ namespace PhpImap\Fixtures; -use const FILEINFO_MIME; +use const FILEINFO_MIME_TYPE; use const FILEINFO_NONE; use PhpImap\IncomingMailAttachment as Base; class IncomingMailAttachment extends Base { /** @var string|null */ - public $override_getFileInfo_mime = null; + public $override_getFileInfo_mime_type = null; public function getFileInfo(int $fileinfo_const = FILEINFO_NONE): string { if ( - FILEINFO_MIME === $fileinfo_const && - isset($this->override_getFileInfo_mime) + FILEINFO_MIME_TYPE === $fileinfo_const && + isset($this->override_getFileInfo_mime_type) ) { - return $this->override_getFileInfo_mime; + return $this->override_getFileInfo_mime_type; } return parent::getFileInfo($fileinfo_const); diff --git a/tests/unit/ImapTest.php b/tests/unit/ImapTest.php index e7355820..670eae0e 100644 --- a/tests/unit/ImapTest.php +++ b/tests/unit/ImapTest.php @@ -35,12 +35,7 @@ class ImapTest extends Base use LiveMailboxTestingTrait; /** - * @psalm-return Generator, - * 1:string, - * 2:PSALM_OPEN_ARGS, - * 3?:bool - * }> + * @psalm-return Generator<'CI ENV with invalid password'|'empty mailbox/username/password', array{0: UnexpectedValueException::class, 1: '/^IMAP error:.[AUTHENTICATIONFAILED]/'|'IMAP error:Can't open mailbox : no such mailbox', 2: array{0: HiddenString, 1: HiddenString, 2: HiddenString, 3: 0, 4: 0, 5: array}, 3?: true}, mixed, void> */ public function OpenFailure(): Generator { @@ -118,7 +113,7 @@ public function testSortEmpty( HiddenString $login, HiddenString $password ): void { - list($mailbox, $remove_mailbox, $path) = $this->getMailboxFromArgs([ + [$mailbox, $remove_mailbox, $path] = $this->getMailboxFromArgs([ $path, $login, $password, diff --git a/tests/unit/Issue509Test.php b/tests/unit/Issue509Test.php index 739b8a40..0d5aa9b7 100644 --- a/tests/unit/Issue509Test.php +++ b/tests/unit/Issue509Test.php @@ -14,7 +14,7 @@ class Issue509Test extends TestCase { - const base64 = + public const base64 = 'vsiz58fPvcq0z7HuLiC05MDlx9jB1rzFvK0gsKi758fVtM+02S4NCsDMt7EgwM/AuiC16b7uILq7 wPvAzCC++L3AtM+02S4NCsDMwM8gvu62u7DUIMfPvcO0wsH2ILHDsd3H1bTPtNkuDQrBprChIMik vcMgsPi9xMD7wLi3ziCw7b/rtce++r3AtM+x7j8NCg0KU2VudCBmcm9tIE1haWw8aHR0cHM6Ly9n @@ -41,7 +41,7 @@ class Issue509Test extends TestCase YW5ub24gRHJpdmUNCkUxNCA0QVMgTG9uZG9uDQp3d3cuY2xvdWR3b3JrZXJzLmNvbXBhbnkNCg0K DQo='; - const sha256 = + public const sha256 = '5656f5f8a872b8989ba3aaecdfbdc6311bf4c5e0219c27b3b004ce83d8ffd6f3'; public function testDecode(): void diff --git a/tests/unit/Issue519Test.php b/tests/unit/Issue519Test.php index f79caf1f..215bb15a 100644 --- a/tests/unit/Issue519Test.php +++ b/tests/unit/Issue519Test.php @@ -17,7 +17,7 @@ class Issue519Test extends TestCase { - const HEADER_VALUES = [ + public const HEADER_VALUES = [ 'inline', 'Inline', 'iNline', @@ -84,26 +84,28 @@ class Issue519Test extends TestCase 'INLINE', ]; - const CID = 'cid:foo.jpg'; + public const CID = 'cid:foo.jpg'; - const ID = 'foo.jpg'; + public const ID = 'foo.jpg'; - const SUBTYPE = 'jpeg'; + public const SUBTYPE = 'jpeg'; - const SIZE_IN_BYTES = 0; + public const SIZE_IN_BYTES = 0; - const HTML = 'foo.html'; + public const HTML = 'foo.html'; - const HTML_EMBED = ''; + public const HTML_EMBED = ''; - const MIME = 'image/jpeg'; + public const MIME_TYPE = 'image/jpeg'; - const EXPECTED_ATTACHMENT_COUNT = 1; + public const EXPECTED_ATTACHMENT_COUNT = 1; - const EXPECTED_ATTACHMENT_COUNT_AFTER_EMBED = 0; + public const EXPECTED_ATTACHMENT_COUNT_AFTER_EMBED = 0; /** - * @psalm-return array + * @psalm-return array + * + * @return string[][] */ public function provider(): array { @@ -154,7 +156,7 @@ public function test(string $header_value): void $attachment->name = self::ID; $attachment->sizeInBytes = self::SIZE_IN_BYTES; $attachment->disposition = $header_value; - $attachment->override_getFileInfo_mime = self::MIME; + $attachment->override_getFileInfo_mime_type = self::MIME_TYPE; $attachment->addDataPartInfo($part); diff --git a/tests/unit/LiveMailboxIssue250Test.php b/tests/unit/LiveMailboxIssue250Test.php index b80ad7fb..8725386c 100644 --- a/tests/unit/LiveMailboxIssue250Test.php +++ b/tests/unit/LiveMailboxIssue250Test.php @@ -37,7 +37,9 @@ class LiveMailboxIssue250Test extends AbstractLiveMailboxTest { /** - * @psalm-return Generator + * @psalm-return Generator + * + * @return Generator */ public function ComposeProvider(): Generator { diff --git a/tests/unit/LiveMailboxIssue490Test.php b/tests/unit/LiveMailboxIssue490Test.php index 059ba8a6..695bc93e 100644 --- a/tests/unit/LiveMailboxIssue490Test.php +++ b/tests/unit/LiveMailboxIssue490Test.php @@ -39,7 +39,7 @@ public function testGetTextAttachments( string $attachmentsDir, string $serverEncoding = 'UTF-8' ): void { - list($mailbox, $remove_mailbox) = $this->getMailbox( + [$mailbox, $remove_mailbox] = $this->getMailbox( $imapPath, $login, $password, @@ -54,7 +54,7 @@ public function testGetTextAttachments( 'subject' => 'barbushin/php-imap#501: '.\bin2hex(\random_bytes(16)), ]; - list($search_criteria) = $this->SubjectSearchCriteriaAndSubject( + [$search_criteria] = $this->SubjectSearchCriteriaAndSubject( $envelope ); diff --git a/tests/unit/LiveMailboxIssue501Test.php b/tests/unit/LiveMailboxIssue501Test.php index d0643da2..34ebc9ec 100644 --- a/tests/unit/LiveMailboxIssue501Test.php +++ b/tests/unit/LiveMailboxIssue501Test.php @@ -59,7 +59,7 @@ public function testGetEmptyBody( string $attachmentsDir, string $serverEncoding = 'UTF-8' ): void { - list($mailbox, $remove_mailbox) = $this->getMailbox( + [$mailbox, $remove_mailbox] = $this->getMailbox( $imapPath, $login, $password, @@ -74,7 +74,7 @@ public function testGetEmptyBody( 'subject' => 'barbushin/php-imap#501: '.\bin2hex(\random_bytes(16)), ]; - list($search_criteria) = $this->SubjectSearchCriteriaAndSubject( + [$search_criteria] = $this->SubjectSearchCriteriaAndSubject( $envelope ); diff --git a/tests/unit/LiveMailboxIssue514Test.php b/tests/unit/LiveMailboxIssue514Test.php index 91cc4b6e..1c8edba5 100644 --- a/tests/unit/LiveMailboxIssue514Test.php +++ b/tests/unit/LiveMailboxIssue514Test.php @@ -45,7 +45,7 @@ public function testEmbed( 'subject' => 'barbushin/php-imap#514--'.\bin2hex(\random_bytes(16)), ]; - list($search_criteria) = $this->SubjectSearchCriteriaAndSubject($envelope); + [$search_criteria] = $this->SubjectSearchCriteriaAndSubject($envelope); $body = [ [ @@ -97,7 +97,7 @@ public function testEmbed( $body ); - list($mailbox, $remove_mailbox, $path) = $this->getMailboxFromArgs([ + [$mailbox, $remove_mailbox, $path] = $this->getMailboxFromArgs([ $imapPath, $login, $password, diff --git a/tests/unit/LiveMailboxTest.php b/tests/unit/LiveMailboxTest.php index ee39b4e6..0261e715 100644 --- a/tests/unit/LiveMailboxTest.php +++ b/tests/unit/LiveMailboxTest.php @@ -44,9 +44,9 @@ */ class LiveMailboxTest extends AbstractLiveMailboxTest { - const RANDOM_MAILBOX_SAMPLE_SIZE = 3; + public const RANDOM_MAILBOX_SAMPLE_SIZE = 3; - const ISSUE_EXPECTED_ATTACHMENT_COUNT = [ + public const ISSUE_EXPECTED_ATTACHMENT_COUNT = [ 448 => 1, 391 => 2, ]; @@ -58,7 +58,7 @@ class LiveMailboxTest extends AbstractLiveMailboxTest */ public function testGetImapStream(HiddenString $imapPath, HiddenString $login, HiddenString $password, string $attachmentsDir, string $serverEncoding = 'UTF-8'): void { - list($mailbox, $remove_mailbox) = $this->getMailbox( + [$mailbox, $remove_mailbox] = $this->getMailbox( $imapPath, $login, $password, @@ -143,7 +143,9 @@ public function testGetImapStream(HiddenString $imapPath, HiddenString $login, H } /** - * @psalm-return Generator + * @psalm-return Generator + * + * @return Generator */ public function ComposeProvider(): Generator { @@ -336,9 +338,9 @@ public function testAppendNudgesMailboxCount( return; } - list($search_criteria) = $this->SubjectSearchCriteriaAndSubject($envelope); + [$search_criteria] = $this->SubjectSearchCriteriaAndSubject($envelope); - list($mailbox, $remove_mailbox, $path) = $this->getMailboxFromArgs( + [$mailbox, $remove_mailbox, $path] = $this->getMailboxFromArgs( $mailbox_args ); @@ -425,9 +427,9 @@ public function testAppendSingleSearchMatchesSort( return; } - list($search_criteria) = $this->SubjectSearchCriteriaAndSubject($envelope); + [$search_criteria] = $this->SubjectSearchCriteriaAndSubject($envelope); - list($mailbox, $remove_mailbox, $path) = $this->getMailboxFromArgs( + [$mailbox, $remove_mailbox, $path] = $this->getMailboxFromArgs( $mailbox_args ); @@ -523,9 +525,9 @@ public function testAppendRetrievalMatchesExpected( return; } - list($search_criteria, $search_subject) = $this->SubjectSearchCriteriaAndSubject($envelope); + [$search_criteria, $search_subject] = $this->SubjectSearchCriteriaAndSubject($envelope); - list($mailbox, $remove_mailbox, $path) = $this->getMailboxFromArgs( + [$mailbox, $remove_mailbox, $path] = $this->getMailboxFromArgs( $mailbox_args ); @@ -651,6 +653,8 @@ public function testAppendRetrievalMatchesExpected( * @param string $actual_result * * @return string + * + * @psalm-pure */ protected function ReplaceBoundaryHere( $expected_result, diff --git a/tests/unit/LiveMailboxTestingTrait.php b/tests/unit/LiveMailboxTestingTrait.php index 6f55d533..3c084347 100644 --- a/tests/unit/LiveMailboxTestingTrait.php +++ b/tests/unit/LiveMailboxTestingTrait.php @@ -26,7 +26,9 @@ trait LiveMailboxTestingTrait /** * Provides constructor arguments for a live mailbox. * - * @psalm-return MAILBOX_ARGS[] + * @psalm-return array{'CI ENV'?: array{0: \ParagonIE\HiddenString\HiddenString, 1: \ParagonIE\HiddenString\HiddenString, 2: \ParagonIE\HiddenString\HiddenString, 3: string}} + * + * @return (\ParagonIE\HiddenString\HiddenString|string)[][] */ public function MailBoxProvider(): array { @@ -49,11 +51,11 @@ public function MailBoxProvider(): array * @param string $attachmentsDir * @param string $serverEncoding * - * @return mixed[] + * @return (Mailbox|\ParagonIE\HiddenString\HiddenString|string)[] * - * @psalm-return array{0:Mailbox, 1:string, 2:HiddenString} + * @psalm-return array{0: Mailbox, 1: string, 2: \ParagonIE\HiddenString\HiddenString} */ - protected function getMailbox(HiddenString $imapPath, HiddenString $login, HiddenString $password, $attachmentsDir, $serverEncoding = 'UTF-8') + protected function getMailbox(HiddenString $imapPath, HiddenString $login, HiddenString $password, $attachmentsDir, $serverEncoding = 'UTF-8'): array { $mailbox = new Mailbox($imapPath->getString(), $login->getString(), $password->getString(), $attachmentsDir, $serverEncoding); @@ -75,14 +77,14 @@ protected function getMailbox(HiddenString $imapPath, HiddenString $login, Hidde */ protected function getMailboxFromArgs(array $mailbox_args): array { - list($path, $username, $password, $attachments_dir) = $mailbox_args; + [$path, $username, $password, $attachments_dir] = $mailbox_args; return $this->getMailbox( $path, $username, $password, $attachments_dir, - isset($mailbox_args[4]) ? $mailbox_args[4] : 'UTF-8' + $mailbox_args[4] ?? 'UTF-8' ); } } diff --git a/tests/unit/LiveMailboxWithManualSetupTest.php b/tests/unit/LiveMailboxWithManualSetupTest.php index 5624b0c0..ba3ce9a0 100644 --- a/tests/unit/LiveMailboxWithManualSetupTest.php +++ b/tests/unit/LiveMailboxWithManualSetupTest.php @@ -25,7 +25,7 @@ class LiveMailboxWithManualSetupTest extends AbstractLiveMailboxTest { /** - * @return Generator + * @psalm-return Generator */ public function RelativeToRootPathProvider(): Generator { @@ -35,7 +35,7 @@ public function RelativeToRootPathProvider(): Generator } /** - * @return Generator + * @psalm-return Generator */ public function statusProviderAbsolutePath(): Generator { @@ -61,7 +61,7 @@ public function statusProviderAbsolutePath(): Generator public function testAbsolutePathStatusFromConstruction( array $mailbox_args ): void { - list($mailbox) = $this->getMailboxFromArgs($mailbox_args); + [$mailbox] = $this->getMailboxFromArgs($mailbox_args); $mailbox->statusMailbox(); diff --git a/tests/unit/MailboxTest.php b/tests/unit/MailboxTest.php index d3bd6387..507ff596 100644 --- a/tests/unit/MailboxTest.php +++ b/tests/unit/MailboxTest.php @@ -30,7 +30,7 @@ final class MailboxTest extends TestCase { - const ANYTHING = 0; + public const ANYTHING = 0; /** * Holds the imap path. @@ -92,9 +92,11 @@ public function testConstructorTrimsPossibleVariables(): void } /** - * @psalm-return list + * @psalm-return non-empty-list + * + * @return string[][] */ - public function SetAndGetServerEncodingProvider() + public function SetAndGetServerEncodingProvider(): array { $data = [ ['UTF-8'], @@ -162,7 +164,9 @@ public function testServerEncodingUppersSetting(): void /** * Provides test data for testing server encodings. * - * @return array + * @return (bool|string)[][] + * + * @psalm-return array{UTF-7: array{0: true, 1: 'UTF-7'}, UTF7-IMAP: array{0: true, 1: 'UTF7-IMAP'}, UTF-8: array{0: true, 1: 'UTF-8'}, ASCII: array{0: true, 1: 'ASCII'}, US-ASCII: array{0: true, 1: 'US-ASCII'}, ISO-8859-1: array{0: true, 1: 'ISO-8859-1'}, UTF7: array{0: false, 1: 'UTF7'}, UTF-7-IMAP: array{0: false, 1: 'UTF-7-IMAP'}, UTF-7IMAP: array{0: false, 1: 'UTF-7IMAP'}, UTF8: array{0: false, 1: 'UTF8'}, USASCII: array{0: false, 1: 'USASCII'}, ASC11: array{0: false, 1: 'ASC11'}, ISO-8859-0: array{0: false, 1: 'ISO-8859-0'}, ISO-8855-1: array{0: false, 1: 'ISO-8855-1'}, ISO-8859: array{0: false, 1: 'ISO-8859'}} */ public function serverEncodingProvider(): array { @@ -254,7 +258,9 @@ public function testPathDelimiterHasADefault(): void /** * Provides test data for testing path delimiter. * - * @psalm-return array{0:string}[] + * @psalm-return array{0: array{0: '0'}, 1: array{0: '1'}, 2: array{0: '2'}, 3: array{0: '3'}, 4: array{0: '4'}, 5: array{0: '5'}, 6: array{0: '6'}, 7: array{0: '7'}, 8: array{0: '8'}, 9: array{0: '9'}, a: array{0: 'a'}, b: array{0: 'b'}, c: array{0: 'c'}, d: array{0: 'd'}, e: array{0: 'e'}, f: array{0: 'f'}, g: array{0: 'g'}, h: array{0: 'h'}, i: array{0: 'i'}, j: array{0: 'j'}, k: array{0: 'k'}, l: array{0: 'l'}, m: array{0: 'm'}, n: array{0: 'n'}, o: array{0: 'o'}, p: array{0: 'p'}, q: array{0: 'q'}, r: array{0: 'r'}, s: array{0: 's'}, t: array{0: 't'}, u: array{0: 'u'}, v: array{0: 'v'}, w: array{0: 'w'}, x: array{0: 'x'}, y: array{0: 'y'}, z: array{0: 'z'}, !: array{0: '!'}, '\\': array{0: '\'}, $: array{0: '$'}, %: array{0: '%'}, §: array{0: '§'}, &: array{0: '&'}, /: array{0: '/'}, (: array{0: '('}, ): array{0: ')'}, =: array{0: '='}, #: array{0: '#'}, ~: array{0: '~'}, *: array{0: '*'}, +: array{0: '+'}, ,: array{0: ','}, ;: array{0: ';'}, '.': array{0: '.'}, ':': array{0: ':'}, <: array{0: '<'}, >: array{0: '>'}, |: array{0: '|'}, _: array{0: '_'}} + * + * @return string[][] */ public function pathDelimiterProvider(): array { @@ -390,7 +396,9 @@ public function testSetAttachmentsIgnore(bool $paramValue): void /** * Provides test data for testing encoding. * - * @psalm-return array + * @psalm-return array{Avañe’ẽ: array{0: 'Avañe’ẽ'}, azərbaycanca: array{0: 'azərbaycanca'}, Bokmål: array{0: 'Bokmål'}, chiCheŵa: array{0: 'chiCheŵa'}, Deutsch: array{0: 'Deutsch'}, 'U.S. English': array{0: 'U.S. English'}, français: array{0: 'français'}, 'Éléments envoyés': array{0: 'Éléments envoyés'}, føroyskt: array{0: 'føroyskt'}, Kĩmĩrũ: array{0: 'Kĩmĩrũ'}, Kɨlaangi: array{0: 'Kɨlaangi'}, oʼzbekcha: array{0: 'oʼzbekcha'}, Plattdüütsch: array{0: 'Plattdüütsch'}, română: array{0: 'română'}, Sängö: array{0: 'Sängö'}, 'Tiếng Việt': array{0: 'Tiếng Việt'}, ɔl-Maa: array{0: 'ɔl-Maa'}, Ελληνικά: array{0: 'Ελληνικά'}, Ўзбек: array{0: 'Ўзбек'}, Азәрбајҹан: array{0: 'Азәрбајҹан'}, Српски: array{0: 'Српски'}, русский: array{0: 'русский'}, 'ѩзыкъ словѣньскъ': array{0: 'ѩзыкъ словѣньскъ'}, العربية: array{0: 'العربية'}, नेपाली: array{0: 'नेपाली'}, 日本語: array{0: '日本語'}, 简体中文: array{0: '简体中文'}, 繁體中文: array{0: '繁體中文'}, 한국어: array{0: '한국어'}, ąčęėįšųūžĄČĘĖĮŠŲŪŽ: array{0: 'ąčęėįšųūžĄČĘĖĮŠŲŪŽ'}} + * + * @return string[][] */ public function encodingTestStringsProvider(): array { @@ -456,7 +464,9 @@ public function testMimeDecodingReturnsCorrectValues(string $str): void /** * Provides test data for testing parsing datetimes. * - * @psalm-return array + * @psalm-return array{'Sun, 14 Aug 2005 16:13:03 +0000 (CEST)': array{0: '2005-08-14T16:13:03+00:00', 1: 1124035983}, 'Sun, 14 Aug 2005 16:13:03 +0000': array{0: '2005-08-14T16:13:03+00:00', 1: 1124035983}, 'Sun, 14 Aug 2005 16:13:03 +1000 (CEST)': array{0: '2005-08-14T06:13:03+00:00', 1: 1123999983}, 'Sun, 14 Aug 2005 16:13:03 +1000': array{0: '2005-08-14T06:13:03+00:00', 1: 1123999983}, 'Sun, 14 Aug 2005 16:13:03 -1000': array{0: '2005-08-15T02:13:03+00:00', 1: 1124071983}, 'Sun, 14 Aug 2005 16:13:03 +1100 (CEST)': array{0: '2005-08-14T05:13:03+00:00', 1: 1123996383}, 'Sun, 14 Aug 2005 16:13:03 +1100': array{0: '2005-08-14T05:13:03+00:00', 1: 1123996383}, 'Sun, 14 Aug 2005 16:13:03 -1100': array{0: '2005-08-15T03:13:03+00:00', 1: 1124075583}, '14 Aug 2005 16:13:03 +1000 (CEST)': array{0: '2005-08-14T06:13:03+00:00', 1: 1123999983}, '14 Aug 2005 16:13:03 +1000': array{0: '2005-08-14T06:13:03+00:00', 1: 1123999983}, '14 Aug 2005 16:13:03 -1000': array{0: '2005-08-15T02:13:03+00:00', 1: 1124071983}} + * + * @return (int|string)[][] */ public function datetimeProvider(): array { @@ -493,7 +503,9 @@ public function testParsedDateDifferentTimeZones(string $dateToParse, int $epoch /** * Provides test data for testing parsing invalid / unparseable datetimes. * - * @psalm-return array + * @psalm-return array{'Sun, 14 Aug 2005 16:13:03 +9000 (CEST)': array{0: 'Sun, 14 Aug 2005 16:13:03 +9000 (CEST)'}, 'Sun, 14 Aug 2005 16:13:03 +9000': array{0: 'Sun, 14 Aug 2005 16:13:03 +9000'}, 'Sun, 14 Aug 2005 16:13:03 -9000': array{0: 'Sun, 14 Aug 2005 16:13:03 -9000'}} + * + * @return string[][] */ public function invalidDatetimeProvider(): array { @@ -529,7 +541,7 @@ public function testParsedDateTimeWithEmptyHeaderDate(): void * * @return string[][] * - * @psalm-return list + * @psalm-return array{0: array{0: '=?iso-8859-1?Q?Sebastian_Kr=E4tzig?= ', 1: 'Sebastian Krätzig '}, 1: array{0: '=?iso-8859-1?Q?Sebastian_Kr=E4tzig?=', 1: 'Sebastian Krätzig'}, 2: array{0: 'sebastian.kraetzig', 1: 'sebastian.kraetzig'}, 3: array{0: '=?US-ASCII?Q?Keith_Moore?= ', 1: 'Keith Moore '}, 4: array{0: ' ', 1: ' '}, 5: array{0: '=?ISO-8859-1?Q?Max_J=F8rn_Simsen?= ', 1: 'Max Jørn Simsen '}, 6: array{0: '=?ISO-8859-1?Q?Andr=E9?= Muster ', 1: 'André Muster '}, 7: array{0: '=?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?= =?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=', 1: 'If you can read this you understand the example.'}, 8: array{0: '', 1: ''}} */ public function mimeEncodingProvider(): array { @@ -600,7 +612,7 @@ public function testSetTimeouts(string $assertMethod, int $timeout, array $types /** * Provides test data for testing connection args. * - * @psalm-return Generator}> + * @psalm-return Generator}, mixed, void> */ public function connectionArgsProvider(): Generator { @@ -684,7 +696,9 @@ public function testSetConnectionArgs(string $assertMethod, int $option, int $re /** * Provides test data for testing mime string decoding. * - * @psalm-return array + * @psalm-return array{'': array{0: '', 1: ''}, '': array{0: '', 1: ''}, '': array{0: '', 1: ''}, '': array{0: '', 1: ''}, 'Some subject here 😘': array{0: '=?UTF-8?q?Some_subject_here_?= =?UTF-8?q?=F0=9F=98=98?=', 1: 'Some subject here 😘'}, mountainguan测试: array{0: '=?UTF-8?Q?mountainguan=E6=B5=8B=E8=AF=95?=', 1: 'mountainguan测试'}, 'This is the Euro symbol \'\'.': array{0: 'This is the Euro symbol ''.', 1: 'This is the Euro symbol ''.'}, 'Some subject here 😘 US-ASCII': array{0: '=?UTF-8?q?Some_subject_here_?= =?UTF-8?q?=F0=9F=98=98?=', 1: 'Some subject here 😘', 2: 'US-ASCII'}, 'mountainguan测试 US-ASCII': array{0: '=?UTF-8?Q?mountainguan=E6=B5=8B=E8=AF=95?=', 1: 'mountainguan测试', 2: 'US-ASCII'}, 'مقتطفات من: صن تزو. \"فن الحرب\". كتب أبل. Something': array{0: 'مقتطفات من: صن تزو. "فن الحرب". كتب أبل. Something', 1: 'مقتطفات من: صن تزو. "فن الحرب". كتب أبل. Something'}, '(事件单编号:TESTA-111111)(通报)入口有陌生人': array{0: '=?utf-8?b?KOS6i+S7tuWNlee8luWPtzpURVNUQS0xMTExMTEpKOmAmuaKpSnl?= =?utf-8?b?haXlj6PmnInpmYznlJ/kuro=?=', 1: '(事件单编号:TESTA-111111)(通报)入口有陌生人'}} + * + * @return string[][] */ public function mimeStrDecodingProvider(): array { @@ -719,13 +733,15 @@ public function testDecodeMimeStr(string $str, string $expectedStr, string $serv /** * Provides test data for testing base64 string decoding. * - * @psalm-return list + * @psalm-return array{0: array{0: 'bm8tcmVwbHlAZXhhbXBsZS5jb20=', 1: 'no-reply@example.com'}, 1: array{0: 'TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGludWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=', 1: 'Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure.'}, 2: array{0: 'SSBjYW4gZWF0IGdsYXNzIGFuZCBpdCBkb2VzIG5vdCBodXJ0IG1lLg==', 1: 'I can eat glass and it does not hurt me.'}, 3: array{0: '77u/4KSV4KS+4KSa4KSCIOCktuCkleCljeCkqOCli+CkruCljeCkr+CkpOCljeCkpOClgeCkruCljSDgpaQg4KSo4KWL4KSq4KS54KS/4KSo4KS44KWN4KSk4KS/IOCkruCkvuCkruCljSDgpaU=', 1: 'काचं शक्नोम्यत्तुम् । नोपहिनस्ति माम् ॥'}, 4: array{0: 'SmUgcGV1eCBtYW5nZXIgZHUgdmVycmUsIMOnYSBuZSBtZSBmYWl0IHBhcyBtYWwu', 1: 'Je peux manger du verre, ça ne me fait pas mal.'}, 5: array{0: 'UG90IHPEgyBtxINuw6JuYyBzdGljbMSDIMiZaSBlYSBudSBtxIMgcsSDbmXImXRlLg==', 1: 'Pot să mănânc sticlă și ea nu mă rănește.'}, 6: array{0: '5oiR6IO95ZCe5LiL546755KD6ICM5LiN5YK36Lqr6auU44CC', 1: '我能吞下玻璃而不傷身體。'}} + * + * @return string[][] */ - public function Base64DecodeProvider() + public function Base64DecodeProvider(): array { return [ ['bm8tcmVwbHlAZXhhbXBsZS5jb20=', 'no-reply@example.com'], - ['TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGludWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4K', 'Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure.'."\n"], + ['TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGludWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=', 'Man is distinguished, not only by his reason, but by this singular passion from other animals, which is a lust of the mind, that by a perseverance of delight in the continued and indefatigable generation of knowledge, exceeds the short vehemence of any carnal pleasure.'], ['SSBjYW4gZWF0IGdsYXNzIGFuZCBpdCBkb2VzIG5vdCBodXJ0IG1lLg==', 'I can eat glass and it does not hurt me.'], ['77u/4KSV4KS+4KSa4KSCIOCktuCkleCljeCkqOCli+CkruCljeCkr+CkpOCljeCkpOClgeCkruCljSDgpaQg4KSo4KWL4KSq4KS54KS/4KSo4KS44KWN4KSk4KS/IOCkruCkvuCkruCljSDgpaU=', 'काचं शक्नोम्यत्तुम् । नोपहिनस्ति माम् ॥'], ['SmUgcGV1eCBtYW5nZXIgZHUgdmVycmUsIMOnYSBuZSBtZSBmYWl0IHBhcyBtYWwu', 'Je peux manger du verre, ça ne me fait pas mal.'], @@ -744,7 +760,9 @@ public function testBase64Decode(string $input, string $expected): void } /** - * @psalm-return list, 3:string}> + * @psalm-return array{0: array{0: string, 1: '', 2: Exceptions\InvalidParameterException::class, 3: 'setAttachmentsDir() expects a string as first parameter!'}, 1: array{0: string, 1: ' ', 2: Exceptions\InvalidParameterException::class, 3: 'setAttachmentsDir() expects a string as first parameter!'}, 2: array{0: string, 1: string, 2: Exceptions\InvalidParameterException::class, 3: string}} + * + * @return string[][] */ public function attachmentDirFailureProvider(): array {