1487 lines
52 KiB
PHP
1487 lines
52 KiB
PHP
<?php
|
|
|
|
namespace PhpImap;
|
|
|
|
use DateTime;
|
|
use Exception;
|
|
use PhpImap\Exceptions\ConnectionException;
|
|
use PhpImap\Exceptions\InvalidParameterException;
|
|
use stdClass;
|
|
|
|
/**
|
|
* @see https://github.com/barbushin/php-imap
|
|
*
|
|
* @author Barbushin Sergey http://linkedin.com/in/barbushin
|
|
*/
|
|
class Mailbox
|
|
{
|
|
protected $imapPath;
|
|
protected $imapLogin;
|
|
protected $imapPassword;
|
|
protected $imapSearchOption = SE_UID;
|
|
protected $connectionRetry = 0;
|
|
protected $connectionRetryDelay = 100;
|
|
protected $imapOptions = 0;
|
|
protected $imapRetriesNum = 0;
|
|
protected $imapParams = [];
|
|
protected $serverEncoding = 'UTF-8';
|
|
protected $attachmentsDir = null;
|
|
protected $expungeOnDisconnect = true;
|
|
protected $timeouts = [];
|
|
protected $attachmentsIgnore = false;
|
|
protected $pathDelimiter = '.';
|
|
private $imapStream;
|
|
|
|
/**
|
|
* @param string $imapPath
|
|
* @param string $login
|
|
* @param string $password
|
|
* @param string $attachmentsDir
|
|
* @param string $serverEncoding
|
|
*
|
|
* @throws InvalidParameterException
|
|
*/
|
|
public function __construct($imapPath, $login, $password, $attachmentsDir = null, $serverEncoding = 'UTF-8')
|
|
{
|
|
$this->imapPath = trim($imapPath);
|
|
$this->imapLogin = trim($login);
|
|
$this->imapPassword = $password;
|
|
$this->setServerEncoding($serverEncoding);
|
|
if (null != $attachmentsDir) {
|
|
$this->setAttachmentsDir($attachmentsDir);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets / Changes the path delimiter character (Supported values: '.', '/').
|
|
*
|
|
* @param string $delimiter Path delimiter
|
|
*
|
|
* @throws InvalidParameterException
|
|
*/
|
|
public function setPathDelimiter($delimiter)
|
|
{
|
|
if (!$this->validatePathDelimiter($delimiter)) {
|
|
throw new InvalidParameterException('setPathDelimiter() can only set the delimiter to these characters: ".", "/"');
|
|
}
|
|
|
|
$this->pathDelimiter = $delimiter;
|
|
}
|
|
|
|
/**
|
|
* Returns the current set path delimiter character.
|
|
*
|
|
* @return string Path delimiter
|
|
*/
|
|
public function getPathDelimiter()
|
|
{
|
|
return $this->pathDelimiter;
|
|
}
|
|
|
|
/**
|
|
* Validates the given path delimiter character.
|
|
*
|
|
* @param string Path delimiter
|
|
*
|
|
* @return bool true (supported) or false (unsupported)
|
|
*/
|
|
public function validatePathDelimiter($delimiter)
|
|
{
|
|
$supported_delimiters = ['.', '/'];
|
|
|
|
if (!\in_array($delimiter, $supported_delimiters)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Returns the current set server encoding.
|
|
*
|
|
* @return string Server encoding (eg. 'UTF-8')
|
|
*/
|
|
public function getServerEncoding()
|
|
{
|
|
return $this->serverEncoding;
|
|
}
|
|
|
|
/**
|
|
* Sets / Changes the server encoding.
|
|
*
|
|
* @param string Server encoding (eg. 'UTF-8')
|
|
*
|
|
* @throws InvalidParameterException
|
|
*/
|
|
public function setServerEncoding($serverEncoding)
|
|
{
|
|
$serverEncoding = strtoupper(trim($serverEncoding));
|
|
|
|
// https://github.com/barbushin/php-imap/issues/336
|
|
$supported_encodings = [];
|
|
if (\extension_loaded('mbstring')) {
|
|
$supported_encodings = mb_list_encodings();
|
|
}
|
|
|
|
if (!\in_array($serverEncoding, $supported_encodings) && 'US-ASCII' != $serverEncoding) {
|
|
throw new InvalidParameterException('"'.$serverEncoding.'" is not supported by setServerEncoding(). Your system only supports these encodings: US-ASCII, '.implode(', ', $supported_encodings));
|
|
}
|
|
|
|
$this->serverEncoding = $serverEncoding;
|
|
}
|
|
|
|
/**
|
|
* Returns the current set IMAP search option.
|
|
*
|
|
* @return string IMAP search option (eg. 'SE_UID')
|
|
*/
|
|
public function getImapSearchOption()
|
|
{
|
|
return $this->imapSearchOption;
|
|
}
|
|
|
|
/**
|
|
* Sets / Changes the IMAP search option.
|
|
*
|
|
* @param string IMAP search option (eg. 'SE_UID')
|
|
*
|
|
* @throws InvalidParameterException
|
|
*/
|
|
public function setImapSearchOption($imapSearchOption)
|
|
{
|
|
$imapSearchOption = strtoupper(trim($imapSearchOption));
|
|
|
|
$supported_options = [SE_FREE, SE_UID];
|
|
|
|
if (!\in_array($imapSearchOption, $supported_options)) {
|
|
throw new InvalidParameterException('"'.$imapSearchOption.'" is not supported by setImapSearchOption(). Supported options are SE_FREE and SE_UID.');
|
|
}
|
|
|
|
$this->imapSearchOption = $imapSearchOption;
|
|
}
|
|
|
|
/**
|
|
* Set $this->attachmentsIgnore param. Allow to ignore attachments when they are not required and boost performance.
|
|
*
|
|
* @param bool $attachmentsIgnore
|
|
*
|
|
* @throws InvalidParameterException
|
|
*/
|
|
public function setAttachmentsIgnore($attachmentsIgnore)
|
|
{
|
|
if (!\is_bool($attachmentsIgnore)) {
|
|
throw new InvalidParameterException('setAttachmentsIgnore() expects a boolean: true or false');
|
|
}
|
|
$this->attachmentsIgnore = $attachmentsIgnore;
|
|
}
|
|
|
|
/**
|
|
* Get $this->attachmentsIgnore param.
|
|
*
|
|
* @return bool $attachmentsIgnore
|
|
*/
|
|
public function getAttachmentsIgnore()
|
|
{
|
|
return $this->attachmentsIgnore;
|
|
}
|
|
|
|
/**
|
|
* Sets the timeout of all or one specific type.
|
|
*
|
|
* @param int $timeout Timeout in seconds
|
|
* @param array $types One of the following: IMAP_OPENTIMEOUT, IMAP_READTIMEOUT, IMAP_WRITETIMEOUT, IMAP_CLOSETIMEOUT
|
|
*
|
|
* @throws InvalidParameterException
|
|
*/
|
|
public function setTimeouts($timeout, $types = [IMAP_OPENTIMEOUT, IMAP_READTIMEOUT, IMAP_WRITETIMEOUT, IMAP_CLOSETIMEOUT])
|
|
{
|
|
$supported_types = [IMAP_OPENTIMEOUT, IMAP_READTIMEOUT, IMAP_WRITETIMEOUT, IMAP_CLOSETIMEOUT];
|
|
|
|
$found_types = array_intersect($types, $supported_types);
|
|
|
|
if (\count($types) != \count($found_types)) {
|
|
throw new InvalidParameterException('You have provided at least one unsupported timeout type. Supported types are: IMAP_OPENTIMEOUT, IMAP_READTIMEOUT, IMAP_WRITETIMEOUT, IMAP_CLOSETIMEOUT');
|
|
}
|
|
|
|
$this->timeouts = array_fill_keys($types, $timeout);
|
|
}
|
|
|
|
/**
|
|
* Returns the IMAP login (usually an email address).
|
|
*
|
|
* @return string IMAP login
|
|
*/
|
|
public function getLogin()
|
|
{
|
|
return $this->imapLogin;
|
|
}
|
|
|
|
/**
|
|
* Set custom connection arguments of imap_open method. See http://php.net/imap_open.
|
|
*
|
|
* @param int $options
|
|
* @param int $retriesNum
|
|
* @param array $params
|
|
*
|
|
* @throws InvalidParameterException
|
|
*/
|
|
public function setConnectionArgs($options = 0, $retriesNum = 0, $params = null)
|
|
{
|
|
if (0 != $options) {
|
|
$supported_options = [OP_READONLY, OP_ANONYMOUS, OP_HALFOPEN, CL_EXPUNGE, OP_DEBUG, OP_SHORTCACHE, OP_SILENT, OP_PROTOTYPE, OP_SECURE];
|
|
if (!\in_array($options, $supported_options)) {
|
|
throw new InvalidParameterException('Please check your option for setConnectionArgs()! Unsupported option "'.$options.'". Available options: https://www.php.net/manual/de/function.imap-open.php');
|
|
}
|
|
$this->imapOptions = $options;
|
|
}
|
|
|
|
if (0 != $retriesNum) {
|
|
if (!\is_int($retriesNum) or $retriesNum < 0) {
|
|
throw new InvalidParameterException('Invalid number of retries provided for setConnectionArgs()! It must be a positive integer. (eg. 1 or 3)');
|
|
}
|
|
$this->imapRetriesNum = $retriesNum;
|
|
}
|
|
|
|
if (null != $params and !empty($params)) {
|
|
$supported_params = ['DISABLE_AUTHENTICATOR'];
|
|
if (!\is_array($params)) {
|
|
throw new InvalidParameterException('setConnectionArgs() requires $params to be an array!');
|
|
}
|
|
|
|
foreach ($params as $key => $value) {
|
|
if (!\in_array($key, $supported_params)) {
|
|
throw new InvalidParameterException('Invalid array key of params provided for setConnectionArgs()! Only DISABLE_AUTHENTICATOR is currently valid.');
|
|
}
|
|
}
|
|
$this->imapParams = $params;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set custom folder for attachments in case you want to have tree of folders for each email
|
|
* i.e. a/1 b/1 c/1 where a,b,c - senders, i.e. john@smith.com.
|
|
*
|
|
* @param string $attachmentsDir Folder where to save attachments
|
|
*
|
|
* @throws InvalidParameterException
|
|
*/
|
|
public function setAttachmentsDir($attachmentsDir)
|
|
{
|
|
if (empty(trim($attachmentsDir))) {
|
|
throw new InvalidParameterException('setAttachmentsDir() expects a string as first parameter!');
|
|
}
|
|
if (!is_dir($attachmentsDir)) {
|
|
throw new InvalidParameterException('Directory "'.$attachmentsDir.'" not found');
|
|
}
|
|
$this->attachmentsDir = rtrim(realpath($attachmentsDir), '\\/');
|
|
}
|
|
|
|
/**
|
|
* Get current saving folder for attachments.
|
|
*
|
|
* @return string Attachments dir
|
|
*/
|
|
public function getAttachmentsDir()
|
|
{
|
|
return $this->attachmentsDir;
|
|
}
|
|
|
|
/*
|
|
* Sets / Changes the attempts / retries to connect
|
|
* @param int $maxAttempts
|
|
* @return void
|
|
*/
|
|
public function setConnectionRetry($maxAttempts)
|
|
{
|
|
$this->connectionRetry = $maxAttempts;
|
|
}
|
|
|
|
/*
|
|
* Sets / Changes the delay between each attempt / retry to connect
|
|
* @param int $milliseconds
|
|
* @return void
|
|
*/
|
|
public function setConnectionRetryDelay($milliseconds)
|
|
{
|
|
$this->connectionRetryDelay = $milliseconds;
|
|
}
|
|
|
|
/**
|
|
* Get IMAP mailbox connection stream.
|
|
*
|
|
* @param bool $forceConnection Initialize connection if it's not initialized
|
|
*
|
|
* @return resource|null
|
|
*/
|
|
public function getImapStream($forceConnection = true)
|
|
{
|
|
if ($forceConnection) {
|
|
if ($this->imapStream && (!\is_resource($this->imapStream) || !imap_ping($this->imapStream))) {
|
|
$this->disconnect();
|
|
$this->imapStream = null;
|
|
}
|
|
if (!$this->imapStream) {
|
|
$this->imapStream = $this->initImapStreamWithRetry();
|
|
}
|
|
}
|
|
|
|
return $this->imapStream;
|
|
}
|
|
|
|
/**
|
|
* Returns the provided string in UTF7-IMAP encoded format.
|
|
*
|
|
* @return string $str UTF-7 encoded string or same as before, when it's no string
|
|
*/
|
|
public function encodeStringToUtf7Imap($str)
|
|
{
|
|
if (\is_string($str)) {
|
|
return mb_convert_encoding($str, 'UTF7-IMAP', mb_detect_encoding($str, 'UTF-8, ISO-8859-1, ISO-8859-15', true));
|
|
}
|
|
|
|
// Return $str as it is, when it is no string
|
|
return $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
|
|
*/
|
|
public function decodeStringFromUtf7ImapToUtf8($str)
|
|
{
|
|
if (\is_string($str)) {
|
|
return mb_convert_encoding($str, 'UTF-8', 'UTF7-IMAP');
|
|
}
|
|
|
|
// Return $str as it is, when it is no string
|
|
return $str;
|
|
}
|
|
|
|
/**
|
|
* Switch mailbox without opening a new connection.
|
|
*
|
|
* @param string $imapPath
|
|
*
|
|
* @throws Exception
|
|
*/
|
|
public function switchMailbox($imapPath)
|
|
{
|
|
if (strpos($imapPath, '}') > 0) {
|
|
$this->imapPath = $imapPath;
|
|
} else {
|
|
$this->imapPath = $this->getCombinedPath($imapPath, true);
|
|
}
|
|
|
|
$this->imap('reopen', $this->imapPath);
|
|
}
|
|
|
|
protected function initImapStreamWithRetry()
|
|
{
|
|
$retry = $this->connectionRetry;
|
|
|
|
do {
|
|
try {
|
|
return $this->initImapStream();
|
|
} catch (ConnectionException $exception) {
|
|
}
|
|
} while (--$retry > 0 && (!$this->connectionRetryDelay || !usleep($this->connectionRetryDelay * 1000)));
|
|
|
|
throw $exception;
|
|
}
|
|
|
|
/**
|
|
* Open an IMAP stream to a mailbox.
|
|
*
|
|
* @return object IMAP stream on success
|
|
*
|
|
* @throws Exception if an error occured
|
|
*/
|
|
protected function initImapStream()
|
|
{
|
|
foreach ($this->timeouts as $type => $timeout) {
|
|
$this->imap('timeout', [$type, $timeout], false);
|
|
}
|
|
|
|
$imapStream = @imap_open($this->imapPath, $this->imapLogin, $this->imapPassword, $this->imapOptions, $this->imapRetriesNum, $this->imapParams);
|
|
|
|
if (!$imapStream) {
|
|
$lastError = imap_last_error();
|
|
|
|
if (!empty(trim($lastError))) {
|
|
// imap error = report imap error
|
|
throw new Exception('IMAP error: '.$lastError);
|
|
} else {
|
|
// no imap error = connectivity issue
|
|
throw new Exception('Connection error: Unable to connect to '.$this->imapPath);
|
|
}
|
|
}
|
|
|
|
// this function is called multiple times and imap keeps errors around.
|
|
// Let's clear them out to avoid it tripping up future calls.
|
|
@imap_errors();
|
|
|
|
return $imapStream;
|
|
}
|
|
|
|
/**
|
|
* Disconnects from IMAP server / mailbox.
|
|
*/
|
|
public function disconnect()
|
|
{
|
|
$imapStream = $this->getImapStream(false);
|
|
if ($imapStream && \is_resource($imapStream)) {
|
|
$this->imap('close', [$imapStream, $this->expungeOnDisconnect ? CL_EXPUNGE : 0], false, null);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets 'expunge on disconnect' parameter.
|
|
*
|
|
* @param bool $isEnabled
|
|
*/
|
|
public function setExpungeOnDisconnect($isEnabled)
|
|
{
|
|
$this->expungeOnDisconnect = $isEnabled;
|
|
}
|
|
|
|
/**
|
|
* Get information about the current mailbox.
|
|
*
|
|
* Returns the information in an object with following properties:
|
|
* Date - current system time formatted according to RFC2822
|
|
* Driver - protocol used to access this mailbox: POP3, IMAP, NNTP
|
|
* Mailbox - the mailbox name
|
|
* Nmsgs - number of mails in the mailbox
|
|
* Recent - number of recent mails in the mailbox
|
|
*
|
|
* @return stdClass
|
|
*
|
|
* @see imap_check
|
|
*/
|
|
public function checkMailbox()
|
|
{
|
|
return $this->imap('check');
|
|
}
|
|
|
|
/**
|
|
* Creates a new mailbox.
|
|
*
|
|
* @param string $name Name of new mailbox (eg. 'PhpImap')
|
|
*
|
|
* @see imap_createmailbox()
|
|
*/
|
|
public function createMailbox($name)
|
|
{
|
|
$this->imap('createmailbox', $this->getCombinedPath($name));
|
|
}
|
|
|
|
/**
|
|
* Deletes a specific mailbox.
|
|
*
|
|
* @param string $name Name of mailbox, which you want to delete (eg. 'PhpImap')
|
|
*
|
|
* @see imap_deletemailbox()
|
|
*/
|
|
public function deleteMailbox($name)
|
|
{
|
|
$this->imap('deletemailbox', $this->getCombinedPath($name));
|
|
}
|
|
|
|
/**
|
|
* Rename an existing mailbox from $oldName to $newName.
|
|
*
|
|
* @param string $oldName Current name of mailbox, which you want to rename (eg. 'PhpImap')
|
|
* @param string $newName New name of mailbox, to which you want to rename it (eg. 'PhpImapTests')
|
|
*/
|
|
public function renameMailbox($oldName, $newName)
|
|
{
|
|
$this->imap('renamemailbox', [$this->getCombinedPath($oldName), $this->getCombinedPath($newName)]);
|
|
}
|
|
|
|
/**
|
|
* Gets status information about the given mailbox.
|
|
*
|
|
* This function returns an object containing status information.
|
|
* The object has the following properties: messages, recent, unseen, uidnext, and uidvalidity.
|
|
*
|
|
* @return stdClass
|
|
*/
|
|
public function statusMailbox()
|
|
{
|
|
return $this->imap('status', [$this->imapPath, SA_ALL]);
|
|
}
|
|
|
|
/**
|
|
* Gets listing the folders.
|
|
*
|
|
* This function returns an object containing listing the folders.
|
|
* The object has the following properties: messages, recent, unseen, uidnext, and uidvalidity.
|
|
*
|
|
* @param string $pattern
|
|
*
|
|
* @return array listing the folders
|
|
*/
|
|
public function getListingFolders($pattern = '*')
|
|
{
|
|
$folders = $this->imap('list', [$this->imapPath, $pattern]) ?: [];
|
|
foreach ($folders as &$folder) {
|
|
$folder = $this->decodeStringFromUtf7ImapToUtf8($folder);
|
|
}
|
|
|
|
return $folders;
|
|
}
|
|
|
|
/**
|
|
* This function uses imap_search() to perform a search on the mailbox currently opened in the given IMAP stream.
|
|
* For example, to match all unanswered mails sent by Mom, you'd use: "UNANSWERED FROM mom".
|
|
*
|
|
* @param string $criteria See http://php.net/imap_search for a complete list of available criteria
|
|
* @param bool $disableServerEncoding Disables server encoding while searching for mails (can be useful on Exchange servers)
|
|
*
|
|
* @return array mailsIds (or empty array)
|
|
*/
|
|
public function searchMailbox($criteria = 'ALL', $disableServerEncoding = false)
|
|
{
|
|
if ($disableServerEncoding) {
|
|
return $this->imap('search', [$criteria, $this->imapSearchOption]) ?: [];
|
|
}
|
|
|
|
return $this->imap('search', [$criteria, $this->imapSearchOption, $this->getServerEncoding()]) ?: [];
|
|
}
|
|
|
|
/**
|
|
* Save a specific body section to a file.
|
|
*
|
|
* @param int $mailId message number
|
|
* @param string $filename
|
|
*
|
|
* @see imap_savebody()
|
|
*/
|
|
public function saveMail($mailId, $filename = 'email.eml')
|
|
{
|
|
$this->imap('savebody', [$filename, $mailId, '', (SE_UID == $this->imapSearchOption) ? FT_UID : 0]);
|
|
}
|
|
|
|
/**
|
|
* Marks mails listed in mailId for deletion.
|
|
*
|
|
* @param int $mailId message number
|
|
*
|
|
* @see imap_delete()
|
|
*/
|
|
public function deleteMail($mailId)
|
|
{
|
|
$this->imap('delete', [$mailId.':'.$mailId, (SE_UID == $this->imapSearchOption) ? FT_UID : 0]);
|
|
}
|
|
|
|
/**
|
|
* Moves mails listed in mailId into new mailbox.
|
|
*
|
|
* @param string $mailId a range or message number
|
|
* @param string $mailBox Mailbox name
|
|
*
|
|
* @see imap_mail_move()
|
|
*
|
|
* @return void
|
|
*/
|
|
public function moveMail($mailId, $mailBox)
|
|
{
|
|
$this->imap('mail_move', [$mailId, $mailBox, CP_UID]) && $this->expungeDeletedMails();
|
|
}
|
|
|
|
/**
|
|
* Copies mails listed in mailId into new mailbox.
|
|
*
|
|
* @param string $mailId a range or message number
|
|
* @param string $mailBox Mailbox name
|
|
*
|
|
* @see imap_mail_copy()
|
|
*/
|
|
public function copyMail($mailId, $mailBox)
|
|
{
|
|
$this->imap('mail_copy', [$mailId, $mailBox, CP_UID]) && $this->expungeDeletedMails();
|
|
}
|
|
|
|
/**
|
|
* Deletes all the mails marked for deletion by imap_delete(), imap_mail_move(), or imap_setflag_full().
|
|
*
|
|
* @see imap_expunge()
|
|
*/
|
|
public function expungeDeletedMails()
|
|
{
|
|
$this->imap('expunge');
|
|
}
|
|
|
|
/**
|
|
* Add the flag \Seen to a mail.
|
|
*
|
|
* @param $mailId
|
|
*/
|
|
public function markMailAsRead($mailId)
|
|
{
|
|
$this->setFlag([$mailId], '\\Seen');
|
|
}
|
|
|
|
/**
|
|
* Remove the flag \Seen from a mail.
|
|
*
|
|
* @param $mailId
|
|
*/
|
|
public function markMailAsUnread($mailId)
|
|
{
|
|
$this->clearFlag([$mailId], '\\Seen');
|
|
}
|
|
|
|
/**
|
|
* Add the flag \Flagged to a mail.
|
|
*
|
|
* @param $mailId
|
|
*/
|
|
public function markMailAsImportant($mailId)
|
|
{
|
|
$this->setFlag([$mailId], '\\Flagged');
|
|
}
|
|
|
|
/**
|
|
* Add the flag \Seen to a mails.
|
|
*/
|
|
public function markMailsAsRead(array $mailId)
|
|
{
|
|
$this->setFlag($mailId, '\\Seen');
|
|
}
|
|
|
|
/**
|
|
* Remove the flag \Seen from some mails.
|
|
*/
|
|
public function markMailsAsUnread(array $mailId)
|
|
{
|
|
$this->clearFlag($mailId, '\\Seen');
|
|
}
|
|
|
|
/**
|
|
* Add the flag \Flagged to some mails.
|
|
*/
|
|
public function markMailsAsImportant(array $mailId)
|
|
{
|
|
$this->setFlag($mailId, '\\Flagged');
|
|
}
|
|
|
|
/**
|
|
* Causes a store to add the specified flag to the flags set for the mails in the specified sequence.
|
|
*
|
|
* @param array $mailsIds Array of mail IDs
|
|
* @param string $flag Which you can set are \Seen, \Answered, \Flagged, \Deleted, and \Draft as defined by RFC2060
|
|
*/
|
|
public function setFlag(array $mailsIds, $flag)
|
|
{
|
|
$this->imap('setflag_full', [implode(',', $mailsIds), $flag, ST_UID]);
|
|
}
|
|
|
|
/**
|
|
* Causes a store to delete the specified flag to the flags set for the mails in the specified sequence.
|
|
*
|
|
* @param array $mailsIds Array of mail IDs
|
|
* @param string $flag Which you can delete are \Seen, \Answered, \Flagged, \Deleted, and \Draft as defined by RFC2060
|
|
*/
|
|
public function clearFlag(array $mailsIds, $flag)
|
|
{
|
|
$this->imap('clearflag_full', [implode(',', $mailsIds), $flag, ST_UID]);
|
|
}
|
|
|
|
/**
|
|
* Fetch mail headers for listed mails ids.
|
|
*
|
|
* Returns an array of objects describing one mail header each. The object will only define a property if it exists. The possible properties are:
|
|
* subject - the mails subject
|
|
* from - who sent it
|
|
* sender - who sent it
|
|
* to - recipient
|
|
* date - when was it sent
|
|
* message_id - Mail-ID
|
|
* references - is a reference to this mail id
|
|
* in_reply_to - is a reply to this mail id
|
|
* size - size in bytes
|
|
* uid - UID the mail has in the mailbox
|
|
* msgno - mail sequence number in the mailbox
|
|
* recent - this mail is flagged as recent
|
|
* flagged - this mail is flagged
|
|
* answered - this mail is flagged as answered
|
|
* deleted - this mail is flagged for deletion
|
|
* seen - this mail is flagged as already read
|
|
* draft - this mail is flagged as being a draft
|
|
*
|
|
* @return array $mailsIds Array of mail IDs
|
|
*/
|
|
public function getMailsInfo(array $mailsIds)
|
|
{
|
|
$mails = $this->imap('fetch_overview', [implode(',', $mailsIds), (SE_UID == $this->imapSearchOption) ? FT_UID : 0]);
|
|
if (\is_array($mails) && \count($mails)) {
|
|
foreach ($mails as &$mail) {
|
|
if (isset($mail->subject) and !empty(trim($mail->subject))) {
|
|
$mail->subject = $this->decodeMimeStr($mail->subject, $this->getServerEncoding());
|
|
}
|
|
if (isset($mail->from) and !empty(trim($mail->from))) {
|
|
$mail->from = $this->decodeMimeStr($mail->from, $this->getServerEncoding());
|
|
}
|
|
if (isset($mail->sender) and !empty(trim($mail->sender))) {
|
|
$mail->sender = $this->decodeMimeStr($mail->sender, $this->getServerEncoding());
|
|
}
|
|
if (isset($mail->to) and !empty(trim($mail->to))) {
|
|
$mail->to = $this->decodeMimeStr($mail->to, $this->getServerEncoding());
|
|
}
|
|
}
|
|
}
|
|
|
|
return $mails;
|
|
}
|
|
|
|
/**
|
|
* Get headers for all messages in the defined mailbox,
|
|
* returns an array of string formatted with header info,
|
|
* one element per mail message.
|
|
*
|
|
* @return array
|
|
*
|
|
* @see imap_headers()
|
|
*/
|
|
public function getMailboxHeaders()
|
|
{
|
|
return $this->imap('headers');
|
|
}
|
|
|
|
/**
|
|
* Get information about the current mailbox.
|
|
*
|
|
* Returns an object with following properties:
|
|
* Date - last change (current datetime)
|
|
* Driver - driver
|
|
* Mailbox - name of the mailbox
|
|
* Nmsgs - number of messages
|
|
* Recent - number of recent messages
|
|
* Unread - number of unread messages
|
|
* Deleted - number of deleted messages
|
|
* Size - mailbox size
|
|
*
|
|
* @return object Object with info
|
|
*
|
|
* @see mailboxmsginfo
|
|
*/
|
|
public function getMailboxInfo()
|
|
{
|
|
return $this->imap('mailboxmsginfo');
|
|
}
|
|
|
|
/**
|
|
* Gets mails ids sorted by some criteria.
|
|
*
|
|
* Criteria can be one (and only one) of the following constants:
|
|
* SORTDATE - mail Date
|
|
* SORTARRIVAL - arrival date (default)
|
|
* SORTFROM - mailbox in first From address
|
|
* SORTSUBJECT - mail subject
|
|
* SORTTO - mailbox in first To address
|
|
* SORTCC - mailbox in first cc address
|
|
* SORTSIZE - size of mail in octets
|
|
*
|
|
* @param int $criteria Sorting criteria (eg. SORTARRIVAL)
|
|
* @param bool $reverse Sort reverse or not
|
|
* @param string $searchCriteria See http://php.net/imap_search for a complete list of available criteria
|
|
*
|
|
* @return array Mails ids
|
|
*/
|
|
public function sortMails($criteria = SORTARRIVAL, $reverse = true, $searchCriteria = 'ALL')
|
|
{
|
|
return $this->imap('sort', [$criteria, $reverse, $this->imapSearchOption, $searchCriteria]);
|
|
}
|
|
|
|
/**
|
|
* Get mails count in mail box.
|
|
*
|
|
* @return int
|
|
*
|
|
* @see imap_num_msg()
|
|
*/
|
|
public function countMails()
|
|
{
|
|
return $this->imap('num_msg');
|
|
}
|
|
|
|
/**
|
|
* Retrieve the quota settings per user.
|
|
*
|
|
* @param string $quota_root Should normally be in the form of which mailbox (i.e. INBOX)
|
|
*
|
|
* @return array
|
|
*
|
|
* @see imap_get_quotaroot()
|
|
*/
|
|
protected function getQuota($quota_root = 'INBOX')
|
|
{
|
|
return $this->imap('get_quotaroot', $quota_root);
|
|
}
|
|
|
|
/**
|
|
* Return quota limit in KB.
|
|
*
|
|
* @param string $quota_root Should normally be in the form of which mailbox (i.e. INBOX)
|
|
*
|
|
* @return int
|
|
*/
|
|
public function getQuotaLimit($quota_root = 'INBOX')
|
|
{
|
|
$quota = $this->getQuota($quota_root);
|
|
|
|
return isset($quota['STORAGE']['limit']) ? $quota['STORAGE']['limit'] : 0;
|
|
}
|
|
|
|
/**
|
|
* Return quota usage in KB.
|
|
*
|
|
* @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
|
|
*/
|
|
public function getQuotaUsage($quota_root = 'INBOX')
|
|
{
|
|
$quota = $this->getQuota($quota_root);
|
|
|
|
return isset($quota['STORAGE']['usage']) ? $quota['STORAGE']['usage'] : 0;
|
|
}
|
|
|
|
/**
|
|
* Get raw mail data.
|
|
*
|
|
* @param int $msgId ID of the message
|
|
* @param bool $markAsSeen Mark the email as seen, when set to true
|
|
*
|
|
* @return string Message of the fetched body
|
|
*/
|
|
public function getRawMail($msgId, $markAsSeen = true)
|
|
{
|
|
$options = (SE_UID == $this->imapSearchOption) ? FT_UID : 0;
|
|
if (!$markAsSeen) {
|
|
$options |= FT_PEEK;
|
|
}
|
|
|
|
return $this->imap('fetchbody', [$msgId, '', $options]);
|
|
}
|
|
|
|
/**
|
|
* Get mail header.
|
|
*
|
|
* @param int $mailId ID of the message
|
|
*
|
|
* @return IncomingMailHeader
|
|
*
|
|
* @throws Exception
|
|
*/
|
|
public function getMailHeader($mailId)
|
|
{
|
|
$headersRaw = $this->imap('fetchheader', [$mailId, (SE_UID == $this->imapSearchOption) ? FT_UID : 0]);
|
|
|
|
if (false === $headersRaw) {
|
|
throw new Exception('Empty mail header - fetchheader failed. Invalid mail ID?');
|
|
}
|
|
|
|
$head = imap_rfc822_parse_headers($headersRaw);
|
|
|
|
$header = new IncomingMailHeader();
|
|
$header->headersRaw = $headersRaw;
|
|
$header->headers = $head;
|
|
$header->id = $mailId;
|
|
$header->isDraft = (!isset($head->date)) ? true : false;
|
|
$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]) : '';
|
|
|
|
if (isset($head->date) and !empty(trim($head->date))) {
|
|
$header->date = self::parseDateTime($head->date);
|
|
} elseif (isset($head->Date) and !empty(trim($head->Date))) {
|
|
$header->date = self::parseDateTime($head->Date);
|
|
} else {
|
|
$now = new DateTime();
|
|
$header->date = self::parseDateTime($now->format('Y-m-d H:i:s'));
|
|
}
|
|
|
|
$header->subject = (isset($head->subject) and !empty(trim($head->subject))) ? $this->decodeMimeStr($head->subject, $this->getServerEncoding()) : null;
|
|
if (isset($head->from) and !empty($head->from)) {
|
|
$header->fromHost = isset($head->from[0]->host) ? $head->from[0]->host : (isset($head->from[1]->host) ? $head->from[1]->host : null);
|
|
$header->fromName = (isset($head->from[0]->personal) and !empty(trim($head->from[0]->personal))) ? $this->decodeMimeStr($head->from[0]->personal, $this->getServerEncoding()) : ((isset($head->from[1]->personal) and (!empty(trim($head->from[1]->personal)))) ? $this->decodeMimeStr($head->from[1]->personal, $this->getServerEncoding()) : null);
|
|
$header->fromAddress = strtolower($head->from[0]->mailbox.'@'.$header->fromHost);
|
|
} elseif (preg_match('/smtp.mailfrom=[-0-9a-zA-Z.+_]+@[-0-9a-zA-Z.+_]+.[a-zA-Z]{2,4}/', $headersRaw, $matches)) {
|
|
$header->fromAddress = substr($matches[0], 14);
|
|
}
|
|
if (isset($head->sender) and !empty($head->sender)) {
|
|
$header->senderHost = isset($head->sender[0]->host) ? $head->sender[0]->host : (isset($head->sender[1]->host) ? $head->sender[1]->host : null);
|
|
$header->senderName = (isset($head->sender[0]->personal) and !empty(trim($head->sender[0]->personal))) ? $this->decodeMimeStr($head->sender[0]->personal, $this->getServerEncoding()) : ((isset($head->sender[1]->personal) and (!empty(trim($head->sender[1]->personal)))) ? $this->decodeMimeStr($head->sender[1]->personal, $this->getServerEncoding()) : null);
|
|
$header->senderAddress = strtolower($head->sender[0]->mailbox.'@'.$header->senderHost);
|
|
}
|
|
if (isset($head->to)) {
|
|
$toStrings = [];
|
|
foreach ($head->to as $to) {
|
|
if (!empty($to->mailbox) && !empty(trim($to->host))) {
|
|
$toEmail = strtolower($to->mailbox.'@'.$to->host);
|
|
$toName = (isset($to->personal) and !empty(trim($to->personal))) ? $this->decodeMimeStr($to->personal, $this->getServerEncoding()) : null;
|
|
$toStrings[] = $toName ? "$toName <$toEmail>" : $toEmail;
|
|
$header->to[$toEmail] = $toName;
|
|
}
|
|
}
|
|
$header->toString = implode(', ', $toStrings);
|
|
}
|
|
|
|
if (isset($head->cc)) {
|
|
foreach ($head->cc as $cc) {
|
|
if (!empty(trim($cc->mailbox)) && !empty(trim($cc->host))) {
|
|
$ccEmail = strtolower($cc->mailbox.'@'.$cc->host);
|
|
$ccName = (isset($cc->personal) and !empty(trim($cc->personal))) ? $this->decodeMimeStr($cc->personal, $this->getServerEncoding()) : null;
|
|
$ccStrings[] = $ccName ? "$ccName <$ccEmail>" : $ccEmail;
|
|
$header->cc[$ccEmail] = $ccName;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isset($head->bcc)) {
|
|
foreach ($head->bcc as $bcc) {
|
|
if (!empty(trim($bcc->mailbox)) && !empty(trim($bcc->host))) {
|
|
$bccEmail = strtolower($bcc->mailbox.'@'.$bcc->host);
|
|
$bccName = (isset($bcc->personal) and !empty(trim($bcc->personal))) ? $this->decodeMimeStr($bcc->personal, $this->getServerEncoding()) : null;
|
|
$bccStrings[] = $bccName ? "$bccName <$bccEmail>" : $bccEmail;
|
|
$header->bcc[$bccEmail] = $bccName;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isset($head->reply_to)) {
|
|
foreach ($head->reply_to as $replyTo) {
|
|
if (!empty(trim($replyTo->mailbox)) && !empty(trim($replyTo->host))) {
|
|
$replyToEmail = strtolower($replyTo->mailbox.'@'.$replyTo->host);
|
|
$replyToName = (isset($replyTo->personal) and !empty(trim($replyTo->personal))) ? $this->decodeMimeStr($replyTo->personal, $this->getServerEncoding()) : null;
|
|
$replyToStrings[] = $replyToName ? "$replyToName <$replyToEmail>" : $replyToEmail;
|
|
$header->replyTo[$replyToEmail] = $replyToName;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (isset($head->message_id)) {
|
|
$header->messageId = $head->message_id;
|
|
}
|
|
|
|
return $header;
|
|
}
|
|
|
|
/**
|
|
* taken from https://www.electrictoolbox.com/php-imap-message-parts/.
|
|
*/
|
|
public function flattenParts($messageParts, $flattenedParts = [], $prefix = '', $index = 1, $fullPrefix = true)
|
|
{
|
|
foreach ($messageParts as $part) {
|
|
$flattenedParts[$prefix.$index] = $part;
|
|
if (isset($part->parts)) {
|
|
if (2 == $part->type) {
|
|
$flattenedParts = $this->flattenParts($part->parts, $flattenedParts, $prefix.$index.'.', 0, false);
|
|
} elseif ($fullPrefix) {
|
|
$flattenedParts = $this->flattenParts($part->parts, $flattenedParts, $prefix.$index.'.');
|
|
} else {
|
|
$flattenedParts = $this->flattenParts($part->parts, $flattenedParts, $prefix);
|
|
}
|
|
unset($flattenedParts[$prefix.$index]->parts);
|
|
}
|
|
++$index;
|
|
}
|
|
|
|
return $flattenedParts;
|
|
}
|
|
|
|
/**
|
|
* Get mail data.
|
|
*
|
|
* @param int $mailId ID of the mail
|
|
* @param bool $markAsSeen Mark the email as seen, when set to true
|
|
*
|
|
* @return IncomingMail
|
|
*/
|
|
public function getMail($mailId, $markAsSeen = true)
|
|
{
|
|
$mail = new IncomingMail();
|
|
$mail->setHeader($this->getMailHeader($mailId));
|
|
|
|
$mailStructure = $this->imap('fetchstructure', [$mailId, (SE_UID == $this->imapSearchOption) ? FT_UID : 0]);
|
|
|
|
if (empty($mailStructure->parts)) {
|
|
$this->initMailPart($mail, $mailStructure, 0, $markAsSeen);
|
|
} else {
|
|
foreach ($this->flattenParts($mailStructure->parts) as $partNum => $partStructure) {
|
|
$this->initMailPart($mail, $partStructure, $partNum, $markAsSeen);
|
|
}
|
|
}
|
|
|
|
return $mail;
|
|
}
|
|
|
|
protected function initMailPart(IncomingMail $mail, $partStructure, $partNum, $markAsSeen = true, $emlParse = false)
|
|
{
|
|
$options = (SE_UID == $this->imapSearchOption) ? FT_UID : 0;
|
|
|
|
if (!$markAsSeen) {
|
|
$options |= FT_PEEK;
|
|
}
|
|
$dataInfo = new DataPartInfo($this, $mail->id, $partNum, $partStructure->encoding, $options);
|
|
|
|
$params = [];
|
|
if (!empty($partStructure->parameters)) {
|
|
foreach ($partStructure->parameters as $param) {
|
|
$params[strtolower($param->attribute)] = (!isset($param->value) || empty(trim($param->value))) ? '' : $this->decodeMimeStr($param->value);
|
|
}
|
|
}
|
|
if (!empty($partStructure->dparameters)) {
|
|
foreach ($partStructure->dparameters as $param) {
|
|
$paramName = strtolower(preg_match('~^(.*?)\*~', $param->attribute, $matches) ? $matches[1] : $param->attribute);
|
|
if (isset($params[$paramName])) {
|
|
$params[$paramName] .= $param->value;
|
|
} else {
|
|
$params[$paramName] = $param->value;
|
|
}
|
|
}
|
|
}
|
|
|
|
$isAttachment = isset($params['filename']) || isset($params['name']);
|
|
|
|
// ignore contentId on body when mail isn't multipart (https://github.com/barbushin/php-imap/issues/71)
|
|
if (!$partNum && TYPETEXT === $partStructure->type) {
|
|
$isAttachment = false;
|
|
}
|
|
|
|
if ($isAttachment) {
|
|
$mail->setHasAttachments(true);
|
|
}
|
|
|
|
// check if the part is a subpart of another attachment part (RFC822)
|
|
if ('RFC822' == $partStructure->subtype && isset($partStructure->disposition) && 'attachment' == $partStructure->disposition) {
|
|
// Although we are downloading each part separately, we are going to download the EML to a single file
|
|
//incase someone wants to process or parse in another process
|
|
$attachment = self::downloadAttachment($dataInfo, $params, $partStructure, $mail->id, false);
|
|
$mail->addAttachment($attachment);
|
|
}
|
|
|
|
// If it comes from an EML file it is an attachment
|
|
if ($emlParse) {
|
|
$isAttachment = true;
|
|
}
|
|
|
|
// Do NOT parse attachments, when getAttachmentsIgnore() is true
|
|
if ($this->getAttachmentsIgnore()
|
|
&& (TYPEMULTIPART !== $partStructure->type
|
|
&& (TYPETEXT !== $partStructure->type || !\in_array(strtolower($partStructure->subtype), ['plain', 'html'])))
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
if ($isAttachment) {
|
|
$attachment = self::downloadAttachment($dataInfo, $params, $partStructure, $mail->id, $emlParse);
|
|
$mail->addAttachment($attachment);
|
|
} else {
|
|
if (isset($params['charset']) and !empty(trim($params['charset']))) {
|
|
$dataInfo->charset = $params['charset'];
|
|
}
|
|
}
|
|
|
|
if (!empty($partStructure->parts)) {
|
|
foreach ($partStructure->parts as $subPartNum => $subPartStructure) {
|
|
if (TYPEMESSAGE === $partStructure->type && 'RFC822' == $partStructure->subtype && (!isset($partStructure->disposition) || 'attachment' !== $partStructure->disposition)) {
|
|
$this->initMailPart($mail, $subPartStructure, $partNum, $markAsSeen);
|
|
} elseif (TYPEMULTIPART === $partStructure->type && 'ALTERNATIVE' == $partStructure->subtype && (!isset($partStructure->disposition) || 'attachment' !== $partStructure->disposition)) {
|
|
// https://github.com/barbushin/php-imap/issues/198
|
|
$this->initMailPart($mail, $subPartStructure, $partNum, $markAsSeen);
|
|
} elseif ('RFC822' == $partStructure->subtype && isset($partStructure->disposition) && 'attachment' == $partStructure->disposition) {
|
|
//If it comes from am EML attachment, download each part separately as a file
|
|
$this->initMailPart($mail, $subPartStructure, $partNum.'.'.($subPartNum + 1), $markAsSeen, true);
|
|
} else {
|
|
$this->initMailPart($mail, $subPartStructure, $partNum.'.'.($subPartNum + 1), $markAsSeen);
|
|
}
|
|
}
|
|
} else {
|
|
if (TYPETEXT === $partStructure->type) {
|
|
if ('plain' == strtolower($partStructure->subtype)) {
|
|
$mail->addDataPartInfo($dataInfo, DataPartInfo::TEXT_PLAIN);
|
|
} elseif (!$partStructure->ifdisposition) {
|
|
$mail->addDataPartInfo($dataInfo, DataPartInfo::TEXT_HTML);
|
|
} elseif ('attachment' != strtolower($partStructure->disposition)) {
|
|
$mail->addDataPartInfo($dataInfo, DataPartInfo::TEXT_HTML);
|
|
}
|
|
} elseif (TYPEMESSAGE === $partStructure->type) {
|
|
$mail->addDataPartInfo($dataInfo, DataPartInfo::TEXT_PLAIN);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Download attachment.
|
|
*
|
|
* @param string $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
|
|
*/
|
|
public function downloadAttachment(DataPartInfo $dataInfo, $params, $partStructure, $mailId, $emlOrigin = false)
|
|
{
|
|
if ('RFC822' == $partStructure->subtype && isset($partStructure->disposition) && 'attachment' == $partStructure->disposition) {
|
|
$fileExt = strtolower($partStructure->subtype).'.eml';
|
|
} elseif ('ALTERNATIVE' == $partStructure->subtype) {
|
|
$fileExt = strtolower($partStructure->subtype).'.eml';
|
|
} elseif (!isset($params['filename']) or empty(trim($params['filename'])) && (!isset($params['name']) or empty(trim($params['name'])))) {
|
|
$fileExt = strtolower($partStructure->subtype);
|
|
} else {
|
|
$fileName = (isset($params['filename']) and !empty(trim($params['filename']))) ? $params['filename'] : $params['name'];
|
|
$fileName = $this->decodeMimeStr($fileName, $this->getServerEncoding());
|
|
$fileName = $this->decodeRFC2231($fileName, $this->getServerEncoding());
|
|
}
|
|
|
|
if (isset($fileExt) && isset($fileName)) {
|
|
$fileName = $fileName.'.'.$fileExt;
|
|
} elseif (isset($fileExt) && !isset($fileName)) {
|
|
$fileName = $fileExt;
|
|
} elseif (!isset($fileExt) && isset($fileName)) {
|
|
$fileName = $fileName;
|
|
}
|
|
|
|
$attachmentId = sha1($fileName.($partStructure->ifid ? $partStructure->id : ''));
|
|
|
|
$attachment = new IncomingMailAttachment();
|
|
$attachment->id = $attachmentId;
|
|
$attachment->contentId = $partStructure->ifid ? trim($partStructure->id, ' <>') : null;
|
|
$attachment->name = $fileName;
|
|
$attachment->disposition = (isset($partStructure->disposition) ? $partStructure->disposition : null);
|
|
$attachment->charset = (isset($params['charset']) and !empty(trim($params['charset']))) ? $params['charset'] : null;
|
|
$attachment->emlOrigin = $emlOrigin;
|
|
|
|
$attachment->addDataPartInfo($dataInfo);
|
|
|
|
if (null != $this->getAttachmentsDir()) {
|
|
$replace = [
|
|
'/\s/' => '_',
|
|
'/[^\w\.]/iu' => '',
|
|
'/_+/' => '_',
|
|
'/(^_)|(_$)/' => '',
|
|
];
|
|
$fileSysName = preg_replace('~[\\\\/]~', '', $mailId.'_'.$attachmentId.'_'.preg_replace(array_keys($replace), $replace, $fileName));
|
|
$filePath = $this->getAttachmentsDir().\DIRECTORY_SEPARATOR.$fileSysName;
|
|
|
|
if (\strlen($filePath) > 255) {
|
|
$ext = pathinfo($filePath, PATHINFO_EXTENSION);
|
|
$filePath = substr($filePath, 0, 255 - 1 - \strlen($ext)).'.'.$ext;
|
|
}
|
|
$attachment->setFilePath($filePath);
|
|
$attachment->saveToDisk();
|
|
}
|
|
|
|
return $attachment;
|
|
}
|
|
|
|
/**
|
|
* Decodes a mime string.
|
|
*
|
|
* @param string $string MIME string to decode
|
|
*
|
|
* @return string Converted string if conversion was successful, or the original string if not
|
|
*
|
|
* @throws Exception
|
|
*/
|
|
public function decodeMimeStr($string, $toCharset = 'utf-8')
|
|
{
|
|
if (empty(trim($string))) {
|
|
throw new Exception('decodeMimeStr() Can not decode an empty string!');
|
|
}
|
|
|
|
$newString = '';
|
|
foreach (imap_mime_header_decode($string) as $element) {
|
|
if (isset($element->text)) {
|
|
$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;
|
|
$newString .= $this->convertStringEncoding($element->text, $fromCharset, $toCharset);
|
|
}
|
|
}
|
|
|
|
return $newString;
|
|
}
|
|
|
|
public function isUrlEncoded($string)
|
|
{
|
|
$hasInvalidChars = preg_match('#[^%a-zA-Z0-9\-_\.\+]#', $string);
|
|
$hasEscapedChars = preg_match('#%[a-zA-Z0-9]{2}#', $string);
|
|
|
|
return !$hasInvalidChars && $hasEscapedChars;
|
|
}
|
|
|
|
protected function decodeRFC2231($string, $charset = 'utf-8')
|
|
{
|
|
if (preg_match("/^(.*?)'.*?'(.*?)$/", $string, $matches)) {
|
|
$encoding = $matches[1];
|
|
$data = $matches[2];
|
|
if ($this->isUrlEncoded($data)) {
|
|
$string = $this->convertStringEncoding(urldecode($data), $encoding, $charset);
|
|
}
|
|
}
|
|
|
|
return $string;
|
|
}
|
|
|
|
/**
|
|
* Converts the datetime to a RFC 3339 compliant format.
|
|
*
|
|
* @param string $dateHeader Header datetime
|
|
*
|
|
* @return string RFC 3339 compliant format or original (unchanged) format,
|
|
* if conversation is not possible
|
|
*/
|
|
public function parseDateTime($dateHeader)
|
|
{
|
|
if (empty(trim($dateHeader))) {
|
|
throw new InvalidParameterException('parseDateTime() expects parameter 1 to be a parsable string datetime');
|
|
}
|
|
|
|
$dateHeaderUnixtimestamp = strtotime($dateHeader);
|
|
|
|
if (!$dateHeaderUnixtimestamp) {
|
|
return $dateHeader;
|
|
}
|
|
|
|
$dateHeaderRfc3339 = date(DATE_RFC3339, $dateHeaderUnixtimestamp);
|
|
|
|
if (!$dateHeaderRfc3339) {
|
|
return $dateHeader;
|
|
}
|
|
|
|
return $dateHeaderRfc3339;
|
|
}
|
|
|
|
/**
|
|
* Converts a string from one encoding to another.
|
|
*
|
|
* @param string $string the string, which you want to convert
|
|
* @param string $fromEncoding the current charset (encoding)
|
|
* @param string $toEncoding the new charset (encoding)
|
|
*
|
|
* @return string Converted string if conversion was successful, or the original string if not
|
|
*/
|
|
public function convertStringEncoding($string, $fromEncoding, $toEncoding)
|
|
{
|
|
if (preg_match('/default|ascii/i', $fromEncoding) || !$string || $fromEncoding == $toEncoding) {
|
|
return $string;
|
|
}
|
|
$convertedString = '';
|
|
$mbLoaded = \extension_loaded('mbstring');
|
|
$supportedEncodings = [];
|
|
if ($mbLoaded) {
|
|
$supportedEncodings = array_map('strtolower', mb_list_encodings());
|
|
}
|
|
if ($mbLoaded && \in_array(strtolower($fromEncoding), $supportedEncodings) && \in_array(strtolower($toEncoding), $supportedEncodings)) {
|
|
$convertedString = mb_convert_encoding($string, $toEncoding, $fromEncoding);
|
|
} elseif (\function_exists('iconv')) {
|
|
$convertedString = @iconv($fromEncoding, $toEncoding.'//TRANSLIT//IGNORE', $string);
|
|
}
|
|
if (('' == $convertedString) or (false === $convertedString)) {
|
|
return $string;
|
|
}
|
|
|
|
return $convertedString;
|
|
}
|
|
|
|
/**
|
|
* Disconnects from the IMAP server / mailbox.
|
|
*/
|
|
public function __destruct()
|
|
{
|
|
$this->disconnect();
|
|
}
|
|
|
|
/**
|
|
* Gets IMAP path.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getImapPath()
|
|
{
|
|
return $this->imapPath;
|
|
}
|
|
|
|
/**
|
|
* Get message in MBOX format.
|
|
*
|
|
* @param int $mailId message number
|
|
*
|
|
* @return string
|
|
*/
|
|
public function getMailMboxFormat($mailId)
|
|
{
|
|
$option = (SE_UID == $this->imapSearchOption) ? FT_UID : 0;
|
|
|
|
return imap_fetchheader($this->getImapStream(), $mailId, $option && FT_PREFETCHTEXT).imap_body($this->getImapStream(), $mailId, $option);
|
|
}
|
|
|
|
/**
|
|
* Get folders list.
|
|
*
|
|
* @param string $search
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getMailboxes($search = '*')
|
|
{
|
|
$arr = [];
|
|
if ($t = imap_getmailboxes($this->getImapStream(), $this->imapPath, $search)) {
|
|
foreach ($t as $item) {
|
|
// https://github.com/barbushin/php-imap/issues/339
|
|
$name = $this->decodeStringFromUtf7ImapToUtf8($item->name);
|
|
$arr[] = [
|
|
'fullpath' => $name,
|
|
'attributes' => $item->attributes,
|
|
'delimiter' => $item->delimiter,
|
|
'shortpath' => substr($name, strpos($name, '}') + 1),
|
|
];
|
|
}
|
|
}
|
|
|
|
return $arr;
|
|
}
|
|
|
|
/**
|
|
* Get folders list.
|
|
*
|
|
* @param string $search
|
|
*
|
|
* @return array
|
|
*/
|
|
public function getSubscribedMailboxes($search = '*')
|
|
{
|
|
$arr = [];
|
|
if ($t = imap_getsubscribed($this->getImapStream(), $this->imapPath, $search)) {
|
|
foreach ($t as $item) {
|
|
// https://github.com/barbushin/php-imap/issues/339
|
|
$name = $this->decodeStringFromUtf7ImapToUtf8($item->name);
|
|
$arr[] = [
|
|
'fullpath' => $name,
|
|
'attributes' => $item->attributes,
|
|
'delimiter' => $item->delimiter,
|
|
'shortpath' => substr($name, strpos($name, '}') + 1),
|
|
];
|
|
}
|
|
}
|
|
|
|
return $arr;
|
|
}
|
|
|
|
/**
|
|
* Subscribe to a mailbox.
|
|
*
|
|
* @param string $mailbox
|
|
*
|
|
* @throws Exception
|
|
*/
|
|
public function subscribeMailbox($mailbox)
|
|
{
|
|
$this->imap('subscribe', $this->getCombinedPath($mailbox));
|
|
}
|
|
|
|
/**
|
|
* Unsubscribe from a mailbox.
|
|
*
|
|
* @param string $mailbox
|
|
*
|
|
* @throws Exception
|
|
*/
|
|
public function unsubscribeMailbox($mailbox)
|
|
{
|
|
$this->imap('unsubscribe', $this->getCombinedPath($mailbox));
|
|
}
|
|
|
|
/**
|
|
* Call IMAP extension function call wrapped with utf7 args conversion & errors handling.
|
|
*
|
|
* @param string $methodShortName Name of PHP imap_ method, but without the 'imap_' prefix. (eg. imap_fetch_overview => fetch_overview)
|
|
* @param array|string $args All arguments of the original method, except the 'resource $imap_stream' (eg. imap_fetch_overview => string $sequence [, int $options = 0 ])
|
|
* @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
|
|
*
|
|
* @throws Exception
|
|
*/
|
|
public function imap($methodShortName, $args = [], $prependConnectionAsFirstArg = true, $throwExceptionClass = Exception::class)
|
|
{
|
|
if (!\is_array($args)) {
|
|
$args = [$args];
|
|
}
|
|
// https://github.com/barbushin/php-imap/issues/242
|
|
if (\in_array($methodShortName, ['open'])) {
|
|
// Mailbox names that contain international characters besides those in the printable ASCII space have to be encoded with imap_utf7_encode().
|
|
// https://www.php.net/manual/en/function.imap-open.php
|
|
if (\is_string($args[0])) {
|
|
if (preg_match("/^\{.*\}(.*)$/", $args[0], $matches)) {
|
|
$mailbox_name = $matches[1];
|
|
|
|
if (!mb_detect_encoding($mailbox_name, 'ASCII', true)) {
|
|
$args[0] = $this->encodeStringToUtf7Imap($mailbox_name);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
foreach ($args as &$arg) {
|
|
if (\is_string($arg)) {
|
|
$arg = $this->encodeStringToUtf7Imap($arg);
|
|
}
|
|
}
|
|
}
|
|
if ($prependConnectionAsFirstArg) {
|
|
array_unshift($args, $this->getImapStream());
|
|
}
|
|
|
|
imap_errors(); // flush errors
|
|
$result = @\call_user_func_array("imap_$methodShortName", $args);
|
|
|
|
if (!$result) {
|
|
$errors = imap_errors();
|
|
if ($errors) {
|
|
if ($throwExceptionClass) {
|
|
throw new $throwExceptionClass("IMAP method imap_$methodShortName() failed with error: ".implode('. ', $errors));
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Combine Subfolder or Folder to the connection.
|
|
* Have the imapPath a folder added to the connection info, then will the $folder added as subfolder.
|
|
* If the parameter $absolute TRUE, then will the connection new builded only with this folder as root element.
|
|
*
|
|
* @param string $folder Folder, the will added to the path
|
|
* @param bool $absolute Add folder as root element to the connection and remove all other from this
|
|
*
|
|
* @return string Return the new path
|
|
*/
|
|
protected function getCombinedPath($folder, $absolute = false)
|
|
{
|
|
if (empty(trim($folder))) {
|
|
return $this->imapPath;
|
|
} elseif ('}' === substr($this->imapPath, -1)) {
|
|
return $this->imapPath.$folder;
|
|
} elseif (true === $absolute) {
|
|
$folder = ('/' === $folder) ? '' : $folder;
|
|
$posConnectionDefinitionEnd = strpos($this->imapPath, '}');
|
|
|
|
return substr($this->imapPath, 0, $posConnectionDefinitionEnd + 1).$folder;
|
|
}
|
|
|
|
return $this->imapPath.$this->getPathDelimiter().$folder;
|
|
}
|
|
}
|