This commit is contained in:
the-djmaze 2022-12-06 10:40:02 +01:00
parent ae40214f46
commit dc335f0016
9 changed files with 101 additions and 186 deletions

View file

@ -16,7 +16,6 @@ use MailSo\Imap\Folder;
use MailSo\Imap\FolderInformation; use MailSo\Imap\FolderInformation;
use MailSo\Imap\SequenceSet; use MailSo\Imap\SequenceSet;
use MailSo\Imap\Enumerations\FolderStatus; use MailSo\Imap\Enumerations\FolderStatus;
use MailSo\Imap\Enumerations\FolderResponseStatus;
/** /**
* @category MailSo * @category MailSo
@ -104,6 +103,38 @@ trait Folders
return $this; return $this;
} }
private function FolderStatusItems() : array
{
$aStatusItems = array(
FolderStatus::MESSAGES,
FolderStatus::UNSEEN,
FolderStatus::UIDNEXT,
FolderStatus::UIDVALIDITY
);
// RFC 4551
if ($this->IsSupported('CONDSTORE')) {
$aStatusItems[] = FolderStatus::HIGHESTMODSEQ;
}
// RFC 7889
if ($this->IsSupported('APPENDLIMIT')) {
$aStatusItems[] = FolderStatus::APPENDLIMIT;
}
// RFC 8474
if ($this->IsSupported('OBJECTID')) {
$aStatusItems[] = FolderStatus::MAILBOXID;
/*
} else if ($this->IsSupported('X-DOVECOT')) {
$aStatusItems[] = 'X-GUID';
*/
}
/* // STATUS SIZE can take a significant amount of time, therefore not active
if ($this->IsSupported('IMAP4rev2')) {
$aStatusItems[] = FolderStatus::SIZE;
}
*/
return $aStatusItems;
}
/** /**
* @throws \InvalidArgumentException * @throws \InvalidArgumentException
* @throws \MailSo\RuntimeException * @throws \MailSo\RuntimeException
@ -114,30 +145,6 @@ trait Folders
*/ */
public function FolderStatus(string $sFolderName, bool $bSelect = false) : FolderInformation public function FolderStatus(string $sFolderName, bool $bSelect = false) : FolderInformation
{ {
$aStatusItems = array(
FolderResponseStatus::MESSAGES,
FolderResponseStatus::UNSEEN,
FolderResponseStatus::UIDNEXT,
FolderResponseStatus::UIDVALIDITY
);
if ($this->IsSupported('CONDSTORE')) {
$aStatusItems[] = FolderResponseStatus::HIGHESTMODSEQ;
}
if ($this->IsSupported('APPENDLIMIT')) {
$aStatusItems[] = FolderResponseStatus::APPENDLIMIT;
}
if ($this->IsSupported('OBJECTID')) {
$aStatusItems[] = FolderResponseStatus::MAILBOXID;
/*
} else if ($this->IsSupported('X-DOVECOT')) {
$aStatusItems[] = 'X-GUID';
*/
}
/* // STATUS SIZE can take a significant amount of time, therefore not active
if ($this->IsSupported('IMAP4rev2')) {
$aStatusItems[] = FolderResponseStatus::SIZE;
}
*/
$oFolderInfo = $this->oCurrentFolderInfo; $oFolderInfo = $this->oCurrentFolderInfo;
$bReselect = false; $bReselect = false;
$bWritable = false; $bWritable = false;
@ -148,12 +155,10 @@ trait Folders
* So we must unselect the folder to be able to get the APPENDLIMIT and UNSEEN. * So we must unselect the folder to be able to get the APPENDLIMIT and UNSEEN.
*/ */
/* /*
if ($this->IsSupported('ESEARCH')) { if ($this->IsSupported('ESEARCH') && !isset($oFolderInfo->UNSEEN)) {
$aResult = $oFolderInfo->getStatusItems(); $oFolderInfo->UNSEEN = $this->MessageSimpleESearch('UNSEEN', ['COUNT'])['COUNT'];
// SELECT or EXAMINE command then UNSEEN is the message sequence number of the first unseen message
$aResult['UNSEEN'] = $this->MessageSimpleESearch('UNSEEN', ['COUNT'])['COUNT'];
return $aResult;
} }
return $oFolderInfo;
*/ */
$bWritable = $oFolderInfo->IsWritable; $bWritable = $oFolderInfo->IsWritable;
$bReselect = true; $bReselect = true;
@ -161,7 +166,7 @@ trait Folders
} }
$oInfo = new FolderInformation($sFolderName, false); $oInfo = new FolderInformation($sFolderName, false);
$this->SendRequest('STATUS', array($this->EscapeFolderName($sFolderName), $aStatusItems)); $this->SendRequest('STATUS', array($this->EscapeFolderName($sFolderName), $this->FolderStatusItems()));
foreach ($this->yieldUntaggedResponses() as $oResponse) { foreach ($this->yieldUntaggedResponses() as $oResponse) {
$oInfo->setStatusFromResponse($oResponse); $oInfo->setStatusFromResponse($oResponse);
} }
@ -170,8 +175,8 @@ trait Folders
// Don't use FolderExamine, else PERMANENTFLAGS is empty in Dovecot // Don't use FolderExamine, else PERMANENTFLAGS is empty in Dovecot
$oFolderInformation = $this->selectOrExamineFolder($sFolderName, $bSelect || $bWritable, false); $oFolderInformation = $this->selectOrExamineFolder($sFolderName, $bSelect || $bWritable, false);
$oFolderInformation->MESSAGES = \max(0, $oFolderInformation->MESSAGES, $oInfo->MESSAGES); $oFolderInformation->MESSAGES = \max(0, $oFolderInformation->MESSAGES, $oInfo->MESSAGES);
// $oFolderInformation has the message sequence number of the first unseen message in the mailbox // SELECT or EXAMINE command then UNSEEN is the message sequence number of the first unseen message.
// so we set it to the amount of unseen messages // And deprecated in IMAP4rev2, so we set it to the amount of unseen messages
$oFolderInformation->UNSEEN = \max(0, $oInfo->UNSEEN); $oFolderInformation->UNSEEN = \max(0, $oInfo->UNSEEN);
$oFolderInformation->UIDNEXT = \max(0, $oFolderInformation->UIDNEXT, $oInfo->UIDNEXT); $oFolderInformation->UIDNEXT = \max(0, $oFolderInformation->UIDNEXT, $oInfo->UIDNEXT);
$oFolderInformation->UIDVALIDITY = \max(0, $oFolderInformation->UIDVALIDITY, $oInfo->UIDVALIDITY); $oFolderInformation->UIDVALIDITY = \max(0, $oFolderInformation->UIDVALIDITY, $oInfo->UIDVALIDITY);
@ -399,9 +404,14 @@ trait Folders
} }
} }
// SELECT or EXAMINE command then UNSEEN is the message sequence number of the first unseen message.
// IMAP4rev2 deprecated // IMAP4rev2 deprecated
$oResult->UNSEEN = null; $oResult->UNSEEN = null;
/*
if ($this->IsSupported('ESEARCH')) {
$oResult->UNSEEN = $this->MessageSimpleESearch('UNSEEN', ['COUNT'])['COUNT'];
}
*/
$this->oCurrentFolderInfo = $oResult; $this->oCurrentFolderInfo = $oResult;
return $oResult; return $oResult;
@ -444,30 +454,8 @@ trait Folders
// RFC 5819 // RFC 5819
if ($bUseListStatus && !$bIsSubscribeList && $this->IsSupported('LIST-STATUS')) if ($bUseListStatus && !$bIsSubscribeList && $this->IsSupported('LIST-STATUS'))
{ {
$aL = array(
FolderStatus::MESSAGES,
FolderStatus::UNSEEN,
FolderStatus::UIDNEXT
);
// RFC 4551
if ($this->IsSupported('CONDSTORE')) {
$aL[] = FolderStatus::HIGHESTMODSEQ;
}
// RFC 7889
if ($this->IsSupported('APPENDLIMIT')) {
$aL[] = FolderStatus::APPENDLIMIT;
}
// RFC 8474
if ($this->IsSupported('OBJECTID')) {
$aL[] = FolderStatus::MAILBOXID;
/*
} else if ($this->IsSupported('X-DOVECOT')) {
$aL[] = 'X-GUID';
*/
}
$aReturnParams[] = 'STATUS'; $aReturnParams[] = 'STATUS';
$aReturnParams[] = $aL; $aReturnParams[] = $this->FolderStatusItems();
} }
/* /*
// RFC 5738 // RFC 5738

View file

@ -1,35 +0,0 @@
<?php
/*
* This file is part of MailSo.
*
* (c) 2014 Usenko Timur
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace MailSo\Imap\Enumerations;
/**
* @category MailSo
* @package Imap
* @subpackage Enumerations
*/
abstract class FolderResponseStatus
{
// RFC 3501
const MESSAGES = 'MESSAGES';
// const RECENT = 'RECENT'; // IMAP4rev2 deprecated
const UIDNEXT = 'UIDNEXT';
const UIDVALIDITY = 'UIDVALIDITY';
const UNSEEN = 'UNSEEN';
// RFC 4551
const HIGHESTMODSEQ = 'HIGHESTMODSEQ';
// RFC 7889
const APPENDLIMIT = 'APPENDLIMIT';
// RFC 8474
const MAILBOXID = 'MAILBOXID';
// RFC 9051 IMAP4rev2
const SIZE = 'SIZE';
}

View file

@ -30,4 +30,6 @@ abstract class FolderStatus
const APPENDLIMIT = 'APPENDLIMIT'; const APPENDLIMIT = 'APPENDLIMIT';
// RFC 8474 // RFC 8474
const MAILBOXID = 'MAILBOXID'; const MAILBOXID = 'MAILBOXID';
// RFC 9051 IMAP4rev2
const SIZE = 'SIZE';
} }

View file

@ -20,11 +20,6 @@ class Folder
// RFC5258 Response data STATUS items when using LIST-EXTENDED // RFC5258 Response data STATUS items when using LIST-EXTENDED
use Traits\Status; use Traits\Status;
/**
* @var string
*/
private $sFullName;
/** /**
* @var string * @var string
*/ */
@ -48,7 +43,7 @@ class Folder
if (!\strlen($sFullName)) { if (!\strlen($sFullName)) {
throw new \InvalidArgumentException; throw new \InvalidArgumentException;
} }
$this->sFullName = $sFullName; $this->FolderName = $sFullName;
$this->setDelimiter($sDelimiter); $this->setDelimiter($sDelimiter);
$this->setFlags($aFlags); $this->setFlags($aFlags);
/* /*
@ -72,7 +67,7 @@ class Folder
public function Name() : string public function Name() : string
{ {
$sNameRaw = $this->sFullName; $sNameRaw = $this->FolderName;
if ($this->sDelimiter) { if ($this->sDelimiter) {
$aNames = \explode($this->sDelimiter, $sNameRaw); $aNames = \explode($this->sDelimiter, $sNameRaw);
return \end($aNames); return \end($aNames);
@ -82,7 +77,7 @@ class Folder
public function FullName() : string public function FullName() : string
{ {
return $this->sFullName; return $this->FolderName;
} }
public function Delimiter() : ?string public function Delimiter() : ?string
@ -102,7 +97,7 @@ class Folder
public function IsInbox() : bool public function IsInbox() : bool
{ {
return 'INBOX' === \strtoupper($this->sFullName) || \in_array('\\inbox', $this->aFlagsLowerCase); return 'INBOX' === \strtoupper($this->FolderName) || \in_array('\\inbox', $this->aFlagsLowerCase);
} }
public function SetMetadata(string $sName, string $sData) : void public function SetMetadata(string $sName, string $sData) : void

View file

@ -19,27 +19,17 @@ class FolderInformation implements \JsonSerializable
{ {
use Traits\Status; use Traits\Status;
/** public bool $IsWritable;
* @var string
*/
public $FolderName;
/** /**
* @var bool
*/
public $IsWritable;
/**
* @var array
* Message flags * Message flags
*/ */
public $Flags = array(); public array $Flags = array();
/** /**
* @var array
* NOTE: Empty when FolderExamine is used * NOTE: Empty when FolderExamine is used
*/ */
public $PermanentFlags = array(); public array $PermanentFlags = array();
function __construct(string $sFolderName, bool $bIsWritable) function __construct(string $sFolderName, bool $bIsWritable)
{ {

View file

@ -22,6 +22,8 @@ namespace MailSo\Imap\Traits;
*/ */
trait Status trait Status
{ {
public string $FolderName;
public public
/** /**
* The number of messages in the mailbox. * The number of messages in the mailbox.
@ -92,11 +94,20 @@ trait Status
*/ */
$SIZE; $SIZE;
public function getStatusItems() : array public function getHash(string $sClientHash) : ?string
{ {
return \array_filter(\get_object_vars($this), function($v, $k){ if (!isset($this->MESSAGES, $this->UIDNEXT)) {
return \property_exists(__TRAIT__, $k); return null;
}, ARRAY_FILTER_USE_BOTH); }
return \md5('FolderHash/'. \implode('-', [
$this->FolderName,
$this->MESSAGES,
$this->UIDNEXT,
$this->UIDVALIDITY,
$this->HIGHESTMODSEQ,
$sClientHash,
\MailSo\Config::$MessageListPermanentFilter
]));
} }
public function setStatus(string $name, $value) : bool public function setStatus(string $name, $value) : bool
@ -157,6 +168,7 @@ trait Status
} }
// SELECT or EXAMINE command // SELECT or EXAMINE command
else if (\is_numeric($oResponse->ResponseList[1]) && \is_string($oResponse->ResponseList[2])) { else if (\is_numeric($oResponse->ResponseList[1]) && \is_string($oResponse->ResponseList[2])) {
// UNSEEN deprecated in IMAP4rev2
if ('UNSEEN' !== $oResponse->ResponseList[2]) { if ('UNSEEN' !== $oResponse->ResponseList[2]) {
$bResult |= $this->setStatus($oResponse->ResponseList[2], $oResponse->ResponseList[1]); $bResult |= $this->setStatus($oResponse->ResponseList[2], $oResponse->ResponseList[1]);
} }

View file

@ -95,9 +95,9 @@ class Folder implements \JsonSerializable
return $this->bExists && $this->oImapFolder->IsSelectable(); return $this->bExists && $this->oImapFolder->IsSelectable();
} }
public function Status() : array public function Hash(string $sClientHash) : ?string
{ {
return $this->oImapFolder->getStatusItems(); return $this->oImapFolder->getHash($sClientHash);
} }
public function IsInbox() : bool public function IsInbox() : bool
@ -206,15 +206,12 @@ class Folder implements \JsonSerializable
{ {
/* /*
$aExtended = null; $aExtended = null;
$aStatus = $this->oImapFolder->getStatusItems(); if (isset($this->oImapFolder->MESSAGES, $this->oImapFolder->UNSEEN, $this->oImapFolder->UIDNEXT)) {
if ($aStatus && isset($aStatus['MESSAGES'], $aStatus['UNSEEN'], $aStatus['UIDNEXT'])) {
$aExtended = array( $aExtended = array(
'totalEmails' => (int) $aStatus['MESSAGES'], 'totalEmails' => (int) $this->oImapFolder->MESSAGES,
'unreadEmails' => (int) $aStatus['UNSEEN'], 'unreadEmails' => (int) $this->oImapFolder->UNSEEN,
'UidNext' => (int) $aStatus['UIDNEXT'], 'UidNext' => (int) $this->oImapFolder->UIDNEXT,
// 'Hash' => $this->MailClient()->GenerateFolderHash( // 'Hash' => $this->Hash($this->MailClient()->GenerateImapClientHash())
// $this->FullName(), $aStatus['MESSAGES'], $aStatus['UIDNEXT'],
// \max(0, $aStatus['HIGHESTMODSEQ'] ?? $aStatus['UIDVALIDITY'])
); );
} }
*/ */

View file

@ -11,11 +11,11 @@
namespace MailSo\Mail; namespace MailSo\Mail;
use MailSo\Imap\Enumerations\FolderResponseStatus; use MailSo\Imap\FolderInformation;
use MailSo\Imap\Enumerations\FetchType;
use MailSo\Imap\Enumerations\MessageFlag; use MailSo\Imap\Enumerations\MessageFlag;
use MailSo\Imap\Enumerations\StoreAction; use MailSo\Imap\Enumerations\StoreAction;
use MailSo\Imap\SequenceSet; use MailSo\Imap\SequenceSet;
use MailSo\Imap\Enumerations\FetchType;
use MailSo\Mime\Enumerations\Header as MimeHeader; use MailSo\Mime\Enumerations\Header as MimeHeader;
use MailSo\Mime\Enumerations\Parameter as MimeParameter; use MailSo\Mime\Enumerations\Parameter as MimeParameter;
@ -407,24 +407,6 @@ class MailClient
return $this; return $this;
} }
protected function initFolderValues(string $sFolderName) : array
{
$oFolderStatus = $this->oImapClient->FolderStatus($sFolderName);
return [
\max(0, $oFolderStatus->MESSAGES ?: 0),
\max(0, $oFolderStatus->UNSEEN ?: 0),
\max(0, $oFolderStatus->UIDNEXT ?: 0),
\max(0, $oFolderStatus->HIGHESTMODSEQ ?: $oFolderStatus->UIDVALIDITY),
$oFolderStatus->APPENDLIMIT ?: $this->oImapClient->AppendLimit(),
$oFolderStatus->MAILBOXID ?: ''
];
}
public function GenerateImapClientHash() : string public function GenerateImapClientHash() : string
{ {
return \md5('ImapClientHash/'. return \md5('ImapClientHash/'.
@ -434,14 +416,6 @@ class MailClient
); );
} }
public function GenerateFolderHash(string $sFolder, int $iCount, int $iUidNext, int $iHighestModSeq) : string
{
return \md5('FolderHash/'.$sFolder.'-'.$iCount.'-'.$iUidNext.'-'.
$iHighestModSeq.'-'.$this->GenerateImapClientHash().'-'.
\MailSo\Config::$MessageListPermanentFilter
);
}
/** /**
* Returns list of new messages since $iPrevUidNext * Returns list of new messages since $iPrevUidNext
* Currently only for INBOX * Currently only for INBOX
@ -501,19 +475,14 @@ class MailClient
*/ */
public function FolderInformation(string $sFolderName, int $iPrevUidNext = 0, SequenceSet $oRange = null) : array public function FolderInformation(string $sFolderName, int $iPrevUidNext = 0, SequenceSet $oRange = null) : array
{ {
list($iCount, $iUnseenCount, $iUidNext, $iHighestModSeq, $iAppendLimit, $sMailboxId) = $this->initFolderValues($sFolderName);
/*
$oInfo = $this->oImapClient->FolderStatusAndSelect($sFolderName);
*/
$aFlags = array(); $aFlags = array();
if ($oRange && \count($oRange)) { if ($oRange && \count($oRange)) {
$oInfo = $this->oImapClient->FolderExamine($sFolderName); // $oInfo = $this->oImapClient->FolderExamine($sFolderName);
$oInfo = $this->oImapClient->FolderStatusAndSelect($sFolderName);
$aFetchResponse = $this->oImapClient->Fetch(array( $aFetchResponse = $this->oImapClient->Fetch(array(
FetchType::UID, FetchType::UID,
FetchType::FLAGS FetchType::FLAGS
), (string) $oRange, $oRange->UID); ), (string) $oRange, $oRange->UID);
foreach ($aFetchResponse as $oFetchResponse) { foreach ($aFetchResponse as $oFetchResponse) {
$iUid = (int) $oFetchResponse->GetFetchValue(FetchType::UID); $iUid = (int) $oFetchResponse->GetFetchValue(FetchType::UID);
$aLowerFlags = \array_map('mb_strtolower', \array_map('\\MailSo\\Base\\Utils::Utf7ModifiedToUtf8', $oFetchResponse->GetFetchValue(FetchType::FLAGS))); $aLowerFlags = \array_map('mb_strtolower', \array_map('\\MailSo\\Base\\Utils::Utf7ModifiedToUtf8', $oFetchResponse->GetFetchValue(FetchType::FLAGS)));
@ -522,22 +491,28 @@ class MailClient
'Flags' => $aLowerFlags 'Flags' => $aLowerFlags
); );
} }
} else {
$oInfo = $this->oImapClient->FolderStatus($sFolderName);
} }
return array( return array(
'Folder' => $sFolderName, 'Folder' => $sFolderName,
'totalEmails' => $iCount, 'totalEmails' => $oInfo->MESSAGES,
'unreadEmails' => $iUnseenCount, 'unreadEmails' => $oInfo->UNSEEN,
'UidNext' => $iUidNext, 'UidNext' => $oInfo->UIDNEXT,
'HighestModSeq' => $iHighestModSeq, 'UidValidity' => $oInfo->UIDVALIDITY,
'AppendLimit' => $iAppendLimit, 'HighestModSeq' => $oInfo->HIGHESTMODSEQ,
'MailboxId' => $sMailboxId, 'AppendLimit' => $oInfo->APPENDLIMIT ?: $this->oImapClient->AppendLimit(),
'MailboxId' => $oInfo->MAILBOXID ?: '',
// 'Flags' => $oInfo->Flags, // 'Flags' => $oInfo->Flags,
// 'PermanentFlags' => $oInfo->PermanentFlags, // 'PermanentFlags' => $oInfo->PermanentFlags,
'Hash' => $this->GenerateFolderHash($sFolderName, $iCount, $iUidNext, $iHighestModSeq), 'Hash' => $oInfo->getHash($this->GenerateImapClientHash()),
// 'Hash' => $this->GenerateFolderHash($sFolderName, $oInfo->MESSAGES, $oInfo->UIDNEXT, $oInfo->HIGHESTMODSEQ);
'MessagesFlags' => $aFlags, 'MessagesFlags' => $aFlags,
'NewMessages' => $this->getFolderNextMessageInformation($sFolderName, $iPrevUidNext, $iUidNext) 'NewMessages' => $this->getFolderNextMessageInformation(
$sFolderName,
$iPrevUidNext,
\intval($oInfo->UIDNEXT)
)
); );
} }
@ -549,9 +524,7 @@ class MailClient
*/ */
public function FolderHash(string $sFolderName) : string public function FolderHash(string $sFolderName) : string
{ {
list($iCount, $iUnseenCount, $iUidNext, $iHighestModSeq) = $this->initFolderValues($sFolderName); return $this->oImapClient->FolderStatus($sFolderName)->getHash($this->GenerateImapClientHash());
return $this->GenerateFolderHash($sFolderName, $iCount, $iUidNext, $iHighestModSeq);
} }
/** /**
@ -832,9 +805,7 @@ class MailClient
$oParams->oCacher = null; $oParams->oCacher = null;
} }
$oMessageCollection->FolderHash = $this->GenerateFolderHash( $oMessageCollection->FolderHash = $oInfo->getHash($this->GenerateImapClientHash());
$oParams->sFolderName, $oInfo->MESSAGES, $oInfo->UIDNEXT, \max(0, $oInfo->HIGHESTMODSEQ ?: $oInfo->UIDVALIDITY)
);
if (!$oParams->iThreadUid) { if (!$oParams->iThreadUid) {
$oMessageCollection->NewMessages = $this->getFolderNextMessageInformation( $oMessageCollection->NewMessages = $this->getFolderNextMessageInformation(

View file

@ -250,14 +250,9 @@ trait Response
{ {
$aResult = $mResponse->jsonSerialize(); $aResult = $mResponse->jsonSerialize();
$aStatus = $mResponse->Status(); $sHash = $mResponse->Hash($this->MailClient()->GenerateImapClientHash());
if ($aStatus && isset($aStatus['MESSAGES'], $aStatus['UIDNEXT'])) { if ($sHash) {
$aResult['Hash'] = $this->MailClient()->GenerateFolderHash( $aResult['Hash'] = $sHash;
$mResponse->FullName(),
$aStatus['MESSAGES'],
$aStatus['UIDNEXT'],
\max(0, $aStatus['HIGHESTMODSEQ'] ?? $aStatus['UIDVALIDITY'])
);
} }
if (null === $this->aCheckableFolder) { if (null === $this->aCheckableFolder) {