canSendEmptyResponse() public méthode

Return if we can do an empty response
public canSendEmptyResponse ( ) : boolean
Résultat boolean
Exemple #1
0
 /**
  * Handle the sync request
  *
  * @return boolean
  * @throws Horde_ActiveSync_Exception
  */
 protected function _handle()
 {
     $this->_logger->info(sprintf('[%s] Handling SYNC command.', $this->_procid));
     // Check policy
     if (!$this->checkPolicyKey($this->_activeSync->getPolicyKey(), Horde_ActiveSync::SYNC_SYNCHRONIZE)) {
         return true;
     }
     // Check global errors.
     if ($error = $this->_activeSync->checkGlobalError()) {
         $this->_statusCode = $error;
         $this->_handleGlobalSyncError();
         return true;
     }
     // Defaults
     $this->_statusCode = self::STATUS_SUCCESS;
     $partial = false;
     try {
         $this->_collections = $this->_activeSync->getCollectionsObject();
     } catch (Horde_ActiveSync_Exception $e) {
         $this->_statusCode = self::STATUS_SERVERERROR;
         $this->_handleGlobalSyncError();
         return true;
     }
     // Sanity check
     if ($this->_device->version >= Horde_ActiveSync::VERSION_TWELVEONE) {
         // We don't have a previous FOLDERSYNC.
         if (!$this->_collections->haveHierarchy()) {
             $this->_logger->notice(sprintf('[%s] No HIERARCHY SYNCKEY in sync_cache, invalidating.', $this->_procid));
             $this->_statusCode = self::STATUS_FOLDERSYNC_REQUIRED;
             $this->_handleGlobalSyncError();
             return true;
         }
     }
     // Start decoding request
     if (!$this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_SYNCHRONIZE)) {
         if ($this->_device->version >= Horde_ActiveSync::VERSION_TWELVEONE) {
             $this->_logger->info(sprintf('[%s] Empty Sync request, taking info from SyncCache.', $this->_procid));
             if ($this->_collections->cachedCollectionCount() == 0) {
                 $this->_logger->warn(sprintf('[%s] Empty SYNC request but no SyncCache or SyncCache with no collections.', $this->_procid));
                 $this->_statusCode = self::STATUS_REQUEST_INCOMPLETE;
                 $this->_handleGlobalSyncError();
                 return true;
             } else {
                 if (!$this->_collections->initEmptySync()) {
                     $this->_statusCode = self::STATUS_REQUEST_INCOMPLETE;
                     $this->_handleGlobalSyncError();
                     return true;
                 }
             }
         } else {
             $this->_statusCode = self::STATUS_REQUEST_INCOMPLETE;
             $this->_handleGlobalSyncError();
             $this->_logger->err('Empty Sync request and protocolversion < 12.1');
             return true;
         }
     } else {
         // Start decoding request.
         $this->_collections->hangingSync = false;
         while (($sync_tag = $this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_WINDOWSIZE) ? Horde_ActiveSync::SYNC_WINDOWSIZE : ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_FOLDERS) ? Horde_ActiveSync::SYNC_FOLDERS : ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_PARTIAL) ? Horde_ActiveSync::SYNC_PARTIAL : ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_WAIT) ? Horde_ActiveSync::SYNC_WAIT : ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_HEARTBEATINTERVAL) ? Horde_ActiveSync::SYNC_HEARTBEATINTERVAL : -1))))) != -1) {
             switch ($sync_tag) {
                 case Horde_ActiveSync::SYNC_HEARTBEATINTERVAL:
                     if ($hbinterval = $this->_decoder->getElementContent()) {
                         $this->_collections->setHeartbeat(array('hbinterval' => $hbinterval));
                         $this->_collections->hangingSync = true;
                         $this->_decoder->getElementEndTag();
                         if ($hbinterval > self::MAX_HEARTBEAT) {
                             $this->_logger->err(sprintf('[%s] HeartbeatInterval outside of allowed range.', $this->_procid));
                             $this->_statusCode = self::STATUS_INVALID_WAIT_HEARTBEATINTERVAL;
                             $this->_handleGlobalSyncError(self::MAX_HEARTBEAT);
                             return true;
                         }
                     }
                     break;
                 case Horde_ActiveSync::SYNC_WAIT:
                     if ($wait = $this->_decoder->getElementContent()) {
                         $this->_collections->setHeartbeat(array('wait' => $wait));
                         $this->_collections->hangingSync = true;
                         $this->_decoder->getElementEndTag();
                         if ($wait > self::MAX_HEARTBEAT / 60) {
                             $this->_logger->err(sprintf('[%s] Wait value outside of allowed range.', $this->_procid));
                             $this->_statusCode = self::STATUS_INVALID_WAIT_HEARTBEATINTERVAL;
                             $this->_handleGlobalSyncError(self::MAX_HEARBEAT / 60);
                             return true;
                         }
                     }
                     break;
                 case Horde_ActiveSync::SYNC_PARTIAL:
                     if ($this->_decoder->getElementContent(Horde_ActiveSync::SYNC_PARTIAL)) {
                         $this->_decoder->getElementEndTag();
                     }
                     $partial = true;
                     break;
                 case Horde_ActiveSync::SYNC_WINDOWSIZE:
                     $this->_collections->setDefaultWindowSize($this->_decoder->getElementContent());
                     if (!$this->_decoder->getElementEndTag()) {
                         $this->_logger->err('PROTOCOL ERROR');
                         return false;
                     }
                     break;
                 case Horde_ActiveSync::SYNC_FOLDERS:
                     if (!$this->_parseSyncFolders()) {
                         // Any errors are handled in _parseSyncFolders() and
                         // appropriate error codes sent to device.
                         return true;
                     }
             }
         }
         if ($this->_device->version >= Horde_ActiveSync::VERSION_TWELVEONE) {
             // These are not allowed in the same request.
             if ($this->_collections->hbinterval !== false && $this->_collections->wait !== false) {
                 $this->_logger->err(sprintf('[%s] Received both HBINTERVAL and WAIT interval in same request.', $this->_procid));
                 $this->_statusCode = Horde_ActiveSync_Status::INVALID_XML;
                 $this->_handleGlobalSyncError();
                 return true;
             }
             // Fill in missing sticky data from cache.
             $this->_collections->validateFromCache();
         }
         // Ensure we have OPTIONS values.
         $this->_collections->ensureOptions();
         // Full or partial sync request?
         if ($partial === true) {
             $this->_logger->info(sprintf('[%s] Executing a PARTIAL SYNC.', $this->_procid));
             if (!$this->_collections->initPartialSync()) {
                 $this->_statusCode = self::STATUS_REQUEST_INCOMPLETE;
                 $this->_handleGlobalSyncError();
                 return true;
             }
             // Fill in any missing collections that were already sent.
             // @TODO: Can we move this to initPartialSync()?
             $this->_collections->getMissingCollectionsFromCache();
         } else {
             // Full request.
             $this->_collections->initFullSync();
         }
         // End SYNC tag.
         if (!$this->_decoder->getElementEndTag()) {
             $this->_statusCode = self::STATUS_PROTERROR;
             $this->_handleGlobalSyncError();
             $this->_logger->err('PROTOCOL ERROR: Missing closing SYNC tag');
             return false;
         }
         // We MUST have syncable collections by now.
         if (!$this->_collections->haveSyncableCollections($this->_device->version)) {
             $this->_statusCode = self::STATUS_KEYMISM;
             $this->_handleGlobalSyncError();
             return true;
         }
         // Update the syncCache with the new collection data.
         $this->_collections->updateCache();
         // Save.
         $this->_collections->save(true);
         $this->_logger->info(sprintf('[%s] All synckeys confirmed. Continuing with SYNC', $this->_procid));
     }
     $pingSettings = $this->_driver->getHeartbeatConfig();
     // If this is >= 12.1, see if we want a looping SYNC.
     if ($this->_collections->canDoLoopingSync() && $this->_device->version >= Horde_ActiveSync::VERSION_TWELVEONE && $this->_statusCode == self::STATUS_SUCCESS) {
         // Calculate the heartbeat
         if (!($heartbeat = $this->_collections->getHeartbeat())) {
             $heartbeat = !empty($pingSettings['heartbeatdefault']) ? $pingSettings['heartbeatdefault'] : 10;
         }
         // Wait for changes.
         $changes = $this->_collections->pollForChanges($heartbeat, $pingSettings['waitinterval']);
         if ($changes !== true && $changes !== false) {
             switch ($changes) {
                 case Horde_ActiveSync_Collections::COLLECTION_ERR_STALE:
                     $this->_logger->notice(sprintf('[%s] Changes in cache detected during looping SYNC exiting here.', $this->_procid));
                     return true;
                 case Horde_ActiveSync_Collections::COLLECTION_ERR_FOLDERSYNC_REQUIRED:
                     $this->_statusCode = self::STATUS_FOLDERSYNC_REQUIRED;
                     $this->_handleGlobalSyncError();
                     return true;
                 case Horde_ActiveSync_Collections::COLLECTION_ERR_SYNC_REQUIRED:
                     $this->_statusCode = self::STATUS_REQUEST_INCOMPLETE;
                     $this->_handleGlobalSyncError();
                     return true;
                 default:
                     $this->_statusCode = self::STATUS_SERVERERROR;
                     $this->_handleGlobalSyncError();
                     return true;
             }
         }
     }
     // See if we can do an empty response
     if ($this->_device->version >= Horde_ActiveSync::VERSION_TWELVEONE && $this->_statusCode == self::STATUS_SUCCESS && empty($changes) && $this->_collections->canSendEmptyResponse()) {
         $this->_logger->info(sprintf('[%s] Sending an empty SYNC response.', $this->_procid));
         $this->_collections->lastsyncendnormal = time();
         $this->_collections->save(true);
         return true;
     }
     $this->_logger->info(sprintf('[%s] Completed parsing incoming request. Peak memory usage: %d.', $this->_procid, memory_get_peak_usage(true)));
     // Start output to client
     $this->_encoder->startWBXML();
     $this->_encoder->startTag(Horde_ActiveSync::SYNC_SYNCHRONIZE);
     $this->_encoder->startTag(Horde_ActiveSync::SYNC_STATUS);
     $this->_encoder->content(self::STATUS_SUCCESS);
     $this->_encoder->endTag();
     $this->_encoder->startTag(Horde_ActiveSync::SYNC_FOLDERS);
     $exporter = new Horde_ActiveSync_Connector_Exporter($this->_activeSync, $this->_encoder);
     $cnt_global = 0;
     $over_window = false;
     foreach ($this->_collections as $id => $collection) {
         $statusCode = self::STATUS_SUCCESS;
         $changecount = 0;
         if ($over_window || $cnt_global > $this->_collections->getDefaultWindowSize()) {
             $this->_sendOverWindowResponse($collection);
             continue;
         }
         try {
             $this->_collections->initCollectionState($collection);
         } catch (Horde_ActiveSync_Exception_StateGone $e) {
             $this->_logger->notice(sprintf('[%s] SYNC terminating, state not found', $this->_procid));
             $statusCode = self::STATUS_KEYMISM;
         } catch (Horde_ActiveSync_Exception_FolderGone $e) {
             // This isn't strictly correct, but at least some versions of
             // iOS need this in order to catch missing state.
             $this->_logger->err($e->getMessage());
             $statusCode = self::STATUS_FOLDERSYNC_REQUIRED;
         } catch (Horde_ActiveSync_Exception_StaleState $e) {
             $this->_logger->notice($e->getMessage());
             return false;
         } catch (Horde_ActiveSync_Exception $e) {
             $this->_logger->err($e->getMessage());
             return false;
         }
         // Outlook explicitly tells the server to NOT check for server
         // changes when importing client changes, unlike EVERY OTHER client
         // out there. This completely screws up many things like conflict
         // detection since we can't update the sync_ts until we actually
         // check for changes. So, we need to FORCE a change detection cycle
         // to be sure we don't screw up state. Any detected changes will be
         // ignored until the next cycle, utilizing the existing mechanism
         // for sending MOREITEMS when we will return the previously
         // selected changes. getchanges defaults to true if it is missing
         // and the synckey != 0, defaults to true if it is present as an
         // empty tag. If it is present, but '0' or not present but synckey
         // is 0 then it defaults to false.
         if (!isset($collection['getchanges']) && $collection['synckey'] != '0') {
             $collection['getchanges'] = true;
         }
         if (!empty($collection['importedchanges']) && empty($collection['getchanges'])) {
             $forceChanges = true;
             $collection['getchanges'] = true;
             $this->_logger->notice(sprintf('[%s] Force a GETCHANGES due to incoming changes.', $this->_procid));
         }
         if ($statusCode == self::STATUS_SUCCESS && (!empty($collection['getchanges']) || !isset($collection['getchanges']) && $collection['synckey'] != '0')) {
             try {
                 $changecount = $this->_collections->getCollectionChangeCount();
             } catch (Horde_ActiveSync_Exception_StaleState $e) {
                 $this->_logger->err(sprintf('[%s] Force restting of state for %s: %s', $this->_procid, $id, $e->getMessage()));
                 $this->_state->loadState(array(), null, Horde_ActiveSync::REQUEST_TYPE_SYNC, $id);
                 $statusCode = self::STATUS_KEYMISM;
             } catch (Horde_ActiveSync_Exception_StateGone $e) {
                 $this->_logger->warn(sprintf('[%s] SYNCKEY not found. Reset required.', $this->_procid));
                 $statusCode = self::STATUS_KEYMISM;
             } catch (Horde_ActiveSync_Exception_FolderGone $e) {
                 $this->_logger->warn(sprintf('[%s] FOLDERSYNC required, collection gone.', $this->_procid));
                 $statusCode = self::STATUS_FOLDERSYNC_REQUIRED;
             }
         }
         // Get new synckey if needed. We need a new synckey if there were
         // any changes (incoming or outgoing), if this is during the
         // initial sync pairing of the collection, or if we received a
         // SYNC due to changes found during a PING (since those changes
         // may be changes to items that never even made it to the client in
         // the first place (See Bug: 12075).
         if ($statusCode == self::STATUS_SUCCESS && (!empty($collection['importedchanges']) || !empty($changecount) || $collection['synckey'] == '0' || $this->_state->getSyncKeyCounter($collection['synckey']) == 1 || !empty($collection['fetchids']) || $this->_collections->hasPingChangeFlag($id))) {
             try {
                 $collection['newsynckey'] = $this->_state->getNewSyncKey($collection['synckey']);
                 $this->_logger->info(sprintf('[%s] Old SYNCKEY: %s, New SYNCKEY: %s', $this->_procid, $collection['synckey'], $collection['newsynckey']));
             } catch (Horde_ActiveSync_Exception $e) {
                 $statusCode = self::STATUS_KEYMISM;
             }
         }
         $this->_encoder->startTag(Horde_ActiveSync::SYNC_FOLDER);
         // Not sent in > 12.0
         if ($this->_device->version <= Horde_ActiveSync::VERSION_TWELVE) {
             $this->_encoder->startTag(Horde_ActiveSync::SYNC_FOLDERTYPE);
             $this->_encoder->content($collection['class']);
             $this->_encoder->endTag();
         }
         $this->_encoder->startTag(Horde_ActiveSync::SYNC_SYNCKEY);
         if (!empty($collection['newsynckey'])) {
             $this->_encoder->content($collection['newsynckey']);
         } else {
             $this->_encoder->content($collection['synckey']);
         }
         $this->_encoder->endTag();
         $this->_encoder->startTag(Horde_ActiveSync::SYNC_FOLDERID);
         $this->_encoder->content($collection['id']);
         $this->_encoder->endTag();
         $this->_encoder->startTag(Horde_ActiveSync::SYNC_STATUS);
         $this->_encoder->content($statusCode);
         $this->_encoder->endTag();
         if ($statusCode == self::STATUS_SUCCESS) {
             // Send server changes to client
             if ($statusCode == self::STATUS_SUCCESS && empty($forceChanges) && (!empty($collection['getchanges']) || !isset($collection['getchanges']) && !empty($collection['synckey']))) {
                 $max_windowsize = !empty($pingSettings['maximumwindowsize']) ? min($collection['windowsize'], $pingSettings['maximumwindowsize']) : $collection['windowsize'];
                 if (!empty($changecount) && $changecount > $max_windowsize || $cnt_global + $changecount > $this->_collections->getDefaultWindowSize()) {
                     $this->_logger->info(sprintf('[%s] Sending MOREAVAILABLE. WINDOWSIZE = %d, $changecount = %d, $cnt_global = %d', $this->_procid, $max_windowsize, $changecount, $cnt_global));
                     $this->_encoder->startTag(Horde_ActiveSync::SYNC_MOREAVAILABLE, false, true);
                     $over_window = $cnt_global + $changecount > $this->_collections->getDefaultWindowSize();
                 }
                 if (!empty($changecount)) {
                     $exporter->setChanges($this->_collections->getCollectionChanges(false), $collection);
                     $this->_encoder->startTag(Horde_ActiveSync::SYNC_COMMANDS);
                     $cnt_collection = 0;
                     while ($cnt_collection < $max_windowsize && $cnt_global < $this->_collections->getDefaultWindowSize() && ($progress = $exporter->sendNextChange())) {
                         $this->_logger->info(sprintf('[%s] Peak memory usage after message: %d', $this->_procid, memory_get_peak_usage(true)));
                         if ($progress === true) {
                             ++$cnt_collection;
                             ++$cnt_global;
                         }
                     }
                     $this->_encoder->endTag();
                 }
             }
             if (!empty($collection['clientids']) || !empty($collection['fetchids']) || !empty($collection['missing']) || !empty($collection['importfailures'])) {
                 $this->_encoder->startTag(Horde_ActiveSync::SYNC_REPLIES);
                 // Output any SYNC_MODIFY failures
                 if (!empty($collection['importfailures'])) {
                     foreach ($collection['importfailures'] as $id => $reason) {
                         $this->_encoder->startTag(Horde_ActiveSync::SYNC_MODIFY);
                         $this->_encoder->startTag(Horde_ActiveSync::SYNC_FOLDERTYPE);
                         $this->_encoder->content($collection['class']);
                         $this->_encoder->endTag();
                         $this->_encoder->startTag(Horde_ActiveSync::SYNC_SERVERENTRYID);
                         $this->_encoder->content($id);
                         $this->_encoder->endTag();
                         $this->_encoder->startTag(Horde_ActiveSync::SYNC_STATUS);
                         $this->_encoder->content($reason);
                         $this->_encoder->endTag();
                         $this->_encoder->endTag();
                     }
                 }
                 // Output server IDs for new items we received and added from client
                 if (!empty($collection['clientids'])) {
                     foreach ($collection['clientids'] as $clientid => $serverid) {
                         if ($serverid) {
                             $status = self::STATUS_SUCCESS;
                         } else {
                             $status = self::STATUS_INVALID;
                         }
                         $this->_encoder->startTag(Horde_ActiveSync::SYNC_ADD);
                         // If we have clientids and a CLASS_EMAIL, this is
                         // a SMS response.
                         if ($collection['class'] == Horde_ActiveSync::CLASS_EMAIL) {
                             $this->_encoder->startTag(Horde_ActiveSync::SYNC_FOLDERTYPE);
                             $this->_encoder->content(Horde_ActiveSync::CLASS_SMS);
                             $this->_encoder->endTag();
                         }
                         $this->_encoder->startTag(Horde_ActiveSync::SYNC_CLIENTENTRYID);
                         $this->_encoder->content($clientid);
                         $this->_encoder->endTag();
                         if ($status == self::STATUS_SUCCESS) {
                             $this->_encoder->startTag(Horde_ActiveSync::SYNC_SERVERENTRYID);
                             $this->_encoder->content($serverid);
                             $this->_encoder->endTag();
                         }
                         $this->_encoder->startTag(Horde_ActiveSync::SYNC_STATUS);
                         $this->_encoder->content($status);
                         $this->_encoder->endTag();
                         $this->_encoder->endTag();
                     }
                 }
                 // Output any errors from missing messages in REMOVE requests.
                 if (!empty($collection['missing'])) {
                     foreach ($collection['missing'] as $uid) {
                         $this->_encoder->startTag(Horde_ActiveSync::SYNC_REMOVE);
                         $this->_encoder->startTag(Horde_ActiveSync::SYNC_CLIENTENTRYID);
                         $this->_encoder->content($uid);
                         $this->_encoder->endTag();
                         $this->_encoder->startTag(Horde_ActiveSync::SYNC_STATUS);
                         $this->_encoder->content(self::STATUS_NOTFOUND);
                         $this->_encoder->endTag();
                         $this->_encoder->endTag();
                     }
                 }
                 if (!empty($collection['fetchids'])) {
                     // Output any FETCH requests
                     foreach ($collection['fetchids'] as $fetch_id) {
                         try {
                             $data = $this->_driver->fetch($collection['serverid'], $fetch_id, $collection);
                             $this->_encoder->startTag(Horde_ActiveSync::SYNC_FETCH);
                             $this->_encoder->startTag(Horde_ActiveSync::SYNC_SERVERENTRYID);
                             $this->_encoder->content($fetch_id);
                             $this->_encoder->endTag();
                             $this->_encoder->startTag(Horde_ActiveSync::SYNC_STATUS);
                             $this->_encoder->content(self::STATUS_SUCCESS);
                             $this->_encoder->endTag();
                             $this->_encoder->startTag(Horde_ActiveSync::SYNC_DATA);
                             $data->encodeStream($this->_encoder);
                             $this->_encoder->endTag();
                             $this->_encoder->endTag();
                         } catch (Horde_Exception_NotFound $e) {
                             $this->_logger->err(sprintf('[%s] Unable to fetch %s', $this->_procid, $fetch_id));
                             $this->_encoder->startTag(Horde_ActiveSync::SYNC_FETCH);
                             $this->_encoder->startTag(Horde_ActiveSync::SYNC_SERVERENTRYID);
                             $this->_encoder->content($fetch_id);
                             $this->_encoder->endTag();
                             $this->_encoder->startTag(Horde_ActiveSync::SYNC_STATUS);
                             $this->_encoder->content(self::STATUS_NOTFOUND);
                             $this->_encoder->endTag();
                             $this->_encoder->endTag();
                         }
                     }
                 }
                 $this->_encoder->endTag();
             }
             // Save the sync state for the next time
             if (!empty($collection['newsynckey'])) {
                 if (!empty($sync) || !empty($importer) || $collection['synckey'] == 0) {
                     $this->_state->setNewSyncKey($collection['newsynckey']);
                     $this->_state->save();
                 } else {
                     $this->_logger->err(sprintf('[%s] Error saving %s - no state information available.', $this->_procid, $collection['newsynckey']));
                 }
                 // Do we need to add the new synckey to the syncCache?
                 if ($collection['newsynckey'] != $collection['synckey']) {
                     $this->_collections->addConfirmedKey($collection['newsynckey']);
                 }
                 $this->_collections->updateCollection($collection, array('newsynckey' => true, 'unsetChanges' => true, 'unsetPingChangeFlag' => true));
             }
         }
         $this->_encoder->endTag();
         $this->_logger->info(sprintf('[%s] Collection output peak memory usage: %d', $this->_procid, memory_get_peak_usage(true)));
     }
     $this->_encoder->endTag();
     $this->_encoder->endTag();
     if ($this->_device->version >= Horde_ActiveSync::VERSION_TWELVEONE) {
         if ($this->_collections->checkStaleRequest()) {
             $this->_logger->info(sprintf('[%s] Changes detected in sync_cache during wait interval, exiting without updating cache.', $this->_procid));
             return true;
         } else {
             $this->_collections->lastsyncendnormal = time();
             $this->_collections->save(true);
         }
     } else {
         $this->_collections->save(true);
     }
     return true;
 }
Exemple #2
0
 /**
  * Handle the sync request
  *
  * @return boolean
  * @throws Horde_ActiveSync_Exception
  */
 protected function _handle()
 {
     $this->_logger->info(sprintf('[%s] Handling SYNC command.', $this->_procid));
     // Check policy
     if (!$this->checkPolicyKey($this->_activeSync->getPolicyKey(), Horde_ActiveSync::SYNC_SYNCHRONIZE)) {
         return true;
     }
     // Check global errors.
     if ($error = $this->_activeSync->checkGlobalError()) {
         $this->_statusCode = $error;
         $this->_handleGlobalSyncError();
         return true;
     }
     // Defaults
     $this->_statusCode = self::STATUS_SUCCESS;
     $partial = false;
     try {
         $this->_collections = $this->_activeSync->getCollectionsObject();
     } catch (Horde_ActiveSync_Exception $e) {
         $this->_statusCode = self::STATUS_SERVERERROR;
         $this->_handleGlobalSyncError();
         return true;
     }
     // Sanity check
     if ($this->_device->version >= Horde_ActiveSync::VERSION_TWELVEONE) {
         // We don't have a previous FOLDERSYNC.
         if (!$this->_collections->haveHierarchy()) {
             $this->_logger->notice(sprintf('[%s] No HIERARCHY SYNCKEY in sync_cache, invalidating.', $this->_procid));
             $this->_statusCode = self::STATUS_FOLDERSYNC_REQUIRED;
             $this->_handleGlobalSyncError();
             return true;
         }
     }
     // Start decoding request
     if (!$this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_SYNCHRONIZE)) {
         if ($this->_device->version >= Horde_ActiveSync::VERSION_TWELVEONE) {
             $this->_logger->info(sprintf('[%s] Empty Sync request, taking info from SyncCache.', $this->_procid));
             if ($this->_collections->cachedCollectionCount() == 0) {
                 $this->_logger->warn(sprintf('[%s] Empty SYNC request but no SyncCache or SyncCache with no collections.', $this->_procid));
                 $this->_statusCode = self::STATUS_REQUEST_INCOMPLETE;
                 $this->_handleGlobalSyncError();
                 return true;
             } else {
                 if (!$this->_collections->initEmptySync()) {
                     $this->_statusCode = self::STATUS_REQUEST_INCOMPLETE;
                     $this->_handleGlobalSyncError();
                     return true;
                 }
             }
         } else {
             $this->_statusCode = self::STATUS_REQUEST_INCOMPLETE;
             $this->_handleGlobalSyncError();
             $this->_logger->err('Empty Sync request and protocolversion < 12.1');
             return true;
         }
     } else {
         // Start decoding request.
         $this->_collections->hangingSync = false;
         while (($sync_tag = $this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_WINDOWSIZE) ? Horde_ActiveSync::SYNC_WINDOWSIZE : ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_FOLDERS) ? Horde_ActiveSync::SYNC_FOLDERS : ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_PARTIAL) ? Horde_ActiveSync::SYNC_PARTIAL : ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_WAIT) ? Horde_ActiveSync::SYNC_WAIT : ($this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_HEARTBEATINTERVAL) ? Horde_ActiveSync::SYNC_HEARTBEATINTERVAL : -1))))) != -1) {
             switch ($sync_tag) {
                 case Horde_ActiveSync::SYNC_HEARTBEATINTERVAL:
                     if ($hbinterval = $this->_decoder->getElementContent()) {
                         $this->_collections->setHeartbeat(array('hbinterval' => $hbinterval));
                         $this->_collections->hangingSync = true;
                         $this->_decoder->getElementEndTag();
                         if ($hbinterval > self::MAX_HEARTBEAT) {
                             $this->_logger->err(sprintf('[%s] HeartbeatInterval outside of allowed range.', $this->_procid));
                             $this->_statusCode = self::STATUS_INVALID_WAIT_HEARTBEATINTERVAL;
                             $this->_handleGlobalSyncError(self::MAX_HEARTBEAT);
                             return true;
                         }
                     }
                     break;
                 case Horde_ActiveSync::SYNC_WAIT:
                     if ($wait = $this->_decoder->getElementContent()) {
                         $this->_collections->setHeartbeat(array('wait' => $wait));
                         $this->_collections->hangingSync = true;
                         $this->_decoder->getElementEndTag();
                         if ($wait > self::MAX_HEARTBEAT / 60) {
                             $this->_logger->err(sprintf('[%s] Wait value outside of allowed range.', $this->_procid));
                             $this->_statusCode = self::STATUS_INVALID_WAIT_HEARTBEATINTERVAL;
                             $this->_handleGlobalSyncError(self::MAX_HEARBEAT / 60);
                             return true;
                         }
                     }
                     break;
                 case Horde_ActiveSync::SYNC_PARTIAL:
                     if ($this->_decoder->getElementContent(Horde_ActiveSync::SYNC_PARTIAL)) {
                         $this->_decoder->getElementEndTag();
                     }
                     $partial = true;
                     break;
                 case Horde_ActiveSync::SYNC_WINDOWSIZE:
                     $this->_collections->setDefaultWindowSize($this->_decoder->getElementContent());
                     if (!$this->_decoder->getElementEndTag()) {
                         $this->_logger->err('PROTOCOL ERROR');
                         return false;
                     }
                     break;
                 case Horde_ActiveSync::SYNC_FOLDERS:
                     if (!$this->_parseSyncFolders()) {
                         // Any errors are handled in _parseSyncFolders() and
                         // appropriate error codes sent to device.
                         return true;
                     }
             }
         }
         if ($this->_device->version >= Horde_ActiveSync::VERSION_TWELVEONE) {
             // These are not allowed in the same request.
             if ($this->_collections->hbinterval !== false && $this->_collections->wait !== false) {
                 $this->_logger->err(sprintf('[%s] Received both HBINTERVAL and WAIT interval in same request.', $this->_procid));
                 $this->_statusCode = Horde_ActiveSync_Status::INVALID_XML;
                 $this->_handleGlobalSyncError();
                 return true;
             }
             // Fill in missing sticky data from cache.
             $this->_collections->validateFromCache();
         }
         // Ensure we have OPTIONS values.
         $this->_collections->ensureOptions();
         // Full or partial sync request?
         if ($partial === true) {
             $this->_logger->info(sprintf('[%s] Executing a PARTIAL SYNC.', $this->_procid));
             if (!$this->_collections->initPartialSync()) {
                 $this->_statusCode = self::STATUS_REQUEST_INCOMPLETE;
                 $this->_handleGlobalSyncError();
                 return true;
             }
         } else {
             // Full request.
             $this->_collections->initFullSync();
         }
         // End SYNC tag.
         if (!$this->_decoder->getElementEndTag()) {
             $this->_statusCode = self::STATUS_PROTERROR;
             $this->_handleGlobalSyncError();
             $this->_logger->err('PROTOCOL ERROR: Missing closing SYNC tag');
             return false;
         }
         // We MUST have syncable collections by now.
         if (!$this->_collections->haveSyncableCollections($this->_device->version)) {
             $this->_statusCode = self::STATUS_KEYMISM;
             $this->_handleGlobalSyncError();
             return true;
         }
         // Update the syncCache with the new collection data.
         $this->_collections->updateCache();
         // Save.
         $this->_collections->save(true);
         $this->_logger->info(sprintf('[%s] All synckeys confirmed. Continuing with SYNC', $this->_procid));
     }
     $pingSettings = $this->_driver->getHeartbeatConfig();
     // Override the total, per-request, WINDOWSIZE?
     if (!empty($pingSettings['maximumrequestwindowsize'])) {
         $this->_collections->setDefaultWindowSize($pingSettings['maximumrequestwindowsize'], true);
     }
     // If this is >= 12.1, see if we want a looping SYNC.
     if ($this->_collections->canDoLoopingSync() && $this->_device->version >= Horde_ActiveSync::VERSION_TWELVEONE && $this->_statusCode == self::STATUS_SUCCESS) {
         // Calculate the heartbeat
         if (!($heartbeat = $this->_collections->getHeartbeat())) {
             $heartbeat = !empty($pingSettings['heartbeatdefault']) ? $pingSettings['heartbeatdefault'] : 10;
         }
         // Wait for changes.
         $changes = $this->_collections->pollForChanges($heartbeat, $pingSettings['waitinterval']);
         if ($changes !== true && $changes !== false) {
             switch ($changes) {
                 case Horde_ActiveSync_Collections::COLLECTION_ERR_STALE:
                     $this->_logger->notice(sprintf('[%s] Changes in cache detected during looping SYNC exiting here.', $this->_procid));
                     return true;
                 case Horde_ActiveSync_Collections::COLLECTION_ERR_FOLDERSYNC_REQUIRED:
                     $this->_statusCode = self::STATUS_FOLDERSYNC_REQUIRED;
                     $this->_handleGlobalSyncError();
                     return true;
                 case Horde_ActiveSync_Collections::COLLECTION_ERR_SYNC_REQUIRED:
                     $this->_statusCode = self::STATUS_REQUEST_INCOMPLETE;
                     $this->_handleGlobalSyncError();
                     return true;
                 default:
                     $this->_statusCode = self::STATUS_SERVERERROR;
                     $this->_handleGlobalSyncError();
                     return true;
             }
         }
     }
     // See if we can do an empty response
     if ($this->_device->version >= Horde_ActiveSync::VERSION_TWELVEONE && $this->_statusCode == self::STATUS_SUCCESS && empty($changes) && $this->_collections->canSendEmptyResponse()) {
         $this->_logger->info(sprintf('[%s] Sending an empty SYNC response.', $this->_procid));
         $this->_collections->lastsyncendnormal = time();
         $this->_collections->save(true);
         return true;
     }
     $this->_logger->info(sprintf('[%s] Completed parsing incoming request. Peak memory usage: %d.', $this->_procid, memory_get_peak_usage(true)));
     // Start output to client
     $this->_encoder->startWBXML();
     $this->_encoder->startTag(Horde_ActiveSync::SYNC_SYNCHRONIZE);
     $this->_encoder->startTag(Horde_ActiveSync::SYNC_STATUS);
     $this->_encoder->content(self::STATUS_SUCCESS);
     $this->_encoder->endTag();
     // Start SYNC_FOLDERS
     $this->_encoder->startTag(Horde_ActiveSync::SYNC_FOLDERS);
     // Get the exporter.
     $exporter = new Horde_ActiveSync_Connector_Exporter_Sync($this->_activeSync, $this->_encoder);
     // Loop through each collection and send all changes, replies, fetchids
     // etc...
     $cnt_global = 0;
     $over_window = false;
     foreach ($this->_collections as $id => $collection) {
         $statusCode = self::STATUS_SUCCESS;
         $changecount = 0;
         if ($over_window || $cnt_global > $this->_collections->getDefaultWindowSize()) {
             $this->_sendOverWindowResponse($collection);
             continue;
         }
         // Initialize this collection's state.
         try {
             $this->_collections->initCollectionState($collection);
         } catch (Horde_ActiveSync_Exception_StateGone $e) {
             $this->_logger->notice(sprintf('[%s] SYNC terminating, state not found', $this->_procid));
             $statusCode = self::STATUS_KEYMISM;
         } catch (Horde_ActiveSync_Exception_FolderGone $e) {
             // This isn't strictly correct, but at least some versions of
             // iOS need this in order to catch missing state.
             $this->_logger->err($e->getMessage());
             $statusCode = self::STATUS_FOLDERSYNC_REQUIRED;
         } catch (Horde_ActiveSync_Exception_StaleState $e) {
             $this->_logger->notice($e->getMessage());
             return false;
         } catch (Horde_ActiveSync_Exception $e) {
             $this->_logger->err($e->getMessage());
             return false;
         }
         // Clients are allowed to NOT request changes. We still must check
         // for them since this would otherwise screw up conflict detection
         // (we can't update sync_ts until we actually check for changes). In
         // this case, we just don't send the changes back to the client
         // until the next SYNC that does set GETCHANGES using the
         // MOREAVAILABLE mechanism.
         if (!empty($collection['importedchanges']) && empty($collection['getchanges'])) {
             $forceChanges = true;
             $collection['getchanges'] = true;
             $this->_logger->notice(sprintf('[%s] Forcing a GETCHANGES due to incoming changes.', $this->_procid));
         }
         // Check for server-side changes, if requested.
         if ($statusCode == self::STATUS_SUCCESS && !empty($collection['getchanges'])) {
             try {
                 $changecount = $this->_collections->getCollectionChangeCount();
             } catch (Horde_ActiveSync_Exception_StaleState $e) {
                 $this->_logger->err(sprintf('[%s] Force restting of state for %s: %s', $this->_procid, $id, $e->getMessage()));
                 $this->_state->loadState(array(), null, Horde_ActiveSync::REQUEST_TYPE_SYNC, $id);
                 $statusCode = self::STATUS_KEYMISM;
             } catch (Horde_ActiveSync_Exception_StateGone $e) {
                 $this->_logger->warn(sprintf('[%s] SYNCKEY not found. Reset required.', $this->_procid));
                 $statusCode = self::STATUS_KEYMISM;
             } catch (Horde_ActiveSync_Exception_FolderGone $e) {
                 $this->_logger->warn(sprintf('[%s] FOLDERSYNC required, collection gone.', $this->_procid));
                 $statusCode = self::STATUS_FOLDERSYNC_REQUIRED;
             } catch (Horde_ActiveSync_Exception_TemporaryFailure $e) {
                 $this->_logger->err(sprintf('[%s] Failure in polling for changes: "%s".', $this->_procid, $e->getMessage()));
                 $statusCode = Horde_ActiveSync_Status::SERVER_ERROR_RETRY;
             } catch (Horde_Exception_AuthenticationFailure $e) {
                 $this->_logger->err(sprintf('[%s] Lost authentication during SYNC!!', $this->_procid));
                 $statusCode = self::STATUS_SERVERERROR;
             }
         }
         // Get new synckey if needed. We need a new synckey if any of the
         // following are true:
         //    - There are any changes (incoming or outgoing).
         //    - This is the initial sync pairing of the collection.
         //    - We received a SYNC due to changes found during a PING
         //      (See Bug: 12075).
         if ($statusCode == self::STATUS_SUCCESS && (!empty($collection['importedchanges']) || !empty($changecount) || $collection['synckey'] == '0' || $this->_state->getSyncKeyCounter($collection['synckey']) == 1 || !empty($collection['fetchids']) || $this->_collections->hasPingChangeFlag($id))) {
             try {
                 $collection['newsynckey'] = $this->_state->getNewSyncKeyWrapper($collection['synckey']);
                 $this->_logger->info(sprintf('[%s] Old SYNCKEY: %s, New SYNCKEY: %s', $this->_procid, $collection['synckey'], $collection['newsynckey']));
             } catch (Horde_ActiveSync_Exception $e) {
                 $statusCode = self::STATUS_KEYMISM;
             }
         }
         // Start SYNC_FOLDER
         $this->_encoder->startTag(Horde_ActiveSync::SYNC_FOLDER);
         //SYNC_FOLDERTYPE
         $exporter->syncFolderType($collection);
         // SYNC_KEY
         $exporter->syncKey($collection);
         // SYNC_FOLDERID
         $exporter->syncFolderId($collection);
         // SYNC_STATUS
         $exporter->syncStatus($statusCode);
         if ($statusCode == self::STATUS_SUCCESS) {
             // Server changes
             if ($statusCode == self::STATUS_SUCCESS && empty($forceChanges) && !empty($collection['getchanges'])) {
                 $max_windowsize = !empty($pingSettings['maximumwindowsize']) ? min($collection['windowsize'], $pingSettings['maximumwindowsize']) : $collection['windowsize'];
                 // MOREAVAILABLE?
                 if (!empty($changecount) && ($changecount > $max_windowsize || $cnt_global + $changecount > $this->_collections->getDefaultWindowSize())) {
                     $this->_logger->info(sprintf('[%s] Sending MOREAVAILABLE. WINDOWSIZE = %d, $changecount = %d, MAX_REQUEST_WINDOWSIZE = %d, $cnt_global = %d', $this->_procid, $max_windowsize, $changecount, $this->_collections->getDefaultWindowSize(), $cnt_global));
                     $this->_encoder->startTag(Horde_ActiveSync::SYNC_MOREAVAILABLE, false, true);
                     $over_window = $cnt_global + $changecount > $this->_collections->getDefaultWindowSize();
                 }
                 // Send each message now.
                 if (!empty($changecount)) {
                     $exporter->setChanges($this->_collections->getCollectionChanges(false), $collection);
                     // Start SYNC_COMMANDS
                     $this->_encoder->startTag(Horde_ActiveSync::SYNC_COMMANDS);
                     $cnt_collection = 0;
                     while ($cnt_collection < $max_windowsize && $cnt_global < $this->_collections->getDefaultWindowSize() && ($progress = $exporter->sendNextChange())) {
                         $this->_logger->info(sprintf('[%s] Peak memory usage after message: %d', $this->_procid, memory_get_peak_usage(true)));
                         if ($progress === true) {
                             ++$cnt_collection;
                             ++$cnt_global;
                         }
                     }
                     // End SYNC_COMMANDS
                     $this->_encoder->endTag();
                 }
             }
             // Check for SYNC_REPLIES
             if (!empty($collection['clientids']) || !empty($collection['fetchids']) || !empty($collection['missing']) || !empty($collection['importfailures']) || !empty($collection['modifiedids'])) {
                 // Start SYNC_REPLIES
                 $this->_encoder->startTag(Horde_ActiveSync::SYNC_REPLIES);
                 // SYNC_MODIFY failures
                 if (!empty($collection['importfailures'])) {
                     $exporter->modifyFailures($collection);
                 }
                 // EAS 16. CHANGED responses for items that need one. This
                 // is basically the results of any AirSyncBaseAttachments
                 // actions on Appointment or Draft Email items.
                 if ($this->_device->version >= Horde_ActiveSync::VERSION_SIXTEEN && !empty($collection['modifiedids'])) {
                     $exporter->syncModifiedResponse($collection);
                 }
                 // Server IDs for new items we received from client
                 if (!empty($collection['clientids'])) {
                     $exporter->syncAddResponse($collection);
                 }
                 // Errors from missing messages in REMOVE requests.
                 if (!empty($collection['missing'])) {
                     $exporter->missingRemove($collection);
                 }
                 if (!empty($collection['fetchids'])) {
                     $exporter->fetchIds($this->_driver, $collection);
                 }
                 // End SYNC_REPLIES
                 $this->_encoder->endTag();
             }
             // Save state
             if (!empty($collection['newsynckey'])) {
                 $this->_state->setNewSyncKey($collection['newsynckey']);
                 $this->_state->save();
                 // Add the new synckey to the syncCache
                 $this->_collections->addConfirmedKey($collection['newsynckey']);
                 $this->_collections->updateCollection($collection, array('newsynckey' => true, 'unsetChanges' => true, 'unsetPingChangeFlag' => true));
             } elseif (!isset($changes)) {
                 // See if we could benefit from updating the collection's
                 // syncStamp value even though there were no changes. If
                 // $changes is set, we did a looping sync and already took
                 // care of this.
                 try {
                     $this->_state->updateSyncStamp();
                 } catch (Horde_ActiveSync_Exception $e) {
                     $this->_logger->err($e->getMessage());
                 }
             }
         }
         // End SYNC_FOLDER
         $this->_encoder->endTag();
         $this->_logger->info(sprintf('[%s] Collection output peak memory usage: %d', $this->_procid, memory_get_peak_usage(true)));
     }
     // End SYNC_FOLDERS
     $this->_encoder->endTag();
     // End SYNC_SYNCHRONIZE
     $this->_encoder->endTag();
     if ($this->_device->version >= Horde_ActiveSync::VERSION_TWELVEONE) {
         if ($this->_collections->checkStaleRequest()) {
             $this->_logger->info(sprintf('[%s] Changes detected in sync_cache during wait interval, exiting without updating cache.', $this->_procid));
             return true;
         } else {
             $this->_collections->lastsyncendnormal = time();
             $this->_collections->save(true);
         }
     } else {
         $this->_collections->save(true);
     }
     return true;
 }