Horde_ActiveSync_Imap_Adapter::getMessageChanges PHP Method

getMessageChanges() public method

Return message changes from the specified mailbox.
public getMessageChanges ( Horde_ActiveSync_Folder_Imap $folder, array $options = [] ) : Horde_ActiveSync_Folder_Imap
$folder Horde_ActiveSync_Folder_Imap The folder object.
$options array 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 - refreshfilter: (boolean) If true, force refresh the query to reflect changes in FILTERTYPE (using the sincedate) @since 2.28.0
return Horde_ActiveSync_Folder_Imap The folder object, containing any change instructions for the device.
    public function getMessageChanges(Horde_ActiveSync_Folder_Imap $folder, array $options = array())
    {
        $imap = $this->_getImapOb();
        $mbox = new Horde_Imap_Client_Mailbox($folder->serverid());
        $flags = array();
        $search_uids = array();
        // 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)));
        // UIDVALIDITY
        if ($folder->uidnext() != 0) {
            $folder->checkValidity($status);
        }
        $current_modseq = $status[Horde_ActiveSync_Folder_Imap::HIGHESTMODSEQ];
        $modseq_corrupted = $folder->modseq() > $current_modseq;
        if ($modseq_corrupted || $current_modseq && $folder->modseq() > 0 && ($folder->modseq() < $current_modseq || !empty($options['softdelete']) || !empty($options['refreshfilter']))) {
            $strategy = new Horde_ActiveSync_Imap_Strategy_Modseq($this->_imap, $status, $folder, $this->_logger);
            $folder = $strategy->getChanges($options);
        } elseif ($folder->uidnext() == 0) {
            $strategy = new Horde_ActiveSync_Imap_Strategy_Initial($this->_imap, $status, $folder, $this->_logger);
            $folder = $strategy->getChanges($options);
        } elseif ($current_modseq == 0) {
            $strategy = new Horde_ActiveSync_Imap_Strategy_Plain($this->_imap, $status, $folder, $this->_logger);
            $folder = $strategy->getChanges($options);
        } elseif ($current_modseq > 0 && $folder->modseq() == 0) {
            throw new Horde_ActiveSync_Exception_StaleState('Transition to MODSEQ enabled server');
        }
        $folder->setStatus($status);
        return $folder;
    }

Usage Example

Example #1
0
 /**
  * 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;
 }
All Usage Examples Of Horde_ActiveSync_Imap_Adapter::getMessageChanges