/**
* Return message changes from the specified mailbox.
*
* @param Horde_ActiveSync_Folder_Imap $folder The folder object.
* @param array $options Additional options:
* - sincedate: (integer) Timestamp of earliest message to retrieve.
* DEFAULT: 0 (Don't filter).
* - protocolversion: (float) EAS protocol version to support.
* DEFAULT: none REQUIRED
* - softdelete: (boolean) If true, calculate SOFTDELETE data.
* @since 2.8.0
*
* @return Horde_ActiveSync_Folder_Imap The folder object, containing any
* change instructions for the device.
*
* @throws Horde_ActiveSync_Exception_FolderGone,
* Horde_ActiveSync_Exception, Horde_ActiveSync_Exception_StaleState
*/
public function getMessageChanges(Horde_ActiveSync_Folder_Imap $folder, array $options = array())
{
$imap = $this->_getImapOb();
$mbox = new Horde_Imap_Client_Mailbox($folder->serverid());
// Note: non-CONDSTORE servers will return a highestmodseq of 0
$status_flags = Horde_Imap_Client::STATUS_HIGHESTMODSEQ | Horde_Imap_Client::STATUS_UIDVALIDITY | Horde_Imap_Client::STATUS_UIDNEXT_FORCE | Horde_Imap_Client::STATUS_MESSAGES | Horde_Imap_Client::STATUS_FORCE_REFRESH;
try {
$status = $imap->status($mbox, $status_flags);
} catch (Horde_Imap_Client_Exception $e) {
// If we can't status the mailbox, assume it's gone.
throw new Horde_ActiveSync_Exception_FolderGone($e);
}
$this->_logger->info(sprintf('[%s] IMAP status: %s', $this->_procid, serialize($status)));
$flags = array();
$categories = array();
$modseq = $status[Horde_ActiveSync_Folder_Imap::HIGHESTMODSEQ];
if ($modseq && $folder->modseq() > 0 && $folder->modseq() < $modseq) {
$this->_logger->info(sprintf('[%s] CONDSTORE and CHANGES', $this->_procid));
$folder->checkValidity($status);
$query = new Horde_Imap_Client_Fetch_Query();
$query->modseq();
$query->flags();
$query->headerText(array('peek' => true));
try {
$fetch_ret = $imap->fetch($mbox, $query, array('changedsince' => $folder->modseq()));
} catch (Horde_Imap_Client_Exception $e) {
$this->_logger->err($e->getMessage());
throw new Horde_ActiveSync_Exception($e);
}
// Prepare the changes and flags array, ensuring no changes after
// $modseq sneak in yet (they will be caught on the next PING or
// SYNC).
$changes = array();
// Get custom flags to use as categories.
$msgFlags = $this->_getMsgFlags();
foreach ($fetch_ret as $uid => $data) {
if ($options['sincedate']) {
$since = new Horde_Date($options['sincedate']);
$headers = Horde_Mime_Headers::parseHeaders($data->getHeaderText());
try {
$date = new Horde_Date($headers->getValue('Date'));
if ($date->compareDate($since) <= -1) {
// Ignore, it's out of the FILTERTYPE range.
$this->_logger->info(sprintf('[%s] Ignoring UID %s since it is outside of the FILTERTYPE (%s)', $this->_procid, $uid, $headers->getValue('Date')));
continue;
}
} catch (Horde_Date_Exception $e) {
}
}
if ($data->getModSeq() <= $modseq) {
$changes[] = $uid;
$flags[$uid] = array('read' => array_search(Horde_Imap_Client::FLAG_SEEN, $data->getFlags()) !== false ? 1 : 0);
if ($options['protocolversion'] > Horde_ActiveSync::VERSION_TWOFIVE) {
$flags[$uid]['flagged'] = array_search(Horde_Imap_Client::FLAG_FLAGGED, $data->getFlags()) !== false ? 1 : 0;
}
if ($options['protocolversion'] > Horde_ActiveSync::VERSION_TWELVEONE) {
$categories[$uid] = array();
foreach ($data->getFlags() as $flag) {
if (($key = array_search(strtolower($flag), array_map('strtolower', $msgFlags))) !== false) {
$categories[$uid][] = $msgFlags[$key];
}
}
}
}
}
$folder->setChanges($changes, $flags, $categories);
try {
$deleted = $imap->vanished($mbox, $folder->modseq(), array('ids' => new Horde_Imap_Client_Ids($folder->messages())));
} catch (Horde_Imap_Client_Excetion $e) {
$this->_logger->err($e->getMessage());
throw new Horde_ActiveSync_Exception($e);
}
$folder->setRemoved($deleted->ids);
if (!empty($options['softdelete']) && !empty($options['sincedate'])) {
$this->_logger->info(sprintf('[%s] Polling for SOFTDELETE in %s before %d', $this->_procid, $folder->serverid(), $options['sincedate']));
$query = new Horde_Imap_Client_Search_Query();
$query->dateSearch(new Horde_Date($options['sincedate']), Horde_Imap_Client_Search_Query::DATE_BEFORE);
$query->ids(new Horde_Imap_Client_Ids($folder->messages()));
try {
$search_ret = $imap->search($mbox, $query, array('results' => array(Horde_Imap_Client::SEARCH_RESULTS_MATCH)));
} catch (Horde_Imap_Client_Exception $e) {
$this->_logger->err($e->getMessage());
throw new Horde_ActiveSync_Exception($e);
}
if ($search_ret['count']) {
$this->_logger->info(sprintf('[%s] Found %d messages to SOFTDELETE.', $this->_procid, count($search_ret['match']->ids)));
$folder->setSoftDeleted($search_ret['match']->ids);
} else {
$this->_logger->info(sprintf('[%s] Found NO messages to SOFTDELETE.', $this->_procid));
}
$folder->setSoftDeleteTimes($options['sincedate'], time());
}
} elseif ($folder->uidnext() == 0) {
$this->_logger->info(sprintf('[%s] INITIAL SYNC', $this->_procid));
$query = new Horde_Imap_Client_Search_Query();
if (!empty($options['sincedate'])) {
$query->dateSearch(new Horde_Date($options['sincedate']), Horde_Imap_Client_Search_Query::DATE_SINCE);
}
$search_ret = $imap->search($mbox, $query, array('results' => array(Horde_Imap_Client::SEARCH_RESULTS_MATCH)));
if ($modseq && $folder->modseq() > 0 && $search_ret['count']) {
$folder->setChanges($search_ret['match']->ids, array());
} elseif (count($search_ret['match']->ids)) {
$query = new Horde_Imap_Client_Fetch_Query();
$query->flags();
$fetch_ret = $imap->fetch($mbox, $query, array('ids' => $search_ret['match']));
foreach ($fetch_ret as $uid => $data) {
$flags[$uid] = array('read' => array_search(Horde_Imap_Client::FLAG_SEEN, $data->getFlags()) !== false ? 1 : 0);
if ($options['protocolversion'] > Horde_ActiveSync::VERSION_TWOFIVE) {
$flags[$uid]['flagged'] = array_search(Horde_Imap_Client::FLAG_FLAGGED, $data->getFlags()) !== false ? 1 : 0;
}
}
$folder->setChanges($search_ret['match']->ids, $flags);
}
} elseif ($modseq == 0) {
$this->_logger->info(sprintf('[%s] NO CONDSTORE or per mailbox MODSEQ. minuid: %s, total_messages: %s', $this->_procid, $folder->minuid(), $status['messages']));
$folder->checkValidity($status);
$query = new Horde_Imap_Client_Search_Query();
if (!empty($options['sincedate'])) {
$query->dateSearch(new Horde_Date($options['sincedate']), Horde_Imap_Client_Search_Query::DATE_SINCE);
}
try {
$search_ret = $imap->search($mbox, $query, array('results' => array(Horde_Imap_Client::SEARCH_RESULTS_MATCH)));
} catch (Horde_Imap_Client_Exception $e) {
$this->_logger->err($e->getMessage());
throw new Horde_ActiveSync_Exception($e);
}
if (count($search_ret['match']->ids)) {
// Update flags.
$query = new Horde_Imap_Client_Fetch_Query();
$query->flags();
try {
$fetch_ret = $imap->fetch($mbox, $query, array('ids' => $search_ret['match']));
} catch (Horde_Imap_Client_Exception $e) {
$this->_logger->err($e->getMessage());
throw new Horde_ActiveSync_Exception($e);
}
foreach ($fetch_ret as $uid => $data) {
$flags[$uid] = array('read' => array_search(Horde_Imap_Client::FLAG_SEEN, $data->getFlags()) !== false ? 1 : 0);
if ($options['protocolversion'] > Horde_ActiveSync::VERSION_TWOFIVE) {
$flags[$uid]['flagged'] = array_search(Horde_Imap_Client::FLAG_FLAGGED, $data->getFlags()) !== false ? 1 : 0;
}
}
$folder->setChanges($search_ret['match']->ids, $flags);
}
$folder->setRemoved($imap->vanished($mbox, null, array('ids' => new Horde_Imap_Client_Ids($folder->messages())))->ids);
} elseif ($modseq > 0 && $folder->modseq() == 0) {
throw new Horde_ActiveSync_Exception_StaleState('Transition to MODSEQ enabled server');
}
$folder->setStatus($status);
return $folder;
}