1164 lines
32 KiB
PHP
1164 lines
32 KiB
PHP
<?php
|
|
/**
|
|
* @author Barbushin Sergey http://linkedin.com/in/barbushin
|
|
* @author BAPCLTD-Marv
|
|
*/
|
|
declare(strict_types=1);
|
|
|
|
namespace PhpImap;
|
|
|
|
use const CL_EXPUNGE;
|
|
use const IMAP_CLOSETIMEOUT;
|
|
use const IMAP_OPENTIMEOUT;
|
|
use const IMAP_READTIMEOUT;
|
|
use const IMAP_WRITETIMEOUT;
|
|
use InvalidArgumentException;
|
|
use const NIL;
|
|
use const PHP_MAJOR_VERSION;
|
|
use PhpImap\Exceptions\ConnectionException;
|
|
use const SE_FREE;
|
|
use const SORTARRIVAL;
|
|
use const SORTCC;
|
|
use const SORTDATE;
|
|
use const SORTFROM;
|
|
use const SORTSIZE;
|
|
use const SORTSUBJECT;
|
|
use const SORTTO;
|
|
use stdClass;
|
|
use Throwable;
|
|
use UnexpectedValueException;
|
|
|
|
/**
|
|
* @psalm-type PARTSTRUCTURE_PARAM = object{attribute:string, value?:string}
|
|
* @psalm-type PARTSTRUCTURE = object{
|
|
* id?:string,
|
|
* encoding:int|mixed,
|
|
* partStructure:object[],
|
|
* parameters:PARTSTRUCTURE_PARAM[],
|
|
* dparameters:object{attribute:string, value:string}[],
|
|
* parts:array<int, object{disposition?:string}>,
|
|
* type:int,
|
|
* subtype:string
|
|
* }
|
|
*/
|
|
final class Imap
|
|
{
|
|
/** @psalm-var list<int> */
|
|
public const SORT_CRITERIA = [
|
|
SORTARRIVAL,
|
|
SORTCC,
|
|
SORTDATE,
|
|
SORTFROM,
|
|
SORTSIZE,
|
|
SORTSUBJECT,
|
|
SORTTO,
|
|
];
|
|
|
|
/** @psalm-var list<int> */
|
|
public const TIMEOUT_TYPES = [
|
|
IMAP_CLOSETIMEOUT,
|
|
IMAP_OPENTIMEOUT,
|
|
IMAP_READTIMEOUT,
|
|
IMAP_WRITETIMEOUT,
|
|
];
|
|
|
|
/** @psalm-var list<int> */
|
|
public const CLOSE_FLAGS = [
|
|
0,
|
|
CL_EXPUNGE,
|
|
];
|
|
|
|
/**
|
|
* @param resource|false $imap_stream
|
|
*
|
|
* @return true
|
|
*
|
|
* @see imap_append()
|
|
*/
|
|
public static function append(
|
|
$imap_stream,
|
|
string $mailbox,
|
|
string $message,
|
|
string $options = null,
|
|
string $internal_date = null
|
|
): bool {
|
|
\imap_errors(); // flush errors
|
|
|
|
$imap_stream = self::EnsureConnection($imap_stream, __METHOD__, 1);
|
|
|
|
if (null !== $options && null !== $internal_date) {
|
|
$result = \imap_append(
|
|
$imap_stream,
|
|
$mailbox,
|
|
$message,
|
|
$options,
|
|
$internal_date
|
|
);
|
|
} elseif (null !== $options) {
|
|
$result = \imap_append($imap_stream, $mailbox, $message, $options);
|
|
} else {
|
|
$result = \imap_append($imap_stream, $mailbox, $message);
|
|
}
|
|
|
|
if (false === $result) {
|
|
throw new UnexpectedValueException('Could not append message to mailbox!', 0, self::HandleErrors(\imap_errors(), 'imap_append'));
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @param false|resource $imap_stream
|
|
*/
|
|
public static function body(
|
|
$imap_stream,
|
|
int $msg_number,
|
|
int $options = 0
|
|
): string {
|
|
\imap_errors(); // flush errors
|
|
|
|
$result = \imap_body(
|
|
self::EnsureConnection($imap_stream, __METHOD__, 1),
|
|
$msg_number,
|
|
$options
|
|
);
|
|
|
|
if (false === $result) {
|
|
throw new UnexpectedValueException('Could not fetch message body from mailbox!', 0, self::HandleErrors(\imap_errors(), 'imap_body'));
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @param false|resource $imap_stream
|
|
*/
|
|
public static function check($imap_stream): object
|
|
{
|
|
\imap_errors(); // flush errors
|
|
|
|
$result = \imap_check(self::EnsureConnection($imap_stream, __METHOD__, 1));
|
|
|
|
if (false === $result) {
|
|
throw new UnexpectedValueException('Could not check imap mailbox!', 0, self::HandleErrors(\imap_errors(), 'imap_check'));
|
|
}
|
|
|
|
/** @var object */
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @param false|resource $imap_stream
|
|
* @param int|string $sequence
|
|
*
|
|
* @return true
|
|
*/
|
|
public static function clearflag_full(
|
|
$imap_stream,
|
|
$sequence,
|
|
string $flag,
|
|
int $options = 0
|
|
): bool {
|
|
\imap_errors(); // flush errors
|
|
|
|
$result = \imap_clearflag_full(
|
|
self::EnsureConnection($imap_stream, __METHOD__, 1),
|
|
self::encodeStringToUtf7Imap(static::EnsureRange(
|
|
$sequence,
|
|
__METHOD__,
|
|
2,
|
|
true
|
|
)),
|
|
self::encodeStringToUtf7Imap($flag),
|
|
$options
|
|
);
|
|
|
|
if (!$result) {
|
|
throw new UnexpectedValueException('Could not clear flag on messages!', 0, self::HandleErrors(\imap_errors(), 'imap_clearflag_full'));
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @param false|resource $imap_stream
|
|
*
|
|
* @psalm-param value-of<self::CLOSE_FLAGS> $flag
|
|
*
|
|
* @return true
|
|
*/
|
|
public static function close($imap_stream, int $flag = 0): bool
|
|
{
|
|
\imap_errors(); // flush errors
|
|
|
|
/** @var int */
|
|
$flag = $flag;
|
|
|
|
$result = \imap_close(self::EnsureConnection($imap_stream, __METHOD__, 1), $flag);
|
|
|
|
if (false === $result) {
|
|
$message = 'Could not close imap connection';
|
|
|
|
if (CL_EXPUNGE === ($flag & CL_EXPUNGE)) {
|
|
$message .= ', messages may not have been expunged';
|
|
}
|
|
|
|
$message .= '!';
|
|
throw new UnexpectedValueException($message, 0, self::HandleErrors(\imap_errors(), 'imap_close'));
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @param false|resource $imap_stream
|
|
*
|
|
* @return true
|
|
*/
|
|
public static function createmailbox($imap_stream, string $mailbox): bool
|
|
{
|
|
\imap_errors(); // flush errors
|
|
|
|
$result = \imap_createmailbox(
|
|
self::EnsureConnection($imap_stream, __METHOD__, 1),
|
|
static::encodeStringToUtf7Imap($mailbox)
|
|
);
|
|
|
|
if (false === $result) {
|
|
throw new UnexpectedValueException('Could not create mailbox!', 0, self::HandleErrors(\imap_errors(), 'createmailbox'));
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @param false|resource $imap_stream
|
|
* @param string|int $msg_number
|
|
*
|
|
* @return true
|
|
*/
|
|
public static function delete(
|
|
$imap_stream,
|
|
$msg_number,
|
|
int $options = 0
|
|
): bool {
|
|
/**
|
|
* @var int
|
|
*
|
|
* @todo remove docblock pending resolution of https://github.com/vimeo/psalm/issues/2620
|
|
*/
|
|
$msg_number = self::encodeStringToUtf7Imap(self::EnsureRange(
|
|
$msg_number,
|
|
__METHOD__,
|
|
1
|
|
));
|
|
|
|
\imap_errors(); // flush errors
|
|
|
|
$result = \imap_delete(
|
|
self::EnsureConnection($imap_stream, __METHOD__, 1),
|
|
$msg_number,
|
|
$options
|
|
);
|
|
|
|
if (false === $result) {
|
|
throw new UnexpectedValueException('Could not delete message from mailbox!', 0, self::HandleErrors(\imap_errors(), 'imap_delete'));
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @param false|resource $imap_stream
|
|
*
|
|
* @return true
|
|
*/
|
|
public static function deletemailbox($imap_stream, string $mailbox): bool
|
|
{
|
|
\imap_errors(); // flush errors
|
|
|
|
$result = \imap_deletemailbox(
|
|
self::EnsureConnection($imap_stream, __METHOD__, 1),
|
|
static::encodeStringToUtf7Imap($mailbox)
|
|
);
|
|
|
|
if (false === $result) {
|
|
throw new UnexpectedValueException('Could not delete mailbox!', 0, self::HandleErrors(\imap_errors(), 'imap_deletemailbox'));
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @param false|resource $imap_stream
|
|
*
|
|
* @return true
|
|
*/
|
|
public static function expunge($imap_stream): bool
|
|
{
|
|
\imap_errors(); // flush errors
|
|
|
|
$result = \imap_expunge(
|
|
self::EnsureConnection($imap_stream, __METHOD__, 1)
|
|
);
|
|
|
|
if (false === $result) {
|
|
throw new UnexpectedValueException('Could not expunge messages from mailbox!', 0, self::HandleErrors(\imap_errors(), 'imap_expunge'));
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @param false|resource $imap_stream
|
|
* @param int|string $sequence
|
|
*
|
|
* @return object[]
|
|
*
|
|
* @psalm-return list<object>
|
|
*/
|
|
public static function fetch_overview(
|
|
$imap_stream,
|
|
$sequence,
|
|
int $options = 0
|
|
): array {
|
|
\imap_errors(); // flush errors
|
|
|
|
$result = \imap_fetch_overview(
|
|
self::EnsureConnection($imap_stream, __METHOD__, 1),
|
|
self::encodeStringToUtf7Imap(self::EnsureRange(
|
|
$sequence,
|
|
__METHOD__,
|
|
1,
|
|
true
|
|
)),
|
|
$options
|
|
);
|
|
|
|
if (false === $result) {
|
|
throw new UnexpectedValueException('Could not fetch overview for message from mailbox!', 0, self::HandleErrors(\imap_errors(), 'imap_fetch_overview'));
|
|
}
|
|
|
|
/** @psalm-var list<object> */
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @param false|resource $imap_stream
|
|
* @param string|int $section
|
|
*/
|
|
public static function fetchbody(
|
|
$imap_stream,
|
|
int $msg_number,
|
|
$section,
|
|
int $options = 0
|
|
): string {
|
|
if (!\is_string($section) && !\is_int($section)) {
|
|
throw new InvalidArgumentException('Argument 3 passed to '.__METHOD__.'() must be a string or integer, '.\gettype($section).' given!');
|
|
}
|
|
|
|
\imap_errors(); // flush errors
|
|
|
|
$result = \imap_fetchbody(
|
|
self::EnsureConnection($imap_stream, __METHOD__, 1),
|
|
$msg_number,
|
|
self::encodeStringToUtf7Imap((string) $section),
|
|
$options
|
|
);
|
|
|
|
if (false === $result) {
|
|
throw new UnexpectedValueException('Could not fetch message body from mailbox!', 0, self::HandleErrors(\imap_errors(), 'imap_fetchbody'));
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @param false|resource $imap_stream
|
|
*/
|
|
public static function fetchheader(
|
|
$imap_stream,
|
|
int $msg_number,
|
|
int $options = 0
|
|
): string {
|
|
\imap_errors(); // flush errors
|
|
|
|
$result = \imap_fetchheader(
|
|
self::EnsureConnection($imap_stream, __METHOD__, 1),
|
|
$msg_number,
|
|
$options
|
|
);
|
|
|
|
if (false === $result) {
|
|
throw new UnexpectedValueException('Could not fetch message header from mailbox!', 0, self::HandleErrors(\imap_errors(), 'imap_fetchheader'));
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @param false|resource $imap_stream
|
|
*
|
|
* @psalm-return PARTSTRUCTURE
|
|
*/
|
|
public static function fetchstructure(
|
|
$imap_stream,
|
|
int $msg_number,
|
|
int $options = 0
|
|
): object {
|
|
\imap_errors(); // flush errors
|
|
|
|
$result = \imap_fetchstructure(
|
|
self::EnsureConnection($imap_stream, __METHOD__, 1),
|
|
$msg_number,
|
|
$options
|
|
);
|
|
|
|
if (false === $result) {
|
|
throw new UnexpectedValueException('Could not fetch message structure from mailbox!', 0, self::HandleErrors(\imap_errors(), 'imap_fetchstructure'));
|
|
}
|
|
|
|
/** @psalm-var PARTSTRUCTURE */
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @param false|resource $imap_stream
|
|
*
|
|
* @todo add return array shape pending resolution of https://github.com/vimeo/psalm/issues/2620
|
|
*/
|
|
public static function get_quotaroot(
|
|
$imap_stream,
|
|
string $quota_root
|
|
): array {
|
|
\imap_errors(); // flush errors
|
|
|
|
$result = \imap_get_quotaroot(
|
|
self::EnsureConnection($imap_stream, __METHOD__, 1),
|
|
self::encodeStringToUtf7Imap($quota_root)
|
|
);
|
|
|
|
if (false === $result) {
|
|
throw new UnexpectedValueException('Could not quota for mailbox!', 0, self::HandleErrors(\imap_errors(), 'imap_get_quotaroot'));
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @param resource|false $imap_stream
|
|
*
|
|
* @return object[]
|
|
*
|
|
* @psalm-return list<object>
|
|
*/
|
|
public static function getmailboxes(
|
|
$imap_stream,
|
|
string $ref,
|
|
string $pattern
|
|
): array {
|
|
\imap_errors(); // flush errors
|
|
|
|
$result = \imap_getmailboxes(
|
|
self::EnsureConnection($imap_stream, __METHOD__, 1),
|
|
$ref,
|
|
$pattern
|
|
);
|
|
|
|
if (false === $result) {
|
|
$errors = \imap_errors();
|
|
|
|
if (false === $errors) {
|
|
/*
|
|
* if there were no errors then there were no mailboxes,
|
|
* rather than a failure to get mailboxes.
|
|
*/
|
|
return [];
|
|
}
|
|
|
|
throw new UnexpectedValueException('Call to imap_getmailboxes() with supplied arguments returned false, not array!', 0, self::HandleErrors(\imap_errors(), 'imap_getmailboxes'));
|
|
}
|
|
|
|
/** @psalm-var list<object> */
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @param resource|false $imap_stream
|
|
*
|
|
* @return object[]
|
|
*
|
|
* @psalm-return list<object>
|
|
*/
|
|
public static function getsubscribed(
|
|
$imap_stream,
|
|
string $ref,
|
|
string $pattern
|
|
): array {
|
|
\imap_errors(); // flush errors
|
|
|
|
$result = \imap_getsubscribed(
|
|
self::EnsureConnection($imap_stream, __METHOD__, 1),
|
|
$ref,
|
|
$pattern
|
|
);
|
|
|
|
if (false === $result) {
|
|
throw new UnexpectedValueException('Call to imap_getsubscribed() with supplied arguments returned false, not array!', 0, self::HandleErrors(\imap_errors(), 'imap_getsubscribed'));
|
|
}
|
|
|
|
/** @psalm-var list<object> */
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @param false|resource $imap_stream
|
|
*/
|
|
public static function headers($imap_stream): array
|
|
{
|
|
\imap_errors(); // flush errors
|
|
|
|
$result = \imap_headers(
|
|
self::EnsureConnection($imap_stream, __METHOD__, 1)
|
|
);
|
|
|
|
if (false === $result) {
|
|
throw new UnexpectedValueException('Could not fetch headers from mailbox!', 0, self::HandleErrors(\imap_errors(), 'imap_headers'));
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @param false|resource $imap_stream
|
|
*
|
|
* @return string[]
|
|
*
|
|
* @psalm-return list<string>
|
|
*/
|
|
public static function listOfMailboxes($imap_stream, string $ref, string $pattern): array
|
|
{
|
|
\imap_errors(); // flush errors
|
|
|
|
$result = \imap_list(
|
|
self::EnsureConnection($imap_stream, __METHOD__, 1),
|
|
static::encodeStringToUtf7Imap($ref),
|
|
static::encodeStringToUtf7Imap($pattern)
|
|
);
|
|
|
|
if (false === $result) {
|
|
throw new UnexpectedValueException('Could not list folders mailbox!', 0, self::HandleErrors(\imap_errors(), 'imap_list'));
|
|
}
|
|
|
|
return \array_values(\array_map(
|
|
static function (string $folder): string {
|
|
return static::decodeStringFromUtf7ImapToUtf8($folder);
|
|
},
|
|
$result
|
|
));
|
|
}
|
|
|
|
/**
|
|
* @param mixed[] An associative array of headers fields
|
|
* @param mixed[] An indexed array of bodies
|
|
*
|
|
* @psalm-param array{
|
|
* subject?:string
|
|
* } $envelope An associative array of headers fields (docblock is not complete)
|
|
* @psalm-param list<array{
|
|
* type?:int,
|
|
* encoding?:int,
|
|
* charset?:string,
|
|
* subtype?:string,
|
|
* description?:string,
|
|
* disposition?:array{filename: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
|
|
{
|
|
return \imap_mail_compose($envelope, $body);
|
|
}
|
|
|
|
/**
|
|
* @param false|resource $imap_stream
|
|
* @param int|string $msglist
|
|
*
|
|
* @return true
|
|
*/
|
|
public static function mail_copy(
|
|
$imap_stream,
|
|
$msglist,
|
|
string $mailbox,
|
|
int $options = 0
|
|
): bool {
|
|
\imap_errors(); // flush errors
|
|
|
|
$result = \imap_mail_copy(
|
|
self::EnsureConnection($imap_stream, __METHOD__, 1),
|
|
static::encodeStringToUtf7Imap(self::EnsureRange(
|
|
$msglist,
|
|
__METHOD__,
|
|
2,
|
|
true
|
|
)),
|
|
static::encodeStringToUtf7Imap($mailbox),
|
|
$options
|
|
);
|
|
|
|
if (false === $result) {
|
|
throw new UnexpectedValueException('Could not copy messages!', 0, self::HandleErrors(\imap_errors(), 'imap_mail_copy'));
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @param false|resource $imap_stream
|
|
* @param int|string $msglist
|
|
*
|
|
* @return true
|
|
*/
|
|
public static function mail_move(
|
|
$imap_stream,
|
|
$msglist,
|
|
string $mailbox,
|
|
int $options = 0
|
|
): bool {
|
|
\imap_errors(); // flush errors
|
|
|
|
$result = \imap_mail_move(
|
|
self::EnsureConnection($imap_stream, __METHOD__, 1),
|
|
static::encodeStringToUtf7Imap(self::EnsureRange(
|
|
$msglist,
|
|
__METHOD__,
|
|
2,
|
|
true
|
|
)),
|
|
static::encodeStringToUtf7Imap($mailbox),
|
|
$options
|
|
);
|
|
|
|
if (false === $result) {
|
|
throw new UnexpectedValueException('Could not move messages!', 0, self::HandleErrors(\imap_errors(), 'imap_mail_move'));
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @param false|resource $imap_stream
|
|
*/
|
|
public static function mailboxmsginfo($imap_stream): stdClass
|
|
{
|
|
\imap_errors(); // flush errors
|
|
|
|
$result = \imap_mailboxmsginfo(
|
|
self::EnsureConnection($imap_stream, __METHOD__, 1)
|
|
);
|
|
|
|
if (false === $result) {
|
|
throw new UnexpectedValueException('Could not fetch mailboxmsginfo from mailbox!', 0, self::HandleErrors(\imap_errors(), 'imap_mailboxmsginfo'));
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @param false|resource $imap_stream
|
|
*/
|
|
public static function num_msg($imap_stream): int
|
|
{
|
|
\imap_errors(); // flush errors
|
|
|
|
$result = \imap_num_msg(self::EnsureConnection($imap_stream, __METHOD__, 1));
|
|
|
|
if (false === $result) {
|
|
throw new UnexpectedValueException('Could not get the number of messages in the mailbox!', 0, self::HandleErrors(\imap_errors(), 'imap_num_msg'));
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @psalm-param array{DISABLE_AUTHENTICATOR:string}|array<empty, empty> $params
|
|
*
|
|
* @return resource
|
|
*/
|
|
public static function open(
|
|
string $mailbox,
|
|
string $username,
|
|
string $password,
|
|
int $options = 0,
|
|
int $n_retries = 0,
|
|
array $params = []
|
|
) {
|
|
if (\preg_match("/^\{.*\}(.*)$/", $mailbox, $matches)) {
|
|
$mailbox_name = $matches[1] ?? '';
|
|
|
|
if (!\mb_detect_encoding($mailbox_name, 'ASCII', true)) {
|
|
$mailbox = static::encodeStringToUtf7Imap($mailbox);
|
|
}
|
|
}
|
|
|
|
\imap_errors(); // flush errors
|
|
|
|
$result = @\imap_open($mailbox, $username, $password, $options, $n_retries, $params);
|
|
|
|
if (!$result) {
|
|
throw new ConnectionException(\imap_errors() ?: []);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @param resource|false $imap_stream
|
|
*
|
|
* @psalm-pure
|
|
*/
|
|
public static function ping($imap_stream): bool
|
|
{
|
|
return (\is_resource($imap_stream) || $imap_stream instanceof \IMAP\Connection) && \imap_ping($imap_stream);
|
|
}
|
|
|
|
/**
|
|
* @param false|resource $imap_stream
|
|
*
|
|
* @return true
|
|
*/
|
|
public static function renamemailbox(
|
|
$imap_stream,
|
|
string $old_mbox,
|
|
string $new_mbox
|
|
): bool {
|
|
$imap_stream = self::EnsureConnection($imap_stream, __METHOD__, 1);
|
|
|
|
$old_mbox = static::encodeStringToUtf7Imap($old_mbox);
|
|
$new_mbox = static::encodeStringToUtf7Imap($new_mbox);
|
|
|
|
\imap_errors(); // flush errors
|
|
|
|
$result = \imap_renamemailbox($imap_stream, $old_mbox, $new_mbox);
|
|
|
|
if (!$result) {
|
|
throw new UnexpectedValueException('Could not rename mailbox!', 0, self::HandleErrors(\imap_errors(), 'imap_renamemailbox'));
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @param false|resource $imap_stream
|
|
*
|
|
* @return true
|
|
*/
|
|
public static function reopen(
|
|
$imap_stream,
|
|
string $mailbox,
|
|
int $options = 0,
|
|
int $n_retries = 0
|
|
): bool {
|
|
$imap_stream = self::EnsureConnection($imap_stream, __METHOD__, 1);
|
|
|
|
$mailbox = static::encodeStringToUtf7Imap($mailbox);
|
|
|
|
\imap_errors(); // flush errors
|
|
|
|
$result = \imap_reopen($imap_stream, $mailbox, $options, $n_retries);
|
|
|
|
if (!$result) {
|
|
throw new UnexpectedValueException('Could not reopen mailbox!', 0, self::HandleErrors(\imap_errors(), 'imap_reopen'));
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @param false|resource $imap_stream
|
|
* @param string|false|resource $file
|
|
*
|
|
* @return true
|
|
*/
|
|
public static function savebody(
|
|
$imap_stream,
|
|
$file,
|
|
int $msg_number,
|
|
string $part_number = '',
|
|
int $options = 0
|
|
): bool {
|
|
$imap_stream = self::EnsureConnection($imap_stream, __METHOD__, 1);
|
|
$file = \is_string($file) ? $file : self::EnsureResource($file, __METHOD__, 2);
|
|
$part_number = self::encodeStringToUtf7Imap($part_number);
|
|
|
|
\imap_errors(); // flush errors
|
|
|
|
$result = \imap_savebody($imap_stream, $file, $msg_number, $part_number, $options);
|
|
|
|
if (!$result) {
|
|
throw new UnexpectedValueException('Could not reopen mailbox!', 0, self::HandleErrors(\imap_errors(), 'imap_savebody'));
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @param false|resource $imap_stream
|
|
*
|
|
* @return int[]
|
|
*
|
|
* @psalm-return list<int>
|
|
*/
|
|
public static function search(
|
|
$imap_stream,
|
|
string $criteria,
|
|
int $options = SE_FREE,
|
|
string $charset = null,
|
|
bool $encodeCriteriaAsUtf7Imap = false
|
|
): array {
|
|
\imap_errors(); // flush errors
|
|
|
|
$imap_stream = static::EnsureConnection($imap_stream, __METHOD__, 1);
|
|
|
|
if ($encodeCriteriaAsUtf7Imap) {
|
|
$criteria = static::encodeStringToUtf7Imap($criteria);
|
|
}
|
|
|
|
if (\is_string($charset)) {
|
|
$result = \imap_search(
|
|
$imap_stream,
|
|
$criteria,
|
|
$options,
|
|
static::encodeStringToUtf7Imap($charset)
|
|
);
|
|
} else {
|
|
$result = \imap_search($imap_stream, $criteria, $options);
|
|
}
|
|
|
|
if (!$result) {
|
|
$errors = \imap_errors();
|
|
|
|
if (false === $errors) {
|
|
/*
|
|
* if there were no errors then there were no matches,
|
|
* rather than a failure to parse criteria.
|
|
*/
|
|
return [];
|
|
}
|
|
|
|
throw new UnexpectedValueException('Could not search mailbox!', 0, self::HandleErrors($errors, 'imap_search'));
|
|
}
|
|
|
|
/** @psalm-var list<int> */
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @param false|resource $imap_stream
|
|
* @param int|string $sequence
|
|
*
|
|
* @return true
|
|
*/
|
|
public static function setflag_full(
|
|
$imap_stream,
|
|
$sequence,
|
|
string $flag,
|
|
int $options = NIL
|
|
): bool {
|
|
\imap_errors(); // flush errors
|
|
|
|
$result = \imap_setflag_full(
|
|
self::EnsureConnection($imap_stream, __METHOD__, 1),
|
|
self::encodeStringToUtf7Imap(static::EnsureRange(
|
|
$sequence,
|
|
__METHOD__,
|
|
2,
|
|
true
|
|
)),
|
|
self::encodeStringToUtf7Imap($flag),
|
|
$options
|
|
);
|
|
|
|
if (!$result) {
|
|
throw new UnexpectedValueException('Could not set flag on messages!', 0, self::HandleErrors(\imap_errors(), 'imap_setflag_full'));
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @param false|resource $imap_stream
|
|
*
|
|
* @psalm-param value-of<self::SORT_CRITERIA> $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[]
|
|
*
|
|
* @psalm-return list<int>
|
|
*/
|
|
public static function sort(
|
|
$imap_stream,
|
|
int $criteria,
|
|
bool $reverse,
|
|
int $options,
|
|
string $search_criteria = null,
|
|
string $charset = null
|
|
): array {
|
|
\imap_errors(); // flush errors
|
|
|
|
$imap_stream = self::EnsureConnection($imap_stream, __METHOD__, 1);
|
|
|
|
/** @var int */
|
|
$criteria = $criteria;
|
|
|
|
if (PHP_MAJOR_VERSION < 8) {
|
|
/** @var int */
|
|
$reverse = (int) $reverse;
|
|
} else {
|
|
/** @var bool */
|
|
$reverse = $reverse;
|
|
}
|
|
|
|
if (null !== $search_criteria && null !== $charset) {
|
|
$result = \imap_sort(
|
|
$imap_stream,
|
|
$criteria,
|
|
$reverse,
|
|
$options,
|
|
self::encodeStringToUtf7Imap($search_criteria),
|
|
self::encodeStringToUtf7Imap($charset)
|
|
);
|
|
} elseif (null !== $search_criteria) {
|
|
$result = \imap_sort(
|
|
$imap_stream,
|
|
$criteria,
|
|
$reverse,
|
|
$options,
|
|
self::encodeStringToUtf7Imap($search_criteria)
|
|
);
|
|
} else {
|
|
$result = \imap_sort(
|
|
$imap_stream,
|
|
$criteria,
|
|
$reverse,
|
|
$options
|
|
);
|
|
}
|
|
|
|
if (false === $result) {
|
|
throw new UnexpectedValueException('Could not sort messages!', 0, self::HandleErrors(\imap_errors(), 'imap_sort'));
|
|
}
|
|
|
|
/** @psalm-var list<int> */
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @param false|resource $imap_stream
|
|
*
|
|
* @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): stdClass
|
|
{
|
|
$imap_stream = self::EnsureConnection($imap_stream, __METHOD__, 1);
|
|
|
|
$mailbox = static::encodeStringToUtf7Imap($mailbox);
|
|
|
|
\imap_errors(); // flush errors
|
|
|
|
$result = \imap_status($imap_stream, $mailbox, $options);
|
|
|
|
if (!$result) {
|
|
throw new UnexpectedValueException('Could not get status of mailbox!', 0, self::HandleErrors(\imap_errors(), 'imap_status'));
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @param false|resource $imap_stream
|
|
*/
|
|
public static function subscribe($imap_stream, string $mailbox): void
|
|
{
|
|
$imap_stream = self::EnsureConnection($imap_stream, __METHOD__, 1);
|
|
|
|
$mailbox = static::encodeStringToUtf7Imap($mailbox);
|
|
|
|
\imap_errors(); // flush errors
|
|
|
|
$result = \imap_subscribe($imap_stream, $mailbox);
|
|
|
|
if (false === $result) {
|
|
throw new UnexpectedValueException('Could not subscribe to mailbox!', 0, self::HandleErrors(\imap_errors(), 'imap_subscribe'));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @psalm-param value-of<self::TIMEOUT_TYPES> $timeout_type
|
|
*
|
|
* @return true|int
|
|
*/
|
|
public static function timeout(
|
|
int $timeout_type,
|
|
int $timeout = -1
|
|
) {
|
|
\imap_errors(); // flush errors
|
|
|
|
/** @var int */
|
|
$timeout_type = $timeout_type;
|
|
|
|
$result = \imap_timeout(
|
|
$timeout_type,
|
|
$timeout
|
|
);
|
|
|
|
if (false === $result) {
|
|
throw new UnexpectedValueException('Could not get/set connection timeout!', 0, self::HandleErrors(\imap_errors(), 'imap_timeout'));
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* @param false|resource $imap_stream
|
|
*/
|
|
public static function unsubscribe(
|
|
$imap_stream,
|
|
string $mailbox
|
|
): void {
|
|
$imap_stream = self::EnsureConnection($imap_stream, __METHOD__, 1);
|
|
|
|
$mailbox = static::encodeStringToUtf7Imap($mailbox);
|
|
|
|
\imap_errors(); // flush errors
|
|
|
|
$result = \imap_unsubscribe($imap_stream, $mailbox);
|
|
|
|
if (false === $result) {
|
|
throw new UnexpectedValueException('Could not unsubscribe from mailbox!', 0, self::HandleErrors(\imap_errors(), 'imap_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
|
|
{
|
|
$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;
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
{
|
|
$out = \mb_convert_encoding($str, 'UTF-8', 'UTF7-IMAP');
|
|
|
|
if (!\is_string($out)) {
|
|
throw new UnexpectedValueException('mb_convert_encoding($str, \'UTF-8\', \'UTF7-IMAP\') could not convert $str');
|
|
}
|
|
|
|
return $out;
|
|
}
|
|
|
|
/**
|
|
* @param false|resource $maybe
|
|
*
|
|
* @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) && !$maybe instanceof \IMAP\Connection)) {
|
|
throw new InvalidArgumentException('Argument '.(string) $argument.' passed to '.$method.' must be a valid resource!');
|
|
}
|
|
|
|
/** @var resource */
|
|
return $maybe;
|
|
}
|
|
|
|
/**
|
|
* @param false|resource $maybe
|
|
*
|
|
* @throws Exceptions\ConnectionException if $maybe is not a valid resource
|
|
*
|
|
* @return resource
|
|
*/
|
|
private static function EnsureConnection($maybe, string $method, int $argument)
|
|
{
|
|
try {
|
|
return self::EnsureResource($maybe, $method, $argument);
|
|
} catch (Throwable $e) {
|
|
throw new Exceptions\ConnectionException('Argument '.(string) $argument.' passed to '.$method.' must be valid resource!', 0, $e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param array|false $errors
|
|
*
|
|
* @psalm-pure
|
|
*/
|
|
private static function HandleErrors($errors, string $method): UnexpectedValueException
|
|
{
|
|
if ($errors) {
|
|
return new UnexpectedValueException('IMAP method '.$method.'() failed with error: '.\implode('. ', $errors));
|
|
}
|
|
|
|
return new UnexpectedValueException('IMAP method '.$method.'() failed!');
|
|
}
|
|
|
|
/**
|
|
* @param scalar $msg_number
|
|
*
|
|
* @psalm-pure
|
|
*/
|
|
private static function EnsureRange(
|
|
$msg_number,
|
|
string $method,
|
|
int $argument,
|
|
bool $allow_sequence = false
|
|
): string {
|
|
if (!\is_int($msg_number) && !\is_string($msg_number)) {
|
|
throw new InvalidArgumentException('Argument 1 passed to '.__METHOD__.'() must be an integer or a string!');
|
|
}
|
|
|
|
$regex = '/^\d+:\d+$/';
|
|
$suffix = '() did not appear to be a valid message id range!';
|
|
|
|
if ($allow_sequence) {
|
|
$regex = '/^\d+(?:(?:,\d+)+|:\d+)$/';
|
|
$suffix = '() did not appear to be a valid message id range or sequence!';
|
|
}
|
|
|
|
if (\is_int($msg_number) || \preg_match('/^\d+$/', $msg_number)) {
|
|
return \sprintf('%1$s:%1$s', $msg_number);
|
|
} elseif (1 !== \preg_match($regex, $msg_number)) {
|
|
throw new InvalidArgumentException('Argument '.(string) $argument.' passed to '.$method.$suffix);
|
|
}
|
|
|
|
return $msg_number;
|
|
}
|
|
}
|