diff --git a/psalm.baseline.xml b/psalm.baseline.xml
index f20869f2..2bcc9e83 100644
--- a/psalm.baseline.xml
+++ b/psalm.baseline.xml
@@ -27,91 +27,22 @@
\is_int($retriesNum)
\is_array($params)
-
- $dataInfo
- $attachment
- $dataInfo
- $attachment
- "imap_$methodShortName"
-
-
- $imapStream
-
-
- object
-
$mail->id
$mail->id
$option && FT_PREFETCHTEXT
-
- new $throwExceptionClass("IMAP method imap_$methodShortName() failed with error: ".implode('. ', $errors))
-
-
- throw new $throwExceptionClass("IMAP method imap_$methodShortName() failed with error: ".implode('. ', $errors));
-
-
- string
-
-
- $delimiter
- $serverEncoding
- $imapSearchOption
- $maxAttempts
- $milliseconds
+
$str
$str
$mailId
$mailId
$mailId
- $prefix
- $index
- $fullPrefix
- $partStructure
- $markAsSeen
- $emlParse
- $toCharset
- $string
- $string
- $charset
-
- $imapPath
- $imapLogin
- $imapPassword
- $imapOAuthAccessToken
- $imapSearchOption
- $connectionRetry
- $connectionRetryDelay
- $imapOptions
- $imapRetriesNum
- $imapParams
- $serverEncoding
- $expungeOnDisconnect
- $timeouts
- $attachmentsIgnore
- $pathDelimiter
- $imapStream
-
-
- initImapStreamWithRetry
+
initMailPart
- isUrlEncoded
- decodeRFC2231
- imap
-
- $this->imapStream
- $serverEncoding
- $imapSearchOption
- $this->connectionRetryDelay * 1000
- $this->imapPath
- $this->imapLogin
- $this->imapPassword
- $this->imapOptions
- $this->imapRetriesNum
- $this->imapParams
+
$mail->subject
$mail->subject
$mail->from
@@ -155,10 +86,11 @@
$param->attribute
$param->attribute
preg_match('~^(.*?)\*~', $param->attribute, $matches) ? $matches[1] : $param->attribute
- $partStructure
$partStructure->subtype
- $partStructure
- $emlParse
+ $subPartStructure
+ $subPartStructure
+ $subPartStructure
+ $subPartStructure
$partStructure->subtype
$partStructure->disposition
$partStructure->subtype
@@ -172,22 +104,7 @@
$partStructure->id
$element->charset
$element->text
- $fromCharset
- $toCharset
- $string
- $string
- $string
- $charset
- $this->imapPath
- $this->imapPath
- $this->imapPath
- $this->imapPath
- $this->imapPath
-
- $types
- $args
-
$head->from[0]
$head->from[1]
@@ -200,20 +117,13 @@
$head->sender[1]
$head->sender[0]
-
- $mailbox_info
+
$value
- $retry
- $timeout
- $folders
- $folder
- $mails
$mail
$to
$cc
$bcc
$replyTo
- $mailStructure
$param
$param
$params[$paramName]
@@ -221,47 +131,17 @@
$fileName
$fileName
$fileName
- $fileName
$element
- $fromCharset
- $toCharset
$item
- $item
- $arg
- $result
-
- string
- string
- string
- string
- bool
+
string
string
- resource|null
- string
- string
- stdClass
- stdClass
- array
- array
- array
- array
- object
- array
- int
- array
int
- int
- string
- IncomingMailAttachment[]
- string
- string
+ int|false
+ IncomingMailAttachment
-
- $retry
- $this->connectionRetryDelay
- $this->imapPath
+
$head->from[0]->mailbox
$head->sender[0]->mailbox
$to->mailbox
@@ -272,18 +152,11 @@
$bcc->host
$replyTo->mailbox
$replyTo->host
- $prefix
- $prefix
- $prefix
- $prefix
- $index
$params[$paramName]
$subPartNum
$subPartNum
$fileName
$fileExt
- $this->imapPath
- $this->imapPath
$mail
@@ -291,7 +164,7 @@
$mail
$mail
-
+
$mail->subject
$mail->from
$mail->sender
@@ -322,40 +195,13 @@
$replyTo->mailbox
$replyTo->host
$replyTo->personal
- $mailStructure->parts
- $mailStructure->parts
- $partStructure->encoding
- $partStructure->parameters
- $partStructure->parameters
$param->value
$param->value
$param->attribute
- $partStructure->dparameters
- $partStructure->dparameters
$param->attribute
$param->attribute
$param->value
$param->value
- $partStructure->type
- $partStructure->subtype
- $partStructure->disposition
- $partStructure->type
- $partStructure->subtype
- $partStructure->parts
- $partStructure->parts
- $partStructure->type
- $partStructure->subtype
- $partStructure->disposition
- $partStructure->type
- $partStructure->subtype
- $partStructure->disposition
- $partStructure->subtype
- $partStructure->disposition
- $partStructure->type
- $partStructure->subtype
- $partStructure->ifdisposition
- $partStructure->disposition
- $partStructure->type
$element->text
$element->charset
$element->charset
@@ -364,48 +210,19 @@
$item->name
$item->attributes
$item->delimiter
- $item->name
- $item->attributes
- $item->delimiter
-
- $this->imapOAuthAccessToken
- $this->pathDelimiter
- $this->serverEncoding
- $this->imapSearchOption
- $this->attachmentsIgnore
- $this->imapLogin
- $this->imapStream
+
$str
$str
- $this->imap('check')
- $this->imap('status', [$this->imapPath, SA_ALL])
- $folders
- $this->imap('search', [$criteria, $this->imapSearchOption]) ?: []
- $this->imap('search', [$criteria, $this->imapSearchOption, $this->getServerEncoding()]) ?: []
- $mails
- $this->imap('headers')
- $this->imap('mailboxmsginfo')
- $this->imap('sort', [$criteria, $reverse, $this->imapSearchOption, $searchCriteria])
- $this->imap('num_msg')
- $this->imap('get_quotaroot', $quota_root)
isset($quota['STORAGE']['limit']) ? $quota['STORAGE']['limit'] : 0
isset($quota['STORAGE']['usage']) ? $quota['STORAGE']['usage'] : 0
- $this->imap('fetchbody', [$msgId, '', $options])
- $this->imapPath
- $this->imapPath
-
- strpos($name, '}')
- strpos($name, '}')
- $posConnectionDefinitionEnd
-
-
- $this->getImapStream()
- $this->getImapStream()
- $this->getImapStream()
- $this->getImapStream()
-
+
+ $mailStructure
+
+
+ $mailStructure->parts
+
$ccStrings
$bccStrings
@@ -454,17 +271,19 @@
subscribeMailbox
unsubscribeMailbox
-
+
+ $dataInfo
$mailId
$emlOrigin
-
+
!\is_int($retriesNum) or $retriesNum < 0
\is_int($retriesNum)
null != $params and !empty($params)
- \is_resource($imapStream)
- $imapStream && \is_resource($imapStream)
+
+ list<scalar|array|object|resource|null>|string
+
$value
diff --git a/src/PhpImap/Mailbox.php b/src/PhpImap/Mailbox.php
index 4ce883e4..9e3e3094 100644
--- a/src/PhpImap/Mailbox.php
+++ b/src/PhpImap/Mailbox.php
@@ -19,23 +19,59 @@
*/
class Mailbox
{
+ /** @var string */
protected $imapPath;
+
+ /** @var string */
protected $imapLogin;
+
+ /** @var string */
protected $imapPassword;
+
+ /** @var string|null */
protected $imapOAuthAccessToken = null;
+
+ /** @var int */
protected $imapSearchOption = SE_UID;
+
+ /** @var int */
protected $connectionRetry = 0;
+
+ /** @var int */
protected $connectionRetryDelay = 100;
+
+ /** @var int */
protected $imapOptions = 0;
+
+ /** @var int */
protected $imapRetriesNum = 0;
+
+ /** @var array */
protected $imapParams = [];
+
+ /** @var string */
protected $serverEncoding = 'UTF-8';
+
/** @var string|null */
protected $attachmentsDir = null;
+
+ /** @var bool */
protected $expungeOnDisconnect = true;
+
+ /**
+ * @var int[]
+ *
+ * @psalm-var array{1?:int, 2?:int, 3?:int, 4?:int}
+ */
protected $timeouts = [];
+
+ /** @var bool */
protected $attachmentsIgnore = false;
+
+ /** @var string */
protected $pathDelimiter = '.';
+
+ /** @var resource|null */
private $imapStream;
/**
@@ -79,7 +115,7 @@ protected function _oauthAuthentication()
{
$oauth_command = 'A AUTHENTICATE XOAUTH2 '.$this->_constructAuthString();
- $oauth_result = fwrite($this->imapStream, $oauth_command);
+ $oauth_result = fwrite($this->getImapStream(), $oauth_command);
if (false === $oauth_result) {
throw new Exception('Could not authenticate using OAuth!');
@@ -124,7 +160,7 @@ public function setOAuthToken($access_token)
/**
* Gets the OAuth Token for the authentication.
*
- * @return string $access_token OAuth Access Token
+ * @return string|null $access_token OAuth Access Token
*/
public function getOAuthToken()
{
@@ -160,7 +196,7 @@ public function getPathDelimiter()
/**
* Validates the given path delimiter character.
*
- * @param string Path delimiter
+ * @param string $delimiter Path delimiter
*
* @return bool true (supported) or false (unsupported)
*/
@@ -188,7 +224,7 @@ public function getServerEncoding()
/**
* Sets / Changes the server encoding.
*
- * @param string Server encoding (eg. 'UTF-8')
+ * @param string $serverEncoding Server encoding (eg. 'UTF-8')
*
* @throws InvalidParameterException
*/
@@ -208,7 +244,7 @@ public function setServerEncoding($serverEncoding)
/**
* Returns the current set IMAP search option.
*
- * @return string IMAP search option (eg. 'SE_UID')
+ * @return int IMAP search option (eg. 'SE_UID')
*/
public function getImapSearchOption()
{
@@ -218,17 +254,17 @@ public function getImapSearchOption()
/**
* Sets / Changes the IMAP search option.
*
- * @param string IMAP search option (eg. 'SE_UID')
+ * @param int $imapSearchOption IMAP search option (eg. 'SE_UID')
+ *
+ * @psalm-param 1|2 $imapSearchOptions
*
* @throws InvalidParameterException
*/
public function setImapSearchOption($imapSearchOption)
{
- $imapSearchOption = strtoupper(trim($imapSearchOption));
-
$supported_options = [SE_FREE, SE_UID];
- if (!\in_array($imapSearchOption, $supported_options)) {
+ if (!\in_array($imapSearchOption, $supported_options, true)) {
throw new InvalidParameterException('"'.$imapSearchOption.'" is not supported by setImapSearchOption(). Supported options are SE_FREE and SE_UID.');
}
@@ -269,6 +305,8 @@ public function getAttachmentsIgnore()
* @param int $timeout Timeout in seconds
* @param array $types One of the following: IMAP_OPENTIMEOUT, IMAP_READTIMEOUT, IMAP_WRITETIMEOUT, IMAP_CLOSETIMEOUT
*
+ * @psalm-param list<1|2|3|4> $types
+ *
* @throws InvalidParameterException
*/
public function setTimeouts($timeout, $types = [IMAP_OPENTIMEOUT, IMAP_READTIMEOUT, IMAP_WRITETIMEOUT, IMAP_CLOSETIMEOUT])
@@ -281,6 +319,7 @@ public function setTimeouts($timeout, $types = [IMAP_OPENTIMEOUT, IMAP_READTIMEO
throw new InvalidParameterException('You have provided at least one unsupported timeout type. Supported types are: IMAP_OPENTIMEOUT, IMAP_READTIMEOUT, IMAP_WRITETIMEOUT, IMAP_CLOSETIMEOUT');
}
+ /** @var array{1?:int, 2?:int, 3?:int, 4?:int} */
$this->timeouts = array_fill_keys($types, $timeout);
}
@@ -367,7 +406,7 @@ public function getAttachmentsDir()
return $this->attachmentsDir;
}
- /*
+ /**
* Sets / Changes the attempts / retries to connect
* @param int $maxAttempts
* @return void
@@ -377,7 +416,7 @@ public function setConnectionRetry($maxAttempts)
$this->connectionRetry = $maxAttempts;
}
- /*
+ /**
* Sets / Changes the delay between each attempt / retry to connect
* @param int $milliseconds
* @return void
@@ -392,7 +431,7 @@ public function setConnectionRetryDelay($milliseconds)
*
* @param bool $forceConnection Initialize connection if it's not initialized
*
- * @return resource|null
+ * @return resource
*/
public function getImapStream($forceConnection = true)
{
@@ -403,9 +442,15 @@ public function getImapStream($forceConnection = true)
}
}
+ /** @var resource */
return $this->imapStream;
}
+ public function hasImapStream() : bool
+ {
+ return \is_resource($this->imapStream) && imap_ping($this->imapStream);
+ }
+
/**
* Returns the provided string in UTF7-IMAP encoded format.
*
@@ -454,6 +499,7 @@ public function switchMailbox($imapPath)
$this->imap('reopen', $this->imapPath);
}
+ /** @return resource */
protected function initImapStreamWithRetry()
{
$retry = $this->connectionRetry;
@@ -471,7 +517,7 @@ protected function initImapStreamWithRetry()
/**
* Open an IMAP stream to a mailbox.
*
- * @return object IMAP stream on success
+ * @return resource IMAP stream on success
*
* @throws Exception if an error occured
*/
@@ -507,9 +553,8 @@ protected function initImapStream()
*/
public function disconnect()
{
- $imapStream = $this->getImapStream(false);
- if ($imapStream && \is_resource($imapStream)) {
- $this->imap('close', [$imapStream, $this->expungeOnDisconnect ? CL_EXPUNGE : 0], false, null);
+ if ($this->hasImapStream()) {
+ $this->imap('close', [$this->getImapStream(false), $this->expungeOnDisconnect ? CL_EXPUNGE : 0], false, null);
}
}
@@ -539,6 +584,7 @@ public function setExpungeOnDisconnect($isEnabled)
*/
public function checkMailbox()
{
+ /** @var stdClass */
return $this->imap('check');
}
@@ -559,11 +605,14 @@ public function createMailbox($name)
*
* @param string $name Name of mailbox, which you want to delete (eg. 'PhpImap')
*
+ * @return bool
+ *
* @see imap_deletemailbox()
*/
public function deleteMailbox($name)
{
- $this->imap('deletemailbox', $this->getCombinedPath($name));
+ /** @var bool */
+ return $this->imap('deletemailbox', $this->getCombinedPath($name));
}
/**
@@ -587,6 +636,7 @@ public function renameMailbox($oldName, $newName)
*/
public function statusMailbox()
{
+ /** @var stdClass */
return $this->imap('status', [$this->imapPath, SA_ALL]);
}
@@ -602,12 +652,10 @@ public function statusMailbox()
*/
public function getListingFolders($pattern = '*')
{
+ /** @var string[] */
$folders = $this->imap('list', [$this->imapPath, $pattern]) ?: [];
- foreach ($folders as &$folder) {
- $folder = $this->decodeStringFromUtf7ImapToUtf8($folder);
- }
- return $folders;
+ return array_map([$this, 'decodeStringFromUtf7ImapToUtf8'], $folders);
}
/**
@@ -622,9 +670,11 @@ public function getListingFolders($pattern = '*')
public function searchMailbox($criteria = 'ALL', $disableServerEncoding = false)
{
if ($disableServerEncoding) {
+ /** @var array */
return $this->imap('search', [$criteria, $this->imapSearchOption]) ?: [];
}
+ /** @var array */
return $this->imap('search', [$criteria, $this->imapSearchOption, $this->getServerEncoding()]) ?: [];
}
@@ -811,6 +861,7 @@ public function getMailsInfo(array $mailsIds)
}
}
+ /** @var array */
return $mails;
}
@@ -825,6 +876,7 @@ public function getMailsInfo(array $mailsIds)
*/
public function getMailboxHeaders()
{
+ /** @var array */
return $this->imap('headers');
}
@@ -847,6 +899,7 @@ public function getMailboxHeaders()
*/
public function getMailboxInfo()
{
+ /** @var object */
return $this->imap('mailboxmsginfo');
}
@@ -870,6 +923,7 @@ public function getMailboxInfo()
*/
public function sortMails($criteria = SORTARRIVAL, $reverse = true, $searchCriteria = 'ALL')
{
+ /** @var array */
return $this->imap('sort', [$criteria, $reverse, $this->imapSearchOption, $searchCriteria]);
}
@@ -882,6 +936,7 @@ public function sortMails($criteria = SORTARRIVAL, $reverse = true, $searchCrite
*/
public function countMails()
{
+ /** @var int */
return $this->imap('num_msg');
}
@@ -896,6 +951,7 @@ public function countMails()
*/
protected function getQuota($quota_root = 'INBOX')
{
+ /** @var array */
return $this->imap('get_quotaroot', $quota_root);
}
@@ -918,7 +974,7 @@ public function getQuotaLimit($quota_root = 'INBOX')
*
* @param string $quota_root Should normally be in the form of which mailbox (i.e. INBOX)
*
- * @return int FALSE in the case of call failure
+ * @return int|false FALSE in the case of call failure
*/
public function getQuotaUsage($quota_root = 'INBOX')
{
@@ -942,6 +998,7 @@ public function getRawMail($msgId, $markAsSeen = true)
$options |= FT_PEEK;
}
+ /** @var string */
return $this->imap('fetchbody', [$msgId, '', $options]);
}
@@ -1050,6 +1107,9 @@ public function getMailHeader($mailId)
*
* @param \stdClass[] $messageParts
* @param \stdClass[] $flattenedParts
+ * @param string $prefix
+ * @param int $index
+ * @param bool $fullPrefix
*
* @psalm-param array $flattenedParts
*
@@ -1111,7 +1171,12 @@ public function getMail($mailId, $markAsSeen = true)
}
/**
+ * @param object $partStructure
* @param string|int $partNum
+ * @param bool $markAsSeen
+ * @param bool $emlParse
+ *
+ * @todo flesh out shape of $partStructure
*/
protected function initMailPart(IncomingMail $mail, $partStructure, $partNum, $markAsSeen = true, $emlParse = false)
{
@@ -1218,13 +1283,13 @@ protected function initMailPart(IncomingMail $mail, $partStructure, $partNum, $m
/**
* Download attachment.
*
- * @param string $dataInfo
+ * @param DataPartInfo $dataInfo
* @param array $params Array of params of mail
* @param object $partStructure Part of mail
* @param int $mailId ID of mail
* @param bool $emlOrigin True, if it indicates, that the attachment comes from an EML (mail) file
*
- * @return IncomingMailAttachment[] $attachment
+ * @return IncomingMailAttachment $attachment
*/
public function downloadAttachment(DataPartInfo $dataInfo, $params, $partStructure, $mailId, $emlOrigin = false)
{
@@ -1275,6 +1340,7 @@ public function downloadAttachment(DataPartInfo $dataInfo, $params, $partStructu
* Decodes a mime string.
*
* @param string $string MIME string to decode
+ * @param string $toCharset
*
* @return string Converted string if conversion was successful, or the original string if not
*
@@ -1289,6 +1355,7 @@ public function decodeMimeStr($string, $toCharset = 'utf-8')
$newString = '';
foreach (imap_mime_header_decode($string) as $element) {
if (isset($element->text)) {
+ /** @var string */
$fromCharset = !isset($element->charset) ? 'iso-8859-1' : $element->charset;
// Convert to UTF-8, if string has UTF-8 characters to avoid broken strings. See https://github.com/barbushin/php-imap/issues/232
$toCharset = isset($element->charset) && preg_match('/(UTF\-8)|(default)/i', $element->charset) ? 'UTF-8' : $toCharset;
@@ -1299,6 +1366,11 @@ public function decodeMimeStr($string, $toCharset = 'utf-8')
return $newString;
}
+ /**
+ * @param string $string
+ *
+ * @return bool
+ */
public function isUrlEncoded($string)
{
$hasInvalidChars = preg_match('#[^%a-zA-Z0-9\-_\.\+]#', $string);
@@ -1307,6 +1379,12 @@ public function isUrlEncoded($string)
return !$hasInvalidChars && $hasEscapedChars;
}
+ /**
+ * @param string $string
+ * @param string $charset
+ *
+ * @return string
+ */
protected function decodeRFC2231($string, $charset = 'utf-8')
{
if (preg_match("/^(.*?)'.*?'(.*?)$/", $string, $matches)) {
@@ -1450,6 +1528,8 @@ public function subscribeMailbox($mailbox)
*
* @param string $mailbox
*
+ * @return void
+ *
* @throws Exception
*/
public function unsubscribeMailbox($mailbox)
@@ -1465,10 +1545,19 @@ public function unsubscribeMailbox($mailbox)
* @param bool $prependConnectionAsFirstArg Add 'resource $imap_stream' as first argument, if set to true
* @param string|null $throwExceptionClass Name of exception class, which will be thrown in case of errors
*
+ * @psalm-param list|string $args
+ * @psalm-param class-string|null $throwExceptionClass
+ *
+ * @return scalar|array|object|resource|null
+ *
* @throws Exception
*/
public function imap($methodShortName, $args = [], $prependConnectionAsFirstArg = true, $throwExceptionClass = Exception::class)
{
+ $method_name = 'imap_'.$methodShortName;
+ if (!\function_exists($method_name)) {
+ throw new InvalidArgumentException('Argument 1 passed to '.__METHOD__.'() did not correspond to a known imap_* function');
+ }
if (!\is_array($args)) {
$args = [$args];
}
@@ -1497,13 +1586,15 @@ public function imap($methodShortName, $args = [], $prependConnectionAsFirstArg
}
imap_errors(); // flush errors
- $result = @\call_user_func_array("imap_$methodShortName", $args);
+
+ /** @var scalar|array|object|resource|null */
+ $result = @\call_user_func_array($method_name, $args);
if (!$result) {
$errors = imap_errors();
if ($errors) {
if ($throwExceptionClass) {
- throw new $throwExceptionClass("IMAP method imap_$methodShortName() failed with error: ".implode('. ', $errors));
+ throw new $throwExceptionClass("IMAP method $method_name() failed with error: ".implode('. ', $errors));
} else {
return false;
}
@@ -1533,6 +1624,10 @@ protected function getCombinedPath($folder, $absolute = false)
$folder = ('/' === $folder) ? '' : $folder;
$posConnectionDefinitionEnd = strpos($this->imapPath, '}');
+ if (false === $posConnectionDefinitionEnd) {
+ throw new UnexpectedValueException('"}" was not present in IMAP path!');
+ }
+
return substr($this->imapPath, 0, $posConnectionDefinitionEnd + 1).$folder;
}
diff --git a/tests/unit/MailboxTest.php b/tests/unit/MailboxTest.php
index a59a1abc..b33d9df2 100644
--- a/tests/unit/MailboxTest.php
+++ b/tests/unit/MailboxTest.php
@@ -213,9 +213,6 @@ public function testSetAndGetImapSearchOption()
$this->mailbox->setImapSearchOption(SE_FREE);
$this->assertEquals($this->mailbox->getImapSearchOption(), 2);
- $this->expectException(InvalidParameterException::class);
- $this->mailbox->setImapSearchOption('SE_FREE');
-
$this->expectException(InvalidParameterException::class);
$this->mailbox->setImapSearchOption(ANYTHING);
@@ -594,7 +591,7 @@ public function testMimeEncoding($str, $expected)
/**
* Provides test data for testing timeouts.
*
- * @psalm-return array}>
+ * @psalm-return array}>
*/
public function timeoutsProvider()
{
@@ -623,7 +620,7 @@ public function timeoutsProvider()
* @param int[] $types
*
* @psalm-param 'assertNull'|'expectException' $assertMethod
- * @psalm-param list $types
+ * @psalm-param list<1|2|3|4> $types
*
* @return void
*/