/**
* Get a list of server changes that occured during the specified time
* period.
*
* @param Horde_ActiveSync_Folder_Base $folder
* The ActiveSync folder object to request changes for.
* @param integer $from_ts The starting timestamp
* @param integer $to_ts The ending timestamp
* @param integer $cutoffdate The earliest date to retrieve back to.
*
* @param boolean $ping If true, returned changeset may
* not contain the full changeset, but rather
* only a single change, designed only to
* indicate *some* change has taken place. The
* value should not be used to determine *what*
* change has taken place.
* @param boolean $ignoreFirstSync If true, will not trigger an initial sync
* if $from_ts is 0. Needed to avoid race
* conditions when we don't have any history
* data. @since 2.6.0
* @todo If we can pass the synckey (
* perhaps as part of $folder), we can
* just look for synckey 0 to know when
* we CAN trigger an initial sync without
* this flag.
* @param integer $maxitems Maximum number of recipients for a RI
* collection. @since 2.12.0
* @param boolean $refreshFilter Force a SOFTDELETE operation and check
* for new items within the (newly changed)
* current FilterType interval.
* @since 2.19.0
*
* @return array An array of hashes that contain the ids of items that have
* changed in the specified collection along with a 'type'
* flag that indicates the type of change.
* @todo H6 - Clean up method parameters, update parent class etc...
* - Return a new ids object.
* - Refactor to use a Repository pattern for each supported
* collection and move the bulk of the logic in the switch
* structure below to the various classes - and refactor out most
* of the stuff in the registry connector since the Repositories
* will handle the basic CRUD operations and change detection on
* each collection.
*/
public function getServerChanges($folder, $from_ts, $to_ts, $cutoffdate, $ping, $ignoreFirstSync = false, $maxitems = 100, $refreshFilter = false)
{
$this->_logger->info(sprintf("[%s] Horde_Core_ActiveSync_Driver::getServerChanges(%s, %u, %u, %u, %d, %s, %u, %s)", $this->_pid, $folder->serverid(), $from_ts, $to_ts, $cutoffdate, $ping, $ignoreFirstSync, $maxitems, $refreshFilter));
$changes = array('add' => array(), 'delete' => array(), 'modify' => array(), 'soft' => array());
ob_start();
switch ($folder->collectionClass()) {
case Horde_ActiveSync::CLASS_CALENDAR:
if ($folder->serverid() == self::APPOINTMENTS_FOLDER_UID) {
$server_id = null;
} else {
$parts = $this->_parseFolderId($folder->serverid());
$server_id = $parts[self::FOLDER_PART_ID];
}
if ($from_ts == 0 && !$ignoreFirstSync) {
// Can't use History if it's a first sync
$startstamp = (int) $cutoffdate;
$endstamp = time() + 32140800;
//60 * 60 * 24 * 31 * 12 == one year
try {
$changes['add'] = $this->_connector->calendar_listUids($startstamp, $endstamp, $server_id);
} catch (Horde_Exception_AuthenticationFailure $e) {
$this->_endBuffer();
throw $e;
} catch (Horde_Exception $e) {
$this->_logger->err($e->getMessage());
$this->_endBuffer();
return array();
}
$folder->setSoftDeleteTimes($cutoffdate, time());
} else {
try {
$changes = $this->_connector->getChanges('calendar', $from_ts, $to_ts, $server_id);
} catch (Horde_Exception_AuthenticationFailure $e) {
$this->_endBuffer();
throw $e;
} catch (Horde_Exception $e) {
$this->_logger->err($e->getMessage());
$this->_endBuffer();
return array();
}
// Softdelete. We check this once per close-to 24 hour period,or
// if the FILTERTYPE range changes. We introduce an element of
// randomness in the time to help avoid a large number of
// clients performing a softdelete at once. It's 23 hours + some
// random number of minutes < 60.
//
// @todo We need to populate additional events if the FILTERTYPE
// is expanded, but we need to persist the previous FILTERTYPE
// so we know exactly which events we already have and which
// we don't (since we don't track the UIDs themselves).
if (!$ping) {
if (!$refreshFilter) {
$sd = $folder->getSoftDeleteTimes();
if ($sd[1] + 82800 + mt_rand(0, 3600) < time()) {
$from = $sd[2];
}
} else {
// Force refresh the FILTERTYPE.
$from = 0;
}
if (isset($from)) {
$this->_logger->info(sprintf('[%s] Polling for SOFTDELETE items in calendar collection', getmypid()));
// Gets the list of ids contained between the last softdelete
// run and the current cutoffdate.
$changes['soft'] = $this->_connector->softDelete('calendar', $from, $cutoffdate, $server_id);
$folder->setSoftDeleteTimes($cutoffdate, time());
}
}
}
break;
case Horde_ActiveSync::CLASS_CONTACTS:
// Multiplexed or multiple?
if ($folder->serverid() == self::CONTACTS_FOLDER_UID) {
$server_id = null;
} else {
$parts = $this->_parseFolderId($folder->serverid());
$server_id = $parts[self::FOLDER_PART_ID];
}
// Can't use History for first sync
if ($from_ts == 0 && !$ignoreFirstSync) {
try {
$changes['add'] = $this->_connector->contacts_listUids($server_id);
} catch (Horde_Exception_AuthenticationFailure $e) {
$this->_endBuffer();
throw $e;
} catch (Horde_Exception $e) {
$this->_logger->err($e->getMessage());
$this->_endBuffer();
return array();
}
} else {
try {
$changes = $this->_connector->getChanges('contacts', $from_ts, $to_ts, $server_id);
} catch (Horde_Exception_AuthenticationFailure $e) {
$this->_endBuffer();
throw $e;
} catch (Horde_Exception $e) {
$this->_logger->err($e->getMessage());
$this->_endBuffer();
return array();
}
}
break;
case Horde_ActiveSync::CLASS_TASKS:
// Multiplexed or multiple?
if ($folder->serverid() == self::TASKS_FOLDER_UID) {
$server_id = null;
} else {
$parts = $this->_parseFolderId($folder->serverid());
$server_id = $parts[self::FOLDER_PART_ID];
}
// Can't use History for first sync
if ($from_ts == 0 && !$ignoreFirstSync) {
try {
$changes['add'] = $this->_connector->tasks_listUids($server_id);
} catch (Horde_Exception_AuthenticationFailure $e) {
$this->_endBuffer();
throw $e;
} catch (Horde_Exception $e) {
$this->_logger->err($e->getMessage());
$this->_endBuffer();
return array();
}
} else {
try {
$changes = $this->_connector->getChanges('tasks', $from_ts, $to_ts, $server_id);
} catch (Horde_Exception_AuthenticationFailure $e) {
$this->_endBuffer();
throw $e;
} catch (Horde_Exception $e) {
$this->_logger->err($e->getMessage());
$this->_endBuffer();
return array();
}
}
break;
case Horde_ActiveSync::CLASS_NOTES:
// Multiplexed or multiple?
if ($folder->serverid() == self::NOTES_FOLDER_UID) {
$server_id = null;
} else {
$parts = $this->_parseFolderId($folder->serverid());
$server_id = $parts[self::FOLDER_PART_ID];
}
// Can't use History for first sync
if ($from_ts == 0 && !$ignoreFirstSync) {
try {
$changes['add'] = $this->_connector->notes_listUids($server_id);
} catch (Horde_Exception_AuthenticationFailure $e) {
$this->_endBuffer();
throw $e;
} catch (Horde_Exception $e) {
$this->_logger->err($e->getMessage());
$this->_endBuffer();
return array();
}
} else {
try {
$changes = $this->_connector->getChanges('notes', $from_ts, $to_ts, $server_id);
} catch (Horde_Exception_AuthenticationFailure $e) {
$this->_endBuffer();
throw $e;
} catch (Horde_Exception $e) {
$this->_logger->err($e->getMessage());
$this->_endBuffer();
return array();
}
}
break;
case 'RI':
$folder->setChanges($this->_connector->getRecipientCache($maxitems));
$changes = array('add' => $folder->added(), 'delete' => $folder->removed(), 'modify' => array(), 'soft' => array());
break;
case Horde_ActiveSync::CLASS_EMAIL:
if (empty($this->_imap)) {
$this->_endBuffer();
return array();
}
$this->_logger->info(sprintf('[%s] %s IMAP PREVIOUS MODSEQ: %d', $this->_pid, $folder->serverid(), $folder->modseq()));
if ($ping) {
try {
$ping_res = $this->_imap->ping($folder);
if ($ping_res) {
$changes['add'] = array(1);
}
} catch (Horde_ActiveSync_Exeption_StaleState $e) {
$this->_endBuffer();
throw $e;
} catch (Horde_ActiveSync_Exception_FolderGone $e) {
$this->_endBuffer();
throw $e;
} catch (Horde_Exception_AuthenticationFailure $e) {
$this->_endBuffer();
throw $e;
} catch (Horde_Exception $e) {
$this->_logger->err($e->getMessage());
$this->_endBuffer();
return array();
}
} else {
// SOFTDELETE, but only if we aren't refreshing FILTERTYPE.
$soft = false;
if (!$refreshFilter) {
$sd = $folder->getSoftDeleteTimes();
if (empty($sd[0]) && empty($sd[1]) && !empty($cutoffdate)) {
// No SOFTDELETE performed, this is likely the first
// sync so we must prime the SOFTDELETE values.
$folder->setSoftDeleteTimes((int) $cutoffdate, time());
} else {
if ($sd[1] + 82800 + mt_rand(0, 3600) < time()) {
$soft = true;
} else {
$soft = false;
}
}
}
try {
$folder = $this->_imap->getMessageChanges($folder, array('sincedate' => (int) $cutoffdate, 'protocolversion' => $this->_version, 'softdelete' => $soft, 'refreshfilter' => $refreshFilter));
// Poll the maillog for reply/forward state changes.
if (empty($GLOBALS['conf']['activesync']['no_maillogsync'])) {
$folder = $this->_getMaillogChanges($folder, $from_ts);
}
} catch (Horde_ActiveSync_Exception_StaleState $e) {
$this->_endBuffer();
throw $e;
} catch (Horde_ActiveSync_Exception_FolderGone $e) {
$this->_endBuffer();
throw $e;
} catch (Horde_Exception_AuthenticationFailure $e) {
$this->_endBuffer();
throw $e;
} catch (Horde_Exception $e) {
$this->_logger->err($e->getMessage());
$this->_endBuffer();
return array();
}
$changes['add'] = $folder->added();
$changes['delete'] = $folder->removed();
$changes['modify'] = $folder->changed();
$changes['soft'] = $folder->getSoftDeleted();
}
}
$results = array();
if (!$folder->haveInitialSync) {
$this->_logger->info(sprintf('[%s] Initial sync, only sending UID.', $this->_pid));
$this->_endBuffer();
$add = $changes['add'];
$changes = null;
return $add;
} else {
foreach ($changes['add'] as $add) {
$results[] = array('id' => $add, 'type' => Horde_ActiveSync::CHANGE_TYPE_CHANGE, 'flags' => Horde_ActiveSync::FLAG_NEWMESSAGE);
}
}
// For CLASS_EMAIL, all changes are a change in flags, categories or
// softdelete.
if ($folder->collectionClass() == Horde_ActiveSync::CLASS_EMAIL) {
$flags = $folder->flags();
$categories = $folder->categories();
foreach ($changes['modify'] as $uid) {
$results[] = array('id' => $uid, 'type' => Horde_ActiveSync::CHANGE_TYPE_FLAGS, 'flags' => $flags[$uid], 'categories' => $categories[$uid]);
}
} else {
foreach ($changes['modify'] as $change) {
$results[] = array('id' => $change, 'type' => Horde_ActiveSync::CHANGE_TYPE_CHANGE);
}
}
// Server Deletions and Softdeletions
foreach ($changes['delete'] as $deleted) {
$results[] = array('id' => $deleted, 'type' => Horde_ActiveSync::CHANGE_TYPE_DELETE);
}
if (!empty($changes['soft'])) {
foreach ($changes['soft'] as $deleted) {
$results[] = array('id' => $deleted, 'type' => Horde_ActiveSync::CHANGE_TYPE_SOFTDELETE);
}
}
$this->_endBuffer();
return $results;
}