%PDF- %PDF-
| Direktori : /home/bitrix/www/bitrix/modules/mail/lib/helper/mailbox/ |
| Current File : /home/bitrix/www/bitrix/modules/mail/lib/helper/mailbox/imap.php |
<?php
namespace Bitrix\Mail\Helper\Mailbox;
use Bitrix\Mail\Helper\MessageFolder;
use Bitrix\Main;
use Bitrix\Mail;
class Imap extends Mail\Helper\Mailbox
{
//const SYNC_TIME_QUOTA = 120;
protected $client;
protected function __construct($mailbox)
{
parent::__construct($mailbox);
$mailbox = &$this->mailbox;
$options = &$mailbox['OPTIONS'];
if (empty($options['imap']) || !is_array($options['imap']))
{
$options['imap'] = array();
}
$imapOptions = &$options['imap'];
if (empty($imapOptions[MessageFolder::INCOME]) || !is_array($imapOptions[MessageFolder::INCOME]))
{
$imapOptions[MessageFolder::INCOME] = array();
}
if (empty($imapOptions[MessageFolder::OUTCOME]) || !is_array($imapOptions[MessageFolder::OUTCOME]))
{
$imapOptions[MessageFolder::OUTCOME] = array();
}
$imapOptions[MessageFolder::OUTCOME] = array_diff($imapOptions[MessageFolder::OUTCOME], $imapOptions[MessageFolder::INCOME]);
$this->client = new Mail\Imap(
$mailbox['SERVER'],
$mailbox['PORT'],
$mailbox['USE_TLS'] == 'Y' || $mailbox['USE_TLS'] == 'S',
$mailbox['USE_TLS'] == 'Y',
$mailbox['LOGIN'],
$mailbox['PASSWORD']
);
}
public function getSyncStatus()
{
$meta = Mail\MailMessageUidTable::getList(array(
'select' => array(
new Main\Entity\ExpressionField('TOTAL', 'COUNT(%s)', 'MESSAGE_ID'),
),
'filter' => array(
'=MAILBOX_ID' => $this->mailbox['ID'],
'>MESSAGE_ID' => 0,
),
))->fetch();
if ($meta['TOTAL'] > 0 && $this->mailbox['OPTIONS']['imap']['total'] > 0)
{
return $meta['TOTAL'] / $this->mailbox['OPTIONS']['imap']['total'];
}
else
{
return parent::getSyncStatus();
}
}
protected function syncInternal()
{
$count = $this->syncMailbox();
if (false === $count)
{
$this->errors = new Main\ErrorCollection($this->client->getErrors()->toArray());
}
return $count;
}
protected function createMessage(Main\Mail\Mail $message, array $fields = array())
{
$dir = reset($this->mailbox['OPTIONS']['imap'][MessageFolder::OUTCOME]) ?: 'INBOX';
$fields = array_merge(
$fields,
array(
'DIR_MD5' => md5($dir),
'DIR_UIDV' => 0,
'MSG_UID' => 0,
)
);
return parent::createMessage($message, $fields);
}
public function uploadMessage(Main\Mail\Mail $message, array $excerpt = null)
{
$dir = reset($this->mailbox['OPTIONS']['imap'][MessageFolder::OUTCOME]) ?: 'INBOX';
$data = $this->client->select($dir, $error);
if (false === $data)
{
$this->errors = new Main\ErrorCollection($this->client->getErrors()->toArray());
return false;
}
if (!empty($excerpt['__unique_headers']))
{
if ($this->client->searchByHeader(false, $dir, $excerpt['__unique_headers'], $error))
{
return false;
}
}
$flags = array('\Seen');
if (!empty($excerpt['ID']) && preg_grep('/^ \x5c \* $/ix', $data['permanentflags']))
{
$flags[] = sprintf('bxuid%s', $excerpt['ID']);
}
$result = $this->client->append(
$dir,
$flags,
new \DateTime,
sprintf(
'%1$s%3$s%3$s%2$s',
$message->getHeaders(),
$message->getBody(),
$message->getMailEol()
),
$error
);
if (false === $result)
{
$this->errors = new Main\ErrorCollection($this->client->getErrors()->toArray());
return false;
}
$this->syncDir($dir);
return $result;
}
public function cacheDirs()
{
$dirs = $this->client->listMailboxes('*', $error, true);
if (false === $dirs)
{
$this->errors = new Main\ErrorCollection($this->client->getErrors()->toArray());
return false;
}
$imapDirs = array();
$disabledDirs = array();
$outcomeDirs = array();
$trashDirs = array();
$spamDirs = array();
$inboxDirs = array();
$inboxTrees = array();
foreach ($dirs as $i => $item)
{
if (strtoupper($item['name']) === 'INBOX')
{
if (!$inboxDirs)
{
$inboxDirs = [$item['name']];
}
else
{
$disabledDirs[] = $item['name'];
}
}
}
foreach ($dirs as $i => $item)
{
$imapDirs[$item['name']] = $item['path'];
if (in_array(reset($item['path']), $inboxDirs))
{
$inboxTrees[$item['name']] = $item['path'];
}
if (preg_grep('/^ \x5c Noselect $/ix', $item['flags']))
{
$disabledDirs[] = $item['name'];
}
if (preg_grep('/^ \x5c Sent $/ix', $item['flags']))
{
$outcomeDirs[] = $item['name'];
}
if (preg_grep('/^ \x5c Trash $/ix', $item['flags']))
{
$trashDirs[] = $item['name'];
}
if (preg_grep('/^ \x5c ( Junk | Spam ) $/ix', $item['flags']))
{
$spamDirs[] = $item['name'];
}
}
$options = &$this->mailbox['OPTIONS'];
$options['imap']['dirs'] = $inboxTrees + $imapDirs;
$options['imap']['disabled'] = $disabledDirs;
$options['imap'][MessageFolder::INCOME] = $inboxDirs;
if (empty($options['imap'][MessageFolder::OUTCOME]))
{
$options['imap'][MessageFolder::OUTCOME] = $outcomeDirs;
}
if (empty($options['imap'][MessageFolder::TRASH]))
{
$options['imap'][MessageFolder::TRASH] = $trashDirs;
}
if (empty($options['imap'][MessageFolder::SPAM]))
{
$options['imap'][MessageFolder::SPAM] = $spamDirs;
}
if (!empty($options['imap']['!sync_dirs']))
{
$options['imap']['sync_dirs'] = $options['imap']['!sync_dirs'];
}
if (!empty($options['imap']['sync_dirs']))
{
$options['imap']['ignore'] = array_values(array_diff(
array_keys($imapDirs),
(array) $options['imap']['sync_dirs']
));
unset($options['imap']['sync_dirs']);
}
if (!array_key_exists('ignore', $options['imap']))
{
$options['imap']['ignore'] = array_merge(
(array) $options['imap'][MessageFolder::TRASH],
(array) $options['imap'][MessageFolder::SPAM]
);
}
$options['imap']['ignore'] = array_unique(array_merge(
(array) $options['imap']['ignore'],
$options['imap']['disabled']
));
Mail\MailboxTable::update(
$this->mailbox['ID'],
array(
'OPTIONS' => $options,
)
);
return $dirs;
}
public function cacheMeta()
{
$dirs = $this->cacheDirs();
if (false === $dirs)
{
return false;
}
$meta = array();
foreach ($dirs as $i => $item)
{
if (!in_array($item['name'], (array) $this->mailbox['OPTIONS']['imap']['ignore']))
{
if (!preg_grep('/^ \x5c Noselect $/ix', $item['flags']))
{
$data = $this->client->examine($item['name'], $error);
if (false === $data)
{
$this->warnings->add($this->client->getErrors()->toArray());
}
else
{
$item = array_merge($item, $data);
}
}
}
$meta[$item['name']] = $item;
}
$options = &$this->mailbox['OPTIONS'];
$options['imap']['total'] = array_reduce(
$meta,
function ($sum, $item)
{
return $sum + $item['exists'];
},
0
);
Mail\MailboxTable::update(
$this->mailbox['ID'],
array(
'OPTIONS' => $options,
)
);
return $meta;
}
protected function getFolderToMessagesMap($messages)
{
if (isset($messages['MSG_UID']))
{
$messages = [$messages];
}
$data = [];
$result = new Main\Result();
foreach ($messages as $message)
{
$id = $message['MSG_UID'];
$folderFrom = MessageFolder::getFolderNameByHash($message['DIR_MD5'], $this->mailbox['OPTIONS']);
$data[$folderFrom][] = $id;
$results[$folderFrom][] = $message;
}
return $result->setData($data);
}
public function markUnseen($messages)
{
$result = $this->getFolderToMessagesMap($messages);
foreach ($result->getData() as $folderFrom => $ids)
{
$result = $this->client->unseen($ids, $folderFrom);
if (!$result->isSuccess() || !$this->client->getErrors()->isEmpty())
{
break;
}
}
return $result;
}
public function markSeen($messages)
{
$result = $this->getFolderToMessagesMap($messages);
foreach ($result->getData() as $folderFrom => $ids)
{
$result = $this->client->seen($ids, $folderFrom);
if (!$result->isSuccess() || !$this->client->getErrors()->isEmpty())
{
break;
}
}
return $result;
}
public function moveMailsToFolder($messages, $folderTo)
{
$result = $this->getFolderToMessagesMap($messages);
$moveResult = new Main\Result();
foreach ($result->getData() as $folderFrom => $ids)
{
$moveResult = $this->client->moveMails($ids, $folderFrom, $folderTo);
if (!$moveResult->isSuccess() || !$this->client->getErrors()->isEmpty())
{
break;
}
}
return $moveResult;
}
public function deleteMails($messages)
{
$result = $this->getFolderToMessagesMap($messages);
foreach ($result->getData() as $folderName => $messageId)
{
$result = $this->client->delete($messageId, $folderName);
}
return $result;
}
public function syncMailbox()
{
if (!$this->client->authenticate($error))
{
return false;
}
$meta = $this->cacheMeta();
if (false === $meta)
{
return false;
}
$count = 0;
$queue = array(
'inbox' => array(),
MessageFolder::OUTCOME => array(),
'other' => array(),
MessageFolder::TRASH => array(),
MessageFolder::SPAM => array(),
);
foreach ($meta as $dir => $item)
{
if (in_array($dir, (array) $this->mailbox['OPTIONS']['imap']['ignore']))
{
continue;
}
if ('inbox' == strtolower(reset($item['path'])))
{
$queue['inbox'][$dir] = $item;
}
else if (in_array($dir, (array) $this->mailbox['OPTIONS']['imap'][MessageFolder::OUTCOME]))
{
$queue[MessageFolder::OUTCOME][$dir] = $item;
}
else if (in_array($dir, (array) $this->mailbox['OPTIONS']['imap'][MessageFolder::TRASH]))
{
$queue[MessageFolder::TRASH][$dir] = $item;
}
else if (in_array($dir, (array) $this->mailbox['OPTIONS']['imap'][MessageFolder::SPAM]))
{
$queue[MessageFolder::SPAM][$dir] = $item;
}
else
{
$queue['other'][$dir] = $item;
}
}
$meta = call_user_func_array('array_merge', $queue);
foreach ($meta as $dir => $item)
{
if ($item['exists'] > 0)
{
$count += $this->syncDir($dir);
if ($this->isTimeQuotaExceeded())
{
break;
}
}
}
if (!$this->isTimeQuotaExceeded())
{
$this->unregisterMessages(array(
'!@DIR_MD5' => array_map(
'md5',
array_diff(
array_keys($meta),
(array) $this->mailbox['OPTIONS']['imap']['ignore']
)
),
));
foreach ($meta as $dir => $item)
{
$this->resyncDir($dir);
if ($this->isTimeQuotaExceeded())
{
break;
}
}
}
return $count;
}
public function syncDir($dir)
{
$count = 0;
if (in_array($dir, (array) $this->mailbox['OPTIONS']['imap']['ignore']))
{
return $count;
}
while ($range = $this->getSyncRange($dir, $uidtoken))
{
$reverse = $range[0] > $range[1];
sort($range);
$messages = $this->client->fetch(
true,
$dir,
join(':', $range),
'(UID FLAGS INTERNALDATE RFC822.SIZE BODY.PEEK[HEADER])',
$error
);
if (empty($messages))
{
if (false === $messages)
{
$this->warnings->add($this->client->getErrors()->toArray());
}
else
{
// @TODO: log
}
break;
}
$reverse ? krsort($messages) : ksort($messages);
$this->blacklistMessages($dir, $messages);
$this->prepareMessages($dir, $uidtoken, $messages);
foreach ($messages as $id => $item)
{
if ($this->syncMessage($dir, $uidtoken, $item))
{
$count++;
}
if ($this->isTimeQuotaExceeded())
{
break 2;
}
}
}
if (false === $range)
{
$this->warnings->add($this->client->getErrors()->toArray());
}
return $count;
}
public function resyncDir($dir)
{
if (in_array($dir, (array) $this->mailbox['OPTIONS']['imap']['ignore']))
{
$this->unregisterMessages(array(
'=DIR_MD5' => md5($dir),
));
return;
}
$meta = $this->client->select($dir, $error);
if (false === $meta)
{
$this->warnings->add($this->client->getErrors()->toArray());
return;
}
$uidtoken = $meta['uidvalidity'];
if ($meta['exists'] > 0)
{
if ($uidtoken > 0)
{
$this->unregisterMessages(array(
'=DIR_MD5' => md5($dir),
'<DIR_UIDV' => $uidtoken,
));
}
}
else
{
$this->unregisterMessages(array(
'=DIR_MD5' => md5($dir),
));
return;
}
$fetcher = function ($range) use ($dir)
{
$messages = $this->client->fetch(false, $dir, $range, '(UID FLAGS)', $error);
if (empty($messages))
{
if (false === $messages)
{
$this->warnings->add($this->client->getErrors()->toArray());
}
else
{
// @TODO: log
}
return;
}
krsort($messages);
return $messages;
};
$messages = $fetcher($meta['exists'] > 10000 ? '1,*' : '1:*');
if (empty($messages))
{
return;
}
$range = array(
reset($messages)['UID'],
end($messages)['UID'],
);
sort($range);
$this->unregisterMessages(array(
'=DIR_MD5' => md5($dir),
'>MSG_UID' => 0,
array(
'LOGIC' => 'OR',
'<MSG_UID' => $range[0],
'>MSG_UID' => $range[1],
),
));
if (!($meta['exists'] > 10000))
{
$this->resyncMessages($dir, $uidtoken, $messages);
return;
}
$range1 = $meta['exists'];
while ($range1 > 0)
{
$rangeSize = $range1 > 10000 ? 8000 : $range1;
$range0 = max($range1 - $rangeSize, 1);
$messages = $fetcher(sprintf('%u:%u', $range0, $range1));
if (empty($messages))
{
return;
}
$this->resyncMessages($dir, $uidtoken, $messages);
if ($this->isTimeQuotaExceeded())
{
return;
}
$range1 -= $rangeSize;
}
}
protected function blacklistMessages($dir, &$messages)
{
$trashDirs = (array) $this->mailbox['OPTIONS']['imap'][MessageFolder::TRASH];
$spamDirs = (array) $this->mailbox['OPTIONS']['imap'][MessageFolder::SPAM];
$targetDir = reset($spamDirs) ?: reset($trashDirs) ?: null;
if (empty($targetDir) || in_array($dir, array_merge($trashDirs, $spamDirs)))
{
return;
}
$blacklist = array(
'email' => array(),
'domain' => array(),
);
$res = Mail\BlacklistTable::getList(array(
'select' => array('ITEM_TYPE', 'ITEM_VALUE'),
'filter' => array(
'=SITE_ID' => $this->mailbox['LID'],
array(
'LOGIC' => 'OR',
'=MAILBOX_ID' => $this->mailbox['ID'],
array(
'=MAILBOX_ID' => 0,
'@USER_ID' => array(0, $this->mailbox['USER_ID']),
),
),
),
));
while ($item = $res->fetch())
{
if (Mail\Blacklist\ItemType::DOMAIN == $item['ITEM_TYPE'])
{
$blacklist['domain'][] = $item['ITEM_VALUE'];
}
else
{
$blacklist['email'][] = $item['ITEM_VALUE'];
}
}
if (empty($blacklist['email']) && empty($blacklist['domain']))
{
return;
}
$targetMessages = array();
foreach ($messages as $id => $item)
{
$parsedHeader = \CMailMessage::parseHeader($item['BODY[HEADER]'], $this->mailbox['LANG_CHARSET']);
$parsedFrom = array_unique(array_map('strtolower', array_filter(array_merge(
\CMailUtil::extractAllMailAddresses($parsedHeader->getHeader('FROM')),
\CMailUtil::extractAllMailAddresses($parsedHeader->getHeader('REPLY-TO'))
), 'trim')));
if (!empty($blacklist['email']))
{
if (array_intersect($parsedFrom, $blacklist['email']))
{
$targetMessages[$id] = $item['UID'];
continue;
}
}
if (!empty($blacklist['domain']))
{
foreach ($parsedFrom as $email)
{
$domain = substr($email, strrpos($email, '@'));
if (in_array($domain, $blacklist['domain']))
{
$targetMessages[$id] = $item['UID'];
continue 2;
}
}
}
}
if (!empty($targetMessages))
{
if ($this->client->moveMails($targetMessages, $dir, $targetDir)->isSuccess())
{
$messages = array_diff_key($messages, $targetMessages);
}
}
}
protected function prepareMessages($dir, $uidtoken, &$messages)
{
$excerpt = array();
$range = array(
reset($messages)['UID'],
end($messages)['UID'],
);
sort($range);
$result = $this->listMessages(array(
'select' => array('ID'),
'filter' => array(
'=DIR_MD5' => md5($dir),
'=DIR_UIDV' => $uidtoken,
'>=MSG_UID' => $range[0],
'<=MSG_UID' => $range[1],
),
), false);
while ($item = $result->fetch())
{
$excerpt[] = $item['ID'];
}
$uids = array();
$hashes = array();
foreach ($messages as $id => $item)
{
$messageUid = md5(sprintf('%s:%u:%u', $dir, $uidtoken, $item['UID']));
if (in_array($messageUid, $excerpt))
{
unset($messages[$id]);
continue;
}
$excerpt[] = $uids[$id] = $messageUid;
$hashes[$id] = md5(sprintf(
'%s:%s:%u',
trim($item['BODY[HEADER]']),
$item['INTERNALDATE'],
$item['RFC822.SIZE']
));
$messages[$id]['__internaldate'] = Main\Type\DateTime::createFromPhp(
\DateTime::createFromFormat(
'j-M-Y H:i:s O',
ltrim(trim($item['INTERNALDATE']), '0')
) ?: new \DateTime
);
$messages[$id]['__fields'] = array(
'ID' => $messageUid,
'DIR_MD5' => md5($dir),
'DIR_UIDV' => $uidtoken,
'MSG_UID' => $item['UID'],
'INTERNALDATE' => $messages[$id]['__internaldate'],
'IS_SEEN' => preg_grep('/^ \x5c Seen $/ix', $item['FLAGS']) ? 'Y' : 'N',
'HEADER_MD5' => $hashes[$id],
'MESSAGE_ID' => 0,
);
if ($bxuid = preg_grep('/^bxuid:?[a-f0-9]+$/i', $item['FLAGS']))
{
$messages[$id]['__replaces'] = preg_replace('/^bxuid:?/i', '', end($bxuid));
}
else if (preg_match('/X-Bitrix-Mail-Message-UID:\s*([a-f0-9]+)/i', $item['BODY[HEADER]'], $matches))
{
$messages[$id]['__replaces'] = $matches[1];
}
}
$hashesMap = array();
foreach ($hashes as $id => $hash)
{
if (!array_key_exists($hash, $hashesMap))
{
$hashesMap[$hash] = array();
}
$hashesMap[$hash][] = $id;
}
$result = $this->listMessages(array(
'select' => array('HEADER_MD5', 'MESSAGE_ID', 'DATE_INSERT'),
'filter' => array(
'@HEADER_MD5' => array_keys($hashesMap),
),
), false);
while ($item = $result->fetch())
{
foreach ((array) $hashesMap[$item['HEADER_MD5']] as $id)
{
$messages[$id]['__created'] = $item['DATE_INSERT'];
$messages[$id]['__fields']['MESSAGE_ID'] = $item['MESSAGE_ID'];
}
}
$result = $this->listMessages(array(
'select' => array('ID', 'MESSAGE_ID', 'DATE_INSERT'),
'filter' => array(
'@ID' => array_values($uids),
),
), false);
while ($item = $result->fetch())
{
$id = array_search($item['ID'], $uids);
$messages[$id]['__created'] = $item['DATE_INSERT'];
$messages[$id]['__fields']['MESSAGE_ID'] = $item['MESSAGE_ID'];
$messages[$id]['__replaces'] = $item['ID'];
}
}
protected function resyncMessages($dir, $uidtoken, &$messages)
{
$excerpt = array();
$range = array(
reset($messages)['UID'],
end($messages)['UID'],
);
sort($range);
$result = $this->listMessages(array(
'select' => array('ID', 'IS_SEEN'),
'filter' => array(
'=DIR_MD5' => md5($dir),
'=DIR_UIDV' => $uidtoken,
'>=MSG_UID' => $range[0],
'<=MSG_UID' => $range[1],
),
), false);
$oldMails = [];
while ($item = $result->fetch())
{
$excerpt[$item['ID']] = $item['IS_SEEN'];
$oldMails[$item['ID']] = $item;
}
$update = array(
'Y' => array(),
'N' => array(),
);
foreach ($messages as $id => $item)
{
$messageUid = md5(sprintf('%s:%u:%u', $dir, $uidtoken, $item['UID']));
if (array_key_exists($messageUid, $excerpt))
{
if (!in_array($excerpt[$messageUid], array('S', 'U')))
{
$messageSeen = preg_grep('/^ \x5c Seen $/ix', $item['FLAGS']) ? 'Y' : 'N';
if ($messageSeen != $excerpt[$messageUid])
{
$update[$messageSeen][] = $messageUid;
}
}
unset($excerpt[$messageUid]);
}
else
{
/*
addMessage2Log(
sprintf(
'IMAP: message lost (%u:%s:%u:%s)',
$this->mailbox['ID'], $dir, $uidtoken, $item['UID']
),
'mail', 0, false
);
*/
}
}
foreach ($update as $seen => $uids)
{
if (!empty($uids))
{
$mailsData = array_filter($oldMails, function ($oldEmailData) use ($uids)
{
return in_array($oldEmailData['ID'], $uids);
});
foreach ($mailsData as $index => $oldMail)
{
$mailsData[$index]['IS_SEEN'] = $seen;
}
$this->updateMessagesRegistry(
array(
'@ID' => $uids,
),
array(
'IS_SEEN' => $seen,
),
$mailsData
);
}
}
if (!empty($excerpt))
{
$this->unregisterMessages(array(
'@ID' => array_keys($excerpt),
));
}
}
protected function syncMessage($dir, $uidtoken, $message)
{
static $hashesMap = array();
$fields = $message['__fields'];
if ($fields['MESSAGE_ID'] > 0)
{
$hashesMap[$fields['HEADER_MD5']] = $fields['MESSAGE_ID'];
}
else
{
if (array_key_exists($fields['HEADER_MD5'], $hashesMap) && $hashesMap[$fields['HEADER_MD5']] > 0)
{
$fields['MESSAGE_ID'] = $hashesMap[$fields['HEADER_MD5']];
}
}
if (!$this->registerMessage($fields, $message['__replaces']))
{
return false;
}
if (!empty($this->mailbox['OPTIONS']['sync_from']))
{
if ($message['__internaldate']->getTimestamp() < $this->mailbox['OPTIONS']['sync_from'])
{
return false;
}
}
if (!empty($message['__created']) && !empty($this->mailbox['OPTIONS']['resync_from']))
{
if ($message['__created']->getTimestamp() < $this->mailbox['OPTIONS']['resync_from'])
{
return false;
}
}
if ($fields['MESSAGE_ID'] > 0)
{
return true;
}
$body = $this->client->fetch(
true,
$dir,
$message['UID'],
'(BODY.PEEK[])',
$error
);
$messageId = 0;
if (!empty($body['BODY[]']))
{
$messageId = $this->cacheMessage(
$body['BODY[]'],
array(
'outcome' => in_array($dir, $this->mailbox['OPTIONS']['imap'][MessageFolder::OUTCOME]),
'seen' => $fields['IS_SEEN'] == 'Y',
'hash' => $fields['HEADER_MD5'],
)
);
}
if ($messageId > 0)
{
$hashesMap[$fields['HEADER_MD5']] = $messageId;
$this->linkMessage($fields['ID'], $messageId);
}
else
{
$this->unregisterMessages(array(
'=ID' => $fields['ID'],
));
}
return $messageId > 0;
}
protected function getSyncRange($dir, &$uidtoken)
{
$meta = $this->client->select($dir, $error);
if (false === $meta)
{
$this->warnings->add($this->client->getErrors()->toArray());
return null;
}
if (!($meta['exists'] > 0))
{
return null;
}
$uidtoken = $meta['uidvalidity'];
$rangeGetter = function ($min, $max) use ($dir, $uidtoken, &$rangeGetter)
{
$size = $max - $min + 1;
$set = array();
$d = $size < 1000 ? 100 : pow(10, round(ceil(log10($size) - 0.7) / 2) * 2 - 2);
for ($i = $min; $i <= $max; $i = $i + $d)
{
$set[] = $i;
}
if (count($set) > 1 && end($set) + 100 >= $max)
{
array_pop($set);
}
$set[] = $max;
$set = $this->client->fetch(false, $dir, join(',', $set), '(UID)', $error);
if (empty($set))
{
return false;
}
ksort($set);
static $uidMin, $uidMax;
if (!isset($uidMin, $uidMax))
{
$minmax = $this->getUidRange($dir, $uidtoken);
if ($minmax)
{
$uidMin = $minmax['MIN'];
$uidMax = $minmax['MAX'];
}
else
{
$uidMin = $uidMax = (end($set)['UID'] + 1);
}
}
if (count($set) == 1)
{
$uid = reset($set)['UID'];
if ($uid > $uidMax || $uid < $uidMin)
{
return array($uid, $uid);
}
}
else if (end($set)['UID'] > $uidMax)
{
$max = current($set)['id'];
do
{
$exmax = $max;
$max = current($set)['id'];
$min = prev($set)['id'];
}
while (current($set)['UID'] > $uidMax && prev($set) && next($set));
if ($max - $min > 200)
{
return $rangeGetter($min, $max);
}
else
{
if ($set[$max]['UID'] - $uidMax < 100)
{
$max = $exmax;
}
return array(
max($set[$min]['UID'], $uidMax + 1),
$set[$max]['UID'],
);
}
}
else if (reset($set)['UID'] < $uidMin)
{
$min = current($set)['id'];
do
{
$exmin = $min;
$min = current($set)['id'];
$max = next($set)['id'];
}
while (current($set)['UID'] < $uidMin && next($set) && prev($set));
if ($max - $min > 200)
{
return $rangeGetter($min, $max);
}
else
{
if ($uidMin - $set[$min]['UID'] < 100)
{
$min = $exmin;
}
return array(
min($set[$max]['UID'], $uidMin - 1),
$set[$min]['UID'],
);
}
}
return null;
};
return $rangeGetter(1, $meta['exists']);
}
protected function getUidRange($dir, $uidtoken)
{
$filter = array(
'=DIR_MD5' => md5($dir),
'=DIR_UIDV' => $uidtoken,
'>MSG_UID' => 0,
);
$min = $this->listMessages(
array(
'select' => array(
'MIN' => 'MSG_UID',
),
'filter' => $filter,
'order' => array(
'MSG_UID' => 'ASC',
),
'limit' => 1,
),
false
)->fetch();
$max = $this->listMessages(
array(
'select' => array(
'MAX' => 'MSG_UID',
),
'filter' => $filter,
'order' => array(
'MSG_UID' => 'DESC',
),
'limit' => 1,
),
false
)->fetch();
if ($min && $max)
{
return array(
'MIN' => $min['MIN'],
'MAX' => $max['MAX'],
);
}
return null;
}
}