Beispiel #1
0
 /**
  * Handles the Sync command
  * Performs the synchronization of messages
  *
  * @param int       $commandCode
  *
  * @access public
  * @return boolean
  */
 public function Handle($commandCode)
 {
     // Contains all requested folders (containers)
     $sc = new SyncCollections();
     $status = SYNC_STATUS_SUCCESS;
     $wbxmlproblem = false;
     $emptysync = false;
     $this->singleFolder = true;
     $this->multiFolderInfo = array();
     $this->globallyExportedItems = 0;
     // check if the hierarchySync was fully completed
     if (USE_PARTIAL_FOLDERSYNC) {
         if (self::$deviceManager->GetFolderSyncComplete() === false) {
             ZLog::Write(LOGLEVEL_INFO, "Request->HandleSync(): Sync request aborted, as exporting of folders has not yet completed");
             self::$topCollector->AnnounceInformation("Aborted due incomplete folder sync", true);
             $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED;
         } else {
             ZLog::Write(LOGLEVEL_INFO, "Request->HandleSync(): FolderSync marked as complete");
         }
     }
     // Start Synchronize
     if (self::$decoder->getElementStartTag(SYNC_SYNCHRONIZE)) {
         // AS 1.0 sends version information in WBXML
         if (self::$decoder->getElementStartTag(SYNC_VERSION)) {
             $sync_version = self::$decoder->getElementContent();
             ZLog::Write(LOGLEVEL_DEBUG, sprintf("WBXML sync version: '%s'", $sync_version));
             if (!self::$decoder->getElementEndTag()) {
                 return false;
             }
         }
         // Synching specified folders
         // Android still sends heartbeat sync even if all syncfolders are disabled.
         // Check if Folders tag is empty (<Folders/>) and only sync if there are
         // some folders in the request. See ZP-172
         $startTag = self::$decoder->getElementStartTag(SYNC_FOLDERS);
         if (isset($startTag[EN_FLAGS]) && $startTag[EN_FLAGS]) {
             while (self::$decoder->getElementStartTag(SYNC_FOLDER)) {
                 $actiondata = array();
                 $actiondata["requested"] = true;
                 $actiondata["clientids"] = array();
                 $actiondata["modifyids"] = array();
                 $actiondata["removeids"] = array();
                 $actiondata["fetchids"] = array();
                 $actiondata["statusids"] = array();
                 // read class, synckey and folderid without SyncParameters Object for now
                 $class = $synckey = $folderid = false;
                 // if there are already collections in SyncCollections, this is min. the second folder
                 if ($sc->HasCollections()) {
                     $this->singleFolder = false;
                 }
                 //for AS versions < 2.5
                 if (self::$decoder->getElementStartTag(SYNC_FOLDERTYPE)) {
                     $class = self::$decoder->getElementContent();
                     ZLog::Write(LOGLEVEL_DEBUG, sprintf("Sync folder: '%s'", $class));
                     if (!self::$decoder->getElementEndTag()) {
                         return false;
                     }
                 }
                 // SyncKey
                 if (self::$decoder->getElementStartTag(SYNC_SYNCKEY)) {
                     $synckey = "0";
                     if (($synckey = self::$decoder->getElementContent()) !== false) {
                         if (!self::$decoder->getElementEndTag()) {
                             return false;
                         }
                     }
                 } else {
                     return false;
                 }
                 // FolderId
                 if (self::$decoder->getElementStartTag(SYNC_FOLDERID)) {
                     $folderid = self::$decoder->getElementContent();
                     if (!self::$decoder->getElementEndTag()) {
                         return false;
                     }
                 }
                 // compatibility mode AS 1.0 - get folderid which was sent during GetHierarchy()
                 if (!$folderid && $class) {
                     $folderid = self::$deviceManager->GetFolderIdFromCacheByClass($class);
                 }
                 // folderid HAS TO BE known by now, so we retrieve the correct SyncParameters object for an update
                 try {
                     $spa = self::$deviceManager->GetStateManager()->GetSynchedFolderState($folderid);
                     // TODO remove resync of folders for < Z-Push 2 beta4 users
                     // this forces a resync of all states previous to Z-Push 2 beta4
                     if (!$spa instanceof SyncParameters) {
                         throw new StateInvalidException("Saved state are not of type SyncParameters");
                     }
                     // new/resync requested
                     if ($synckey == "0") {
                         $spa->RemoveSyncKey();
                         $spa->DelFolderStat();
                         $spa->SetMoveState(false);
                     } else {
                         if ($synckey !== false) {
                             if ($synckey !== $spa->GetSyncKey() && $synckey !== $spa->GetNewSyncKey() || !!$spa->GetMoveState()) {
                                 ZLog::Write(LOGLEVEL_DEBUG, "HandleSync(): Synckey does not match latest saved for this folder or there is a move state, removing folderstat to force Exporter setup");
                                 $spa->DelFolderStat();
                             }
                             $spa->SetSyncKey($synckey);
                         }
                     }
                 } catch (StateInvalidException $stie) {
                     $spa = new SyncParameters();
                     $status = SYNC_STATUS_INVALIDSYNCKEY;
                     self::$topCollector->AnnounceInformation("State invalid - Resync folder", $this->singleFolder);
                     self::$deviceManager->ForceFolderResync($folderid);
                     $this->saveMultiFolderInfo("exception", "StateInvalidException");
                 }
                 // update folderid.. this might be a new object
                 $spa->SetFolderId($folderid);
                 $spa->SetBackendFolderId(self::$deviceManager->GetBackendIdForFolderId($folderid));
                 if ($class !== false) {
                     $spa->SetContentClass($class);
                 }
                 // Get class for as versions >= 12.0
                 if (!$spa->HasContentClass()) {
                     try {
                         $spa->SetContentClass(self::$deviceManager->GetFolderClassFromCacheByID($spa->GetFolderId()));
                         ZLog::Write(LOGLEVEL_DEBUG, sprintf("GetFolderClassFromCacheByID from Device Manager: '%s' for id:'%s'", $spa->GetContentClass(), $spa->GetFolderId()));
                     } catch (NoHierarchyCacheAvailableException $nhca) {
                         $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED;
                         self::$deviceManager->ForceFullResync();
                     }
                 }
                 // done basic SPA initialization/loading -> add to SyncCollection
                 $sc->AddCollection($spa);
                 $sc->AddParameter($spa, "requested", true);
                 if ($spa->HasContentClass()) {
                     self::$topCollector->AnnounceInformation(sprintf("%s request", $spa->GetContentClass()), $this->singleFolder);
                 } else {
                     ZLog::Write(LOGLEVEL_WARN, "Not possible to determine class of request. Request did not contain class and apparently there is an issue with the HierarchyCache.");
                 }
                 // SUPPORTED properties
                 if (($se = self::$decoder->getElementStartTag(SYNC_SUPPORTED)) !== false) {
                     // ZP-481: LG phones send an empty supported tag, so only read the contents if available here
                     // if <Supported/> is received, it's as no supported fields would have been sent at all.
                     // unsure if this is the correct approach, or if in this case some default list should be used
                     if ($se[EN_FLAGS] & EN_FLAGS_CONTENT) {
                         $supfields = array();
                         WBXMLDecoder::ResetInWhile("syncSupported");
                         while (WBXMLDecoder::InWhile("syncSupported")) {
                             $el = self::$decoder->getElement();
                             if ($el[EN_TYPE] == EN_TYPE_ENDTAG) {
                                 break;
                             } else {
                                 $supfields[] = $el[EN_TAG];
                             }
                         }
                         self::$deviceManager->SetSupportedFields($spa->GetFolderId(), $supfields);
                     }
                 }
                 // Deletes as moves can be an empty tag as well as have value
                 if (self::$decoder->getElementStartTag(SYNC_DELETESASMOVES)) {
                     $spa->SetDeletesAsMoves(true);
                     if (($dam = self::$decoder->getElementContent()) !== false) {
                         $spa->SetDeletesAsMoves((bool) $dam);
                         if (!self::$decoder->getElementEndTag()) {
                             return false;
                         }
                     }
                 }
                 // Get changes can be an empty tag as well as have value
                 // code block partly contributed by dw2412
                 if (self::$decoder->getElementStartTag(SYNC_GETCHANGES)) {
                     $sc->AddParameter($spa, "getchanges", true);
                     if (($gc = self::$decoder->getElementContent()) !== false) {
                         $sc->AddParameter($spa, "getchanges", $gc);
                         if (!self::$decoder->getElementEndTag()) {
                             return false;
                         }
                     }
                 }
                 if (self::$decoder->getElementStartTag(SYNC_WINDOWSIZE)) {
                     $ws = self::$decoder->getElementContent();
                     // normalize windowsize - see ZP-477
                     if ($ws == 0 || $ws > WINDOW_SIZE_MAX) {
                         $ws = WINDOW_SIZE_MAX;
                     }
                     $spa->SetWindowSize($ws);
                     // also announce the currently requested window size to the DeviceManager
                     self::$deviceManager->SetWindowSize($spa->GetFolderId(), $spa->GetWindowSize());
                     if (!self::$decoder->getElementEndTag()) {
                         return false;
                     }
                 }
                 // conversation mode requested
                 if (self::$decoder->getElementStartTag(SYNC_CONVERSATIONMODE)) {
                     $spa->SetConversationMode(true);
                     if (($conversationmode = self::$decoder->getElementContent()) !== false) {
                         $spa->SetConversationMode((bool) $conversationmode);
                         if (!self::$decoder->getElementEndTag()) {
                             return false;
                         }
                     }
                 }
                 // Do not truncate by default
                 $spa->SetTruncation(SYNC_TRUNCATION_ALL);
                 // use default conflict handling if not specified by the mobile
                 $spa->SetConflict(SYNC_CONFLICT_DEFAULT);
                 // save the current filtertype because it might have been changed on the mobile
                 $currentFilterType = $spa->GetFilterType();
                 while (self::$decoder->getElementStartTag(SYNC_OPTIONS)) {
                     $firstOption = true;
                     WBXMLDecoder::ResetInWhile("syncOptions");
                     while (WBXMLDecoder::InWhile("syncOptions")) {
                         // foldertype definition
                         if (self::$decoder->getElementStartTag(SYNC_FOLDERTYPE)) {
                             $foldertype = self::$decoder->getElementContent();
                             ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): specified options block with foldertype '%s'", $foldertype));
                             // switch the foldertype for the next options
                             $spa->UseCPO($foldertype);
                             // save the current filtertype because it might have been changed on the mobile
                             $currentFilterType = $spa->GetFilterType();
                             // set to synchronize all changes. The mobile could overwrite this value
                             $spa->SetFilterType(SYNC_FILTERTYPE_ALL);
                             if (!self::$decoder->getElementEndTag()) {
                                 return false;
                             }
                         } else {
                             if ($firstOption) {
                                 $spa->UseCPO();
                                 // save the current filtertype because it might have been changed on the mobile
                                 $currentFilterType = $spa->GetFilterType();
                                 // set to synchronize all changes. The mobile could overwrite this value
                                 $spa->SetFilterType(SYNC_FILTERTYPE_ALL);
                             }
                         }
                         $firstOption = false;
                         if (self::$decoder->getElementStartTag(SYNC_FILTERTYPE)) {
                             $spa->SetFilterType(self::$decoder->getElementContent());
                             if (!self::$decoder->getElementEndTag()) {
                                 return false;
                             }
                         }
                         if (self::$decoder->getElementStartTag(SYNC_TRUNCATION)) {
                             $spa->SetTruncation(self::$decoder->getElementContent());
                             if (!self::$decoder->getElementEndTag()) {
                                 return false;
                             }
                         }
                         if (self::$decoder->getElementStartTag(SYNC_RTFTRUNCATION)) {
                             $spa->SetRTFTruncation(self::$decoder->getElementContent());
                             if (!self::$decoder->getElementEndTag()) {
                                 return false;
                             }
                         }
                         if (self::$decoder->getElementStartTag(SYNC_MIMESUPPORT)) {
                             $spa->SetMimeSupport(self::$decoder->getElementContent());
                             if (!self::$decoder->getElementEndTag()) {
                                 return false;
                             }
                         }
                         if (self::$decoder->getElementStartTag(SYNC_MIMETRUNCATION)) {
                             $spa->SetMimeTruncation(self::$decoder->getElementContent());
                             if (!self::$decoder->getElementEndTag()) {
                                 return false;
                             }
                         }
                         if (self::$decoder->getElementStartTag(SYNC_CONFLICT)) {
                             $spa->SetConflict(self::$decoder->getElementContent());
                             if (!self::$decoder->getElementEndTag()) {
                                 return false;
                             }
                         }
                         while (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_BODYPREFERENCE)) {
                             if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TYPE)) {
                                 $bptype = self::$decoder->getElementContent();
                                 $spa->BodyPreference($bptype);
                                 if (!self::$decoder->getElementEndTag()) {
                                     return false;
                                 }
                             }
                             if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TRUNCATIONSIZE)) {
                                 $spa->BodyPreference($bptype)->SetTruncationSize(self::$decoder->getElementContent());
                                 if (!self::$decoder->getElementEndTag()) {
                                     return false;
                                 }
                             }
                             if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_ALLORNONE)) {
                                 $spa->BodyPreference($bptype)->SetAllOrNone(self::$decoder->getElementContent());
                                 if (!self::$decoder->getElementEndTag()) {
                                     return false;
                                 }
                             }
                             if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_PREVIEW)) {
                                 $spa->BodyPreference($bptype)->SetPreview(self::$decoder->getElementContent());
                                 if (!self::$decoder->getElementEndTag()) {
                                     return false;
                                 }
                             }
                             if (!self::$decoder->getElementEndTag()) {
                                 return false;
                             }
                         }
                         $e = self::$decoder->peek();
                         if ($e[EN_TYPE] == EN_TYPE_ENDTAG) {
                             self::$decoder->getElementEndTag();
                             break;
                         }
                     }
                 }
                 // limit items to be synchronized to the mobiles if configured
                 if (defined('SYNC_FILTERTIME_MAX') && SYNC_FILTERTIME_MAX > SYNC_FILTERTYPE_ALL && (!$spa->HasFilterType() || $spa->GetFilterType() == SYNC_FILTERTYPE_ALL || $spa->GetFilterType() > SYNC_FILTERTIME_MAX)) {
                     ZLog::Write(LOGLEVEL_DEBUG, sprintf("SYNC_FILTERTIME_MAX defined. Filter set to value: %s", SYNC_FILTERTIME_MAX));
                     $spa->SetFilterType(SYNC_FILTERTIME_MAX);
                 }
                 // unset filtertype for KOE GAB folder
                 if (KOE_CAPABILITY_GAB && self::$deviceManager->IsKoe() && $spa->GetBackendFolderId() == self::$deviceManager->GetKoeGabBackendFolderId()) {
                     $spa->SetFilterType(SYNC_FILTERTYPE_ALL);
                     ZLog::Write(LOGLEVEL_DEBUG, "HandleSync(): KOE GAB folder - setting filter type to unlimited");
                 }
                 if ($currentFilterType != $spa->GetFilterType()) {
                     ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): filter type has changed (old: '%s', new: '%s'), removing folderstat to force Exporter setup", $currentFilterType, $spa->GetFilterType()));
                     $spa->DelFolderStat();
                 }
                 // Check if the hierarchycache is available. If not, trigger a HierarchySync
                 if (self::$deviceManager->IsHierarchySyncRequired()) {
                     $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED;
                     ZLog::Write(LOGLEVEL_DEBUG, "HierarchyCache is also not available. Triggering HierarchySync to device");
                 }
                 if (($el = self::$decoder->getElementStartTag(SYNC_PERFORM)) && $el[EN_FLAGS] & EN_FLAGS_CONTENT) {
                     // We can not proceed here as the content class is unknown
                     if ($status != SYNC_STATUS_SUCCESS) {
                         ZLog::Write(LOGLEVEL_WARN, "Ignoring all incoming actions as global status indicates problem.");
                         $wbxmlproblem = true;
                         break;
                     }
                     $performaction = true;
                     // unset the importer
                     $this->importer = false;
                     $nchanges = 0;
                     WBXMLDecoder::ResetInWhile("syncActions");
                     while (WBXMLDecoder::InWhile("syncActions")) {
                         // ADD, MODIFY, REMOVE or FETCH
                         $element = self::$decoder->getElement();
                         if ($element[EN_TYPE] != EN_TYPE_STARTTAG) {
                             self::$decoder->ungetElement($element);
                             break;
                         }
                         if ($status == SYNC_STATUS_SUCCESS) {
                             $nchanges++;
                         }
                         // Foldertype sent when synching SMS
                         if (self::$decoder->getElementStartTag(SYNC_FOLDERTYPE)) {
                             $foldertype = self::$decoder->getElementContent();
                             ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): incoming data with foldertype '%s'", $foldertype));
                             if (!self::$decoder->getElementEndTag()) {
                                 return false;
                             }
                         } else {
                             $foldertype = false;
                         }
                         $serverid = false;
                         if (self::$decoder->getElementStartTag(SYNC_SERVERENTRYID)) {
                             if (($serverid = self::$decoder->getElementContent()) !== false) {
                                 if (!self::$decoder->getElementEndTag()) {
                                     // end serverid
                                     return false;
                                 }
                             }
                         }
                         if (self::$decoder->getElementStartTag(SYNC_CLIENTENTRYID)) {
                             $clientid = self::$decoder->getElementContent();
                             if (!self::$decoder->getElementEndTag()) {
                                 // end clientid
                                 return false;
                             }
                         } else {
                             $clientid = false;
                         }
                         // Get the SyncMessage if sent
                         if (($el = self::$decoder->getElementStartTag(SYNC_DATA)) && $el[EN_FLAGS] & EN_FLAGS_CONTENT) {
                             $message = ZPush::getSyncObjectFromFolderClass($spa->GetContentClass());
                             // KOE ZO-42: OL sends Notes as Appointments
                             if ($spa->GetContentClass() == "Notes" && KOE_CAPABILITY_NOTES && self::$deviceManager->IsKoe()) {
                                 ZLog::Write(LOGLEVEL_DEBUG, "HandleSync(): KOE sends Notes as Appointments, read as SyncAppointment and convert it into a SyncNote object.");
                                 $message = new SyncAppointment();
                                 $message->Decode(self::$decoder);
                                 $note = new SyncNote();
                                 if (isset($message->asbody)) {
                                     $note->asbody = $message->asbody;
                                 }
                                 if (isset($message->categories)) {
                                     $note->categories = $message->categories;
                                 }
                                 if (isset($message->subject)) {
                                     $note->subject = $message->subject;
                                 }
                                 if (isset($message->dtstamp)) {
                                     $note->lastmodified = $message->dtstamp;
                                 }
                                 // set SyncNote->Color from a color category
                                 $note->SetColorFromCategory();
                                 $message = $note;
                             } else {
                                 $message->Decode(self::$decoder);
                                 // set Ghosted fields
                                 $message->emptySupported(self::$deviceManager->GetSupportedFields($spa->GetFolderId()));
                             }
                             if (!self::$decoder->getElementEndTag()) {
                                 // end applicationdata
                                 return false;
                             }
                         } else {
                             $message = false;
                         }
                         switch ($element[EN_TAG]) {
                             case SYNC_FETCH:
                                 array_push($actiondata["fetchids"], $serverid);
                                 break;
                             default:
                                 // get the importer
                                 if ($this->importer == false) {
                                     $status = $this->getImporter($sc, $spa, $actiondata);
                                 }
                                 if ($status == SYNC_STATUS_SUCCESS) {
                                     $this->importMessage($spa, $actiondata, $element[EN_TAG], $message, $clientid, $serverid, $foldertype, $nchanges);
                                 } else {
                                     ZLog::Write(LOGLEVEL_WARN, "Ignored incoming change, global status indicates problem.");
                                 }
                                 break;
                         }
                         if ($actiondata["fetchids"]) {
                             self::$topCollector->AnnounceInformation(sprintf("Fetching %d", $nchanges));
                         } else {
                             self::$topCollector->AnnounceInformation(sprintf("Incoming %d", $nchanges));
                         }
                         if (!self::$decoder->getElementEndTag()) {
                             // end add/change/delete/move
                             return false;
                         }
                     }
                     if ($status == SYNC_STATUS_SUCCESS && $this->importer !== false) {
                         ZLog::Write(LOGLEVEL_INFO, sprintf("Processed '%d' incoming changes", $nchanges));
                         if (!$actiondata["fetchids"]) {
                             self::$topCollector->AnnounceInformation(sprintf("%d incoming", $nchanges), $this->singleFolder);
                             $this->saveMultiFolderInfo("incoming", $nchanges);
                         }
                         try {
                             // Save the updated state, which is used for the exporter later
                             $sc->AddParameter($spa, "state", $this->importer->GetState());
                         } catch (StatusException $stex) {
                             $status = $stex->getCode();
                         }
                     }
                     if (!self::$decoder->getElementEndTag()) {
                         // end PERFORM
                         return false;
                     }
                 }
                 // save the failsave state
                 if (!empty($actiondata["statusids"])) {
                     unset($actiondata["failstate"]);
                     $actiondata["failedsyncstate"] = $sc->GetParameter($spa, "state");
                     self::$deviceManager->GetStateManager()->SetSyncFailState($actiondata);
                 }
                 // save actiondata
                 $sc->AddParameter($spa, "actiondata", $actiondata);
                 if (!self::$decoder->getElementEndTag()) {
                     // end collection
                     return false;
                 }
                 // AS14 does not send GetChanges anymore. We should do it if there were no incoming changes
                 if (!isset($performaction) && !$sc->GetParameter($spa, "getchanges") && $spa->HasSyncKey()) {
                     $sc->AddParameter($spa, "getchanges", true);
                 }
             }
             // END FOLDER
             if (!$wbxmlproblem && !self::$decoder->getElementEndTag()) {
                 // end collections
                 return false;
             }
         }
         // end FOLDERS
         if (self::$decoder->getElementStartTag(SYNC_HEARTBEATINTERVAL)) {
             $hbinterval = self::$decoder->getElementContent();
             if (!self::$decoder->getElementEndTag()) {
                 // SYNC_HEARTBEATINTERVAL
                 return false;
             }
         }
         if (self::$decoder->getElementStartTag(SYNC_WAIT)) {
             $wait = self::$decoder->getElementContent();
             if (!self::$decoder->getElementEndTag()) {
                 // SYNC_WAIT
                 return false;
             }
             // internally the heartbeat interval and the wait time are the same
             // heartbeat is in seconds, wait in minutes
             $hbinterval = $wait * 60;
         }
         if (self::$decoder->getElementStartTag(SYNC_WINDOWSIZE)) {
             $sc->SetGlobalWindowSize(self::$decoder->getElementContent());
             ZLog::Write(LOGLEVEL_DEBUG, "Sync(): Global WindowSize requested: " . $sc->GetGlobalWindowSize());
             if (!self::$decoder->getElementEndTag()) {
                 // SYNC_WINDOWSIZE
                 return false;
             }
         }
         if (self::$decoder->getElementStartTag(SYNC_PARTIAL)) {
             $partial = true;
         } else {
             $partial = false;
         }
         if (!$wbxmlproblem && !self::$decoder->getElementEndTag()) {
             // end sync
             return false;
         }
     } else {
         $emptysync = true;
     }
     // END SYNCHRONIZE
     // check heartbeat/wait time
     if (isset($hbinterval)) {
         if ($hbinterval < 60 || $hbinterval > 3540) {
             $status = SYNC_STATUS_INVALIDWAITORHBVALUE;
             ZLog::Write(LOGLEVEL_WARN, sprintf("HandleSync(): Invalid heartbeat or wait value '%s'", $hbinterval));
         }
     }
     // Partial & Empty Syncs need saved data to proceed with synchronization
     if ($status == SYNC_STATUS_SUCCESS && ($emptysync === true || $partial === true)) {
         ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Partial or Empty sync requested. Retrieving data of synchronized folders."));
         // Load all collections - do not overwrite existing (received!), load states, check permissions and only load confirmed states!
         try {
             $sc->LoadAllCollections(false, true, true, true, true);
         } catch (StateInvalidException $siex) {
             $status = SYNC_STATUS_INVALIDSYNCKEY;
             self::$topCollector->AnnounceInformation("StateNotFoundException", $this->singleFolder);
             $this->saveMultiFolderInfo("exeption", "StateNotFoundException");
         } catch (StatusException $stex) {
             $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED;
             self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), $this->singleFolder);
             $this->saveMultiFolderInfo("exeption", "StatusException");
         }
         // update a few values
         foreach ($sc as $folderid => $spa) {
             // manually set getchanges parameter for this collection if it is synchronized
             if ($spa->HasSyncKey()) {
                 $sc->AddParameter($spa, "getchanges", true);
                 // announce WindowSize to DeviceManager
                 self::$deviceManager->SetWindowSize($folderid, $spa->GetWindowSize());
             }
         }
         if (!$sc->HasCollections()) {
             $status = SYNC_STATUS_SYNCREQUESTINCOMPLETE;
         }
     } else {
         if (isset($hbinterval)) {
             // load the hierarchy data - there are no permissions to verify so we just set it to false
             if (!$sc->LoadCollection(false, true, false)) {
                 $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED;
                 self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), $this->singleFolder);
                 $this->saveMultiFolderInfo("exeption", "StatusException");
             }
         }
     }
     // HEARTBEAT & Empty sync
     if ($status == SYNC_STATUS_SUCCESS && (isset($hbinterval) || $emptysync == true)) {
         $interval = defined('PING_INTERVAL') && PING_INTERVAL > 0 ? PING_INTERVAL : 30;
         if (isset($hbinterval)) {
             $sc->SetLifetime($hbinterval);
         }
         // states are lazy loaded - we have to make sure that they are there!
         $loadstatus = SYNC_STATUS_SUCCESS;
         foreach ($sc as $folderid => $spa) {
             // some androids do heartbeat on the OUTBOX folder, with weird results - ZP-362
             // we do not load the state so we will never get relevant changes on the OUTBOX folder
             if (self::$deviceManager->GetFolderTypeFromCacheById($folderid) == SYNC_FOLDER_TYPE_OUTBOX) {
                 ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Heartbeat on Outbox folder not allowed"));
                 continue;
             }
             $fad = array();
             // if loading the states fails, we do not enter heartbeat, but we keep $status on SYNC_STATUS_SUCCESS
             // so when the changes are exported the correct folder gets an SYNC_STATUS_INVALIDSYNCKEY
             if ($loadstatus == SYNC_STATUS_SUCCESS) {
                 $loadstatus = $this->loadStates($sc, $spa, $fad);
             }
         }
         if ($loadstatus == SYNC_STATUS_SUCCESS) {
             $foundchanges = false;
             try {
                 // always check for changes
                 ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Entering Heartbeat mode"));
                 $foundchanges = $sc->CheckForChanges($sc->GetLifetime(), $interval);
             } catch (StatusException $stex) {
                 if ($stex->getCode() == SyncCollections::OBSOLETE_CONNECTION) {
                     $status = SYNC_COMMONSTATUS_SYNCSTATEVERSIONINVALID;
                 } else {
                     $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED;
                     self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), $this->singleFolder);
                     $this->saveMultiFolderInfo("exeption", "StatusException");
                 }
             }
             // in case there are no changes and no other request has synchronized while we waited, we can reply with an empty response
             if (!$foundchanges && $status == SYNC_STATUS_SUCCESS) {
                 // if there were changes to the SPA or CPOs we need to save this before we terminate
                 // only save if the state was not modified by some other request, if so, return state invalid status
                 foreach ($sc as $folderid => $spa) {
                     if (self::$deviceManager->CheckHearbeatStateIntegrity($spa->GetFolderId(), $spa->GetUuid(), $spa->GetUuidCounter())) {
                         $status = SYNC_COMMONSTATUS_SYNCSTATEVERSIONINVALID;
                     } else {
                         $sc->SaveCollection($spa);
                     }
                 }
                 if ($status == SYNC_STATUS_SUCCESS) {
                     ZLog::Write(LOGLEVEL_DEBUG, "No changes found and no other process changed states. Replying with empty response and closing connection.");
                     self::$specialHeaders = array();
                     self::$specialHeaders[] = "Connection: close";
                     return true;
                 }
             }
             if ($foundchanges) {
                 foreach ($sc->GetChangedFolderIds() as $folderid => $changecount) {
                     // check if there were other sync requests for a folder during the heartbeat
                     $spa = $sc->GetCollection($folderid);
                     if ($changecount > 0 && $sc->WaitedForChanges() && self::$deviceManager->CheckHearbeatStateIntegrity($spa->GetFolderId(), $spa->GetUuid(), $spa->GetUuidCounter())) {
                         ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): heartbeat: found %d changes in '%s' which was already synchronized. Heartbeat aborted!", $changecount, $folderid));
                         $status = SYNC_COMMONSTATUS_SYNCSTATEVERSIONINVALID;
                     } else {
                         ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): heartbeat: found %d changes in '%s'", $changecount, $folderid));
                     }
                 }
             }
         }
     }
     // Start the output
     ZLog::Write(LOGLEVEL_DEBUG, "HandleSync(): Start Output");
     // global status
     // SYNC_COMMONSTATUS_* start with values from 101
     if ($status != SYNC_COMMONSTATUS_SUCCESS && ($status == SYNC_STATUS_FOLDERHIERARCHYCHANGED || $status > 100)) {
         $this->sendStartTags();
         self::$encoder->startTag(SYNC_STATUS);
         self::$encoder->content($status);
         self::$encoder->endTag();
         self::$encoder->endTag();
         // SYNC_SYNCHRONIZE
         return true;
     }
     // Loop through requested folders
     foreach ($sc as $folderid => $spa) {
         // get actiondata
         $actiondata = $sc->GetParameter($spa, "actiondata");
         if ($status == SYNC_STATUS_SUCCESS && (!$spa->GetContentClass() || !$spa->GetFolderId())) {
             ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): no content class or folderid found for collection."));
             continue;
         }
         if (!$sc->GetParameter($spa, "requested")) {
             ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): partial sync for folder class '%s' with id '%s'", $spa->GetContentClass(), $spa->GetFolderId()));
         }
         // initialize exporter to get changecount
         $changecount = false;
         $exporter = false;
         $streamimporter = false;
         $newFolderStat = false;
         $setupExporter = true;
         // TODO we could check against $sc->GetChangedFolderIds() on heartbeat so we do not need to configure all exporter again
         if ($status == SYNC_STATUS_SUCCESS && ($sc->GetParameter($spa, "getchanges") || !$spa->HasSyncKey())) {
             // no need to run the exporter if the globalwindowsize is already full
             if ($sc->GetGlobalWindowSize() == $this->globallyExportedItems) {
                 ZLog::Write(LOGLEVEL_DEBUG, sprintf("Sync(): no exporter setup for '%s' as GlobalWindowSize is full.", $spa->GetFolderId()));
                 $setupExporter = false;
             }
             // if the maximum request timeout is reached, stop processing other collections
             if (Request::IsRequestTimeoutReached()) {
                 ZLog::Write(LOGLEVEL_DEBUG, sprintf("Sync(): no exporter setup for '%s' as request timeout reached, omitting output for collection.", $spa->GetFolderId()));
                 $setupExporter = false;
             }
             // compare the folder statistics if the backend supports this
             if ($setupExporter && self::$backend->HasFolderStats()) {
                 // check if the folder stats changed -> if not, don't setup the exporter, there are no changes!
                 $newFolderStat = self::$backend->GetFolderStat(ZPush::GetAdditionalSyncFolderStore($spa->GetBackendFolderId()), $spa->GetBackendFolderId());
                 if ($newFolderStat !== false && !$spa->IsExporterRunRequired($newFolderStat, true)) {
                     $changecount = 0;
                     $setupExporter = false;
                 }
             }
             // Do a full Exporter setup if we can't avoid it
             if ($setupExporter) {
                 //make sure the states are loaded
                 $status = $this->loadStates($sc, $spa, $actiondata);
                 if ($status == SYNC_STATUS_SUCCESS) {
                     try {
                         // if this is an additional folder the backend has to be setup correctly
                         if (!self::$backend->Setup(ZPush::GetAdditionalSyncFolderStore($spa->GetBackendFolderId()))) {
                             throw new StatusException(sprintf("HandleSync() could not Setup() the backend for folder id %s/%s", $spa->GetFolderId(), $spa->GetBackendFolderId()), SYNC_STATUS_FOLDERHIERARCHYCHANGED);
                         }
                         // Use the state from the importer, as changes may have already happened
                         $exporter = self::$backend->GetExporter($spa->GetBackendFolderId());
                         if ($exporter === false) {
                             throw new StatusException(sprintf("HandleSync() could not get an exporter for folder id %s/%s", $spa->GetFolderId(), $spa->GetBackendFolderId()), SYNC_STATUS_FOLDERHIERARCHYCHANGED);
                         }
                     } catch (StatusException $stex) {
                         $status = $stex->getCode();
                     }
                     try {
                         // Stream the messages directly to the PDA
                         $streamimporter = new ImportChangesStream(self::$encoder, ZPush::getSyncObjectFromFolderClass($spa->GetContentClass()));
                         if ($exporter !== false) {
                             $exporter->SetMoveStates($spa->GetMoveState());
                             $exporter->Config($sc->GetParameter($spa, "state"));
                             $exporter->ConfigContentParameters($spa->GetCPO());
                             $exporter->InitializeExporter($streamimporter);
                             $changecount = $exporter->GetChangeCount();
                         }
                     } catch (StatusException $stex) {
                         if ($stex->getCode() === SYNC_FSSTATUS_CODEUNKNOWN && $spa->HasSyncKey()) {
                             $status = SYNC_STATUS_INVALIDSYNCKEY;
                         } else {
                             $status = $stex->getCode();
                         }
                     }
                     if (!$spa->HasSyncKey()) {
                         self::$topCollector->AnnounceInformation(sprintf("Exporter registered. %d objects queued.", $changecount), $this->singleFolder);
                         $this->saveMultiFolderInfo("queued", $changecount);
                         // update folder status as initialized
                         $spa->SetFolderSyncTotal($changecount);
                         $spa->SetFolderSyncRemaining($changecount);
                         if ($changecount > 0) {
                             self::$deviceManager->SetFolderSyncStatus($folderid, DeviceManager::FLD_SYNC_INITIALIZED);
                         }
                     } else {
                         if ($status != SYNC_STATUS_SUCCESS) {
                             self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), $this->singleFolder);
                             $this->saveMultiFolderInfo("exception", "StatusException");
                         }
                     }
                 }
             }
         }
         // Get a new sync key to output to the client if any changes have been send by the mobile or a new synckey is to be sent
         if (!empty($actiondata["modifyids"]) || !empty($actiondata["clientids"]) || !empty($actiondata["removeids"]) || !$spa->HasSyncKey() && $status == SYNC_STATUS_SUCCESS) {
             $spa->SetNewSyncKey(self::$deviceManager->GetStateManager()->GetNewSyncKey($spa->GetSyncKey()));
         } else {
             // when reaching the global limit for changes of all collections, stop processing other collections (ZP-697)
             if ($sc->GetGlobalWindowSize() <= $this->globallyExportedItems) {
                 ZLog::Write(LOGLEVEL_DEBUG, "Global WindowSize for amount of exported changes reached, omitting output for collection.");
                 continue;
             }
             // get a new synckey if there are changes are we did not reach the limit yet
             if ($changecount > 0) {
                 $spa->SetNewSyncKey(self::$deviceManager->GetStateManager()->GetNewSyncKey($spa->GetSyncKey()));
             }
         }
         // Fir AS 14.0+ omit output for folder, if there were no incoming or outgoing changes and no Fetch
         if (Request::GetProtocolVersion() >= 14.0 && !$spa->HasNewSyncKey() && $changecount == 0 && empty($actiondata["fetchids"]) && $status == SYNC_STATUS_SUCCESS && ($newFolderStat === false || !$spa->IsExporterRunRequired($newFolderStat))) {
             ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync: No changes found for %s folder id '%s'. Omitting output.", $spa->GetContentClass(), $spa->GetFolderId()));
             continue;
         }
         // if there are no other responses sent, we should end with a global status
         if ($status == SYNC_STATUS_FOLDERHIERARCHYCHANGED && $this->startTagsSent === false) {
             $this->sendStartTags();
             self::$encoder->startTag(SYNC_STATUS);
             self::$encoder->content($status);
             self::$encoder->endTag();
             self::$encoder->endTag();
             // SYNC_SYNCHRONIZE
             return true;
         }
         // there is something to send here, sync folder to output
         $this->syncFolder($sc, $spa, $exporter, $changecount, $streamimporter, $status, $newFolderStat);
         // reset status for the next folder
         $status = SYNC_STATUS_SUCCESS;
     }
     // END foreach collection
     //SYNC_FOLDERS - only if the starttag was sent
     if ($this->startFolderTagSent) {
         self::$encoder->endTag();
     }
     //SYNC_SYNCHRONIZE - only if the starttag was sent
     if ($this->startTagsSent) {
         self::$encoder->endTag();
     }
     // final top announcement for a multi-folder sync
     if ($sc->GetCollectionCount() > 1) {
         self::$topCollector->AnnounceInformation($this->getMultiFolderInfoLine($sc->GetCollectionCount()), true);
     }
     return true;
 }
function HandleSync($backend, $protocolversion, $devid)
{
    global $zpushdtd;
    global $input, $output;
    global $user, $auth_pw;
    global $sessionstarttime;
    // Contains all containers requested
    $collections = array();
    // Init WBXML decoder
    $decoder = new WBXMLDecoder($input, $zpushdtd);
    // Init state machine
    $statemachine = new StateMachine($devid, $user);
    // Start decode
    $shortsyncreq = false;
    $fetchitems = false;
    $dataimported = false;
    $dataavailable = false;
    $partial = false;
    $maxcacheage = 960;
    // 15 Minutes + 1 to store it long enough for Device being connected to ActiveSync PC.
    $SyncStatus = SYNC_STATUS_SUCCESS;
    // AS14 over all SyncStatus
    if (!$decoder->getElementStartTag(SYNC_SYNCHRONIZE)) {
        if ($protocolversion >= 12.1) {
            if (!($SyncCache = unserialize($statemachine->getSyncCache())) || !isset($SyncCache['collections'])) {
                _HandleSyncError(SYNC_STATUS_REQUEST_INCOMPLETE);
                debugLog("HandleSync: Empty Sync request and no SyncCache or SyncCache without collections. " . "(SyncCache[lastuntil]+" . $maxcacheage . "=" . ($SyncCache['lastuntil'] + $maxcacheage) . ", " . "Time now" . time() . ", " . "SyncCache[collections]=" . (isset($SyncCache['collections']) ? "Yes" : "No") . ", " . "SyncCache array=" . (is_array($SyncCache) ? "Yes" : "No") . ") " . "(STATUS = " . SYNC_STATUS_REQUEST_INCOMPLETE . ")");
                return true;
            } else {
                if (sizeof($SyncCache['confirmed_synckeys']) > 0) {
                    debugLog("HandleSync: We have unconfirmed sync keys but during short request. Enforce full Sync Request (STATUS = " . SYNC_STATUS_REQUEST_INCOMPLETE . ")");
                    _HandleSyncError(SYNC_STATUS_REQUEST_INCOMPLETE);
                    return true;
                }
                $shortsyncreq = true;
                $SyncCache['timestamp'] = time();
                $statemachine->setSyncCache(serialize($SyncCache));
                debugLog("HandleSync: Empty Sync request and taken info from SyncCache.");
                $collections = array();
                foreach ($SyncCache['collections'] as $key => $value) {
                    $collection = $value;
                    $collection['collectionid'] = $key;
                    if (isset($collection['synckey'])) {
                        $collection['onlyoptionbodypreference'] = $protocolversion >= 14.0 && (!isset($collection["BodyPreference"][1]) && !isset($collection["BodyPreference"][2]) && !isset($collection["BodyPreference"][3]) && !isset($collection["BodyPreference"][4]));
                        $collection['syncstate'] = $statemachine->getSyncState($collection['synckey']);
                        if (isset($collection['optionfoldertype'])) {
                            $collection[$collection['optionfoldertype'] . 'syncstate'] = $statemachine->getSyncState($collection['optionfoldertype'] . $collection['synckey']);
                        }
                        if ($collection['synckey'] == "0") {
                            $msginfos[$key] = array();
                        } else {
                            $msginfos[$key] = unserialize($statemachine->getSyncState("mi" . $collection['synckey']));
                        }
                        array_push($collections, $collection);
                    }
                }
                if (count($collections) == 0) {
                    debugLog("HandleSync: Don't have any collections. Enforce full request. (STATUS = " . SYNC_STATUS_REQUEST_INCOMPLETE . ")");
                    _HandleSyncError(SYNC_STATUS_REQUEST_INCOMPLETE);
                    return true;
                }
            }
        } else {
            _HandleSyncError(SYNC_STATUS_REQUEST_INCOMPLETE);
            debugLog("HandleSync: Empty Sync request and protocolversion < 12.1 (STATUS = " . SYNC_STATUS_REQUEST_INCOMPLETE . ")");
            return true;
        }
    } else {
        if (!isset($SyncCache)) {
            $SyncCache = unserialize($statemachine->getSyncCache());
        }
        // Just to update the timestamp...
        $SyncCache['timestamp'] = time();
        // Check if time of last sync is too long ago (but only in case we don't expect a full request!)
        $statemachine->setSyncCache(serialize($SyncCache));
        $SyncCache['wait'] = false;
        $SyncCache['hbinterval'] = false;
        while (($synctag = $decoder->getElementStartTag(SYNC_MAXITEMS) ? SYNC_MAXITEMS : ($decoder->getElementStartTag(SYNC_FOLDERS) ? SYNC_FOLDERS : ($decoder->getElementStartTag(SYNC_PARTIAL) ? SYNC_PARTIAL : ($decoder->getElementStartTag(SYNC_WAIT) ? SYNC_WAIT : ($decoder->getElementStartTag(SYNC_HEARTBEATINTERVAL) ? SYNC_HEARTBEATINTERVAL : -1))))) != -1) {
            switch ($synctag) {
                case SYNC_HEARTBEATINTERVAL:
                    if ($SyncCache['hbinterval'] = $decoder->getElementContent()) {
                        $decoder->getElementEndTag();
                    }
                    debugLog('HandleSync: Got Heartbeat Interval Sync (' . $SyncCache['hbinterval'] . ' Seconds)');
                    if ($SyncCache['hbinterval'] > REAL_SCRIPT_TIMEOUT - 600) {
                        _HandleSyncError(SYNC_STATUS_INVALID_WAIT_HEARTBEATINTERVAL, REAL_SCRIPT_TIMEOUT - 600);
                        debugLog('HandleSync: HeartbeatInterval larger than ' . (REAL_SCRIPT_TIMEOUT - 600) . ' Seconds. This violates the protocol spec. (STATUS = ".SYNC_STATUS_INVALID_WAIT_HEARTBEATINTERVAL.", LIMIT = ' . (REAL_SCRIPT_TIMEOUT - 600) . ')');
                        return true;
                    }
                    break;
                case SYNC_WAIT:
                    if ($SyncCache['wait'] = $decoder->getElementContent()) {
                        $decoder->getElementEndTag();
                    }
                    debugLog('HandleSync: Got Wait Sync (' . $SyncCache['wait'] . ' Minutes)');
                    if ($SyncCache['wait'] > (REAL_SCRIPT_TIMEOUT - 600) / 60) {
                        _HandleSyncError(SYNC_STATUS_INVALID_WAIT_HEARTBEATINTERVAL, (REAL_SCRIPT_TIMEOUT - 600) / 60);
                        debugLog('HandleSync: Wait larger than ' . (REAL_SCRIPT_TIMEOUT - 600) / 60 . ' Minutes. This violates the protocol spec. (STATUS = ".SYNC_STATUS_INVALID_WAIT_HEARTBEATINTERVAL.", LIMIT = ' . (REAL_SCRIPT_TIMEOUT - 600) / 60 . ')');
                        return true;
                    }
                    break;
                case SYNC_PARTIAL:
                    if ($decoder->getElementContent(SYNC_PARTIAL)) {
                        $decoder->getElementEndTag();
                    }
                    $partial = true;
                    break;
                case SYNC_MAXITEMS:
                    //					_HandleSyncError("12");
                    //					return true;
                    // Sending Max Items outside a collection is invalid according to specs...
                    $default_maxitems = $decoder->getElementContent();
                    if (!$decoder->getElementEndTag()) {
                        return false;
                    }
                    break;
                case SYNC_FOLDERS:
                    $dataimported = false;
                    while ($decoder->getElementStartTag(SYNC_FOLDER)) {
                        $collection = array();
                        // Intializing the collection
                        $collection['clientids'] = array();
                        $collection['fetchids'] = array();
                        $msginfo = array();
                        // set default truncation value
                        $collection['truncation'] = SYNC_TRUNCATION_ALL;
                        // set default conflict behavior from config if the device doesn't send a conflict resolution parameter
                        $collection['conflict'] = SYNC_CONFLICT_DEFAULT;
                        $collection['onlyoptionbodypreference'] = false;
                        $collection['RightsManagementSupport'] = false;
                        while (($foldertag = $decoder->getElementStartTag(SYNC_FOLDERTYPE) ? SYNC_FOLDERTYPE : ($decoder->getElementStartTag(SYNC_SYNCKEY) ? SYNC_SYNCKEY : ($decoder->getElementStartTag(SYNC_FOLDERID) ? SYNC_FOLDERID : ($decoder->getElementStartTag(SYNC_MAXITEMS) ? SYNC_MAXITEMS : ($decoder->getElementStartTag(SYNC_SUPPORTED) ? SYNC_SUPPORTED : ($decoder->getElementStartTag(SYNC_CONVERSATIONMODE) ? SYNC_CONVERSATIONMODE : ($decoder->getElementStartTag(SYNC_DELETESASMOVES) ? SYNC_DELETESASMOVES : ($decoder->getElementStartTag(SYNC_GETCHANGES) ? SYNC_GETCHANGES : ($decoder->getElementStartTag(SYNC_OPTIONS) ? SYNC_OPTIONS : ($decoder->getElementStartTag(SYNC_PERFORM) ? SYNC_PERFORM : -1)))))))))) != -1) {
                            switch ($foldertag) {
                                case SYNC_SYNCKEY:
                                    $collection["synckey"] = $decoder->getElementContent();
                                    if (!$decoder->getElementEndTag()) {
                                        return false;
                                    }
                                    // Get our sync state for this collection
                                    $collection["syncstate"] = $statemachine->getSyncState($collection["synckey"]);
                                    if ($collection['synckey'] != "0") {
                                        $msginfo = unserialize($statemachine->getSyncState("mi" . $collection['synckey']));
                                    }
                                    if (($delstatus = $statemachine->cleanOldSyncState($collection["synckey"])) !== true) {
                                        _HandleSyncError(abs($delstatus));
                                        return true;
                                    }
                                    $statemachine->cleanOldSyncState("mi" . $collection["synckey"]);
                                    if (is_numeric($collection['syncstate']) && $collection['syncstate'] < 0 && strlen($collection['syncstate']) < 8) {
                                        debugLog("HandleSync: GetSyncState - Got an error in HandleSync ( STATUS = " . SYNC_STATUS_INVALID_SYNCKEY . ")");
                                        _HandleSyncError(SYNC_STATUS_INVALID_SYNCKEY);
                                        return false;
                                    }
                                    // Reset the msginfos for the collectionid if set and synckey is 0
                                    if ($collection['synckey'] == '0' && isset($msginfo)) {
                                        debugLog("HandleSync: SyncKey 0 detected and msginfos contains information for the collection - resetting msginfos");
                                        unset($msginfo);
                                    }
                                    break;
                                case SYNC_FOLDERID:
                                    $collection["collectionid"] = $decoder->getElementContent();
                                    if (!$decoder->getElementEndTag()) {
                                        return false;
                                    }
                                    if ($collection['onlyoptionbodypreference'] == false && isset($SyncCache['collections'][$collection["collectionid"]]["BodyPreference"])) {
                                        $collection['onlyoptionbodypreference'] = $protocolversion >= 14.0 && (!isset($SyncCache['collections'][$collection["collectionid"]]["BodyPreference"][1]) && !isset($SyncCache['collections'][$collection["collectionid"]]["BodyPreference"][2]) && !isset($SyncCache['collections'][$collection["collectionid"]]["BodyPreference"][3]) && !isset($SyncCache['collections'][$collection["collectionid"]]["BodyPreference"][4]));
                                    }
                                    break;
                                case SYNC_FOLDERTYPE:
                                    $collection["class"] = $decoder->getElementContent();
                                    debugLog("HandleSync: Sync folder:{$collection["class"]}");
                                    if (!$decoder->getElementEndTag()) {
                                        return false;
                                    }
                                    break;
                                case SYNC_MAXITEMS:
                                    $collection["maxitems"] = $decoder->getElementContent();
                                    if (!$decoder->getElementEndTag()) {
                                        return false;
                                    }
                                    break;
                                case SYNC_CONVERSATIONMODE:
                                    if (($collection["conversationmode"] = $decoder->getElementContent()) !== false) {
                                        if (!$decoder->getElementEndTag()) {
                                            return false;
                                        }
                                    } else {
                                        $collection["conversationmode"] = true;
                                    }
                                    break;
                                case SYNC_SUPPORTED:
                                    while (1) {
                                        $el = $decoder->getElement();
                                        if ($el[EN_TYPE] == EN_TYPE_ENDTAG) {
                                            break;
                                        }
                                    }
                                    break;
                                case SYNC_DELETESASMOVES:
                                    if (($collection["deletesasmoves"] = $decoder->getElementContent()) !== false) {
                                        if (!$decoder->getElementEndTag()) {
                                            return false;
                                        }
                                    } else {
                                        $collection["deletesasmoves"] = true;
                                    }
                                    break;
                                case SYNC_GETCHANGES:
                                    if (($collection["getchanges"] = $decoder->getElementContent()) !== false) {
                                        if (!$decoder->getElementEndTag()) {
                                            return false;
                                        }
                                    } else {
                                        $collection["getchanges"] = true;
                                    }
                                    break;
                                case SYNC_OPTIONS:
                                    while (($syncoptionstag = $decoder->getElementStartTag(SYNC_FOLDERTYPE) ? SYNC_FOLDERTYPE : ($decoder->getElementStartTag(SYNC_FILTERTYPE) ? SYNC_FILTERTYPE : ($decoder->getElementStartTag(SYNC_TRUNCATION) ? SYNC_TRUNCATION : ($decoder->getElementStartTag(SYNC_RTFTRUNCATION) ? SYNC_RTFTRUNCATION : ($decoder->getElementStartTag(SYNC_MIMESUPPORT) ? SYNC_MIMESUPPORT : ($decoder->getElementStartTag(SYNC_MIMETRUNCATION) ? SYNC_MIMETRUNCATION : ($decoder->getElementStartTag(SYNC_CONFLICT) ? SYNC_CONFLICT : ($decoder->getElementStartTag(SYNC_AIRSYNCBASE_BODYPREFERENCE) ? SYNC_AIRSYNCBASE_BODYPREFERENCE : ($decoder->getElementStartTag(SYNC_RIGHTSMANAGEMENT_RIGHTSMANAGEMENTSUPPORT) ? SYNC_RIGHTSMANAGEMENT_RIGHTSMANAGEMENTSUPPORT : -1))))))))) != -1) {
                                        // dw2412 in as14 this is used to sent SMS type messages
                                        switch ($syncoptionstag) {
                                            case SYNC_FOLDERTYPE:
                                                $collection['optionfoldertype'] = $decoder->getElementContent();
                                                $collection[$collection['optionfoldertype']]['RightsManagementSupport'] = false;
                                                $collection[$collection['optionfoldertype']]['truncation'] = SYNC_TRUNCATION_ALL;
                                                $collection[$collection['optionfoldertype']]['conflict'] = SYNC_CONFLICT_DEFAULT;
                                                if (!$decoder->getElementEndTag()) {
                                                    return false;
                                                }
                                                // In case there is no optionfoldertype set in our cache, remove all old
                                                // optionfoldertype sync keys in case any exist
                                                if (!isset($SyncCache['collections'][$collection['collectionid']]['optionfoldertype'])) {
                                                    $statemachine->removeSyncState($collection['optionfoldertype'] . $collection["synckey"]);
                                                }
                                                $collection[$collection['optionfoldertype'] . 'syncstate'] = $statemachine->getSyncState($collection['optionfoldertype'] . $collection['synckey']);
                                                if (($delstatus = $statemachine->cleanOldSyncState($collection['optionfoldertype'] . $collection["synckey"])) !== true) {
                                                    _HandleSyncError(abs($delstatus));
                                                    return true;
                                                }
                                                break;
                                            case SYNC_FILTERTYPE:
                                                if (isset($collection['optionfoldertype'])) {
                                                    $collection[$collection['optionfoldertype']]["filtertype"] = $decoder->getElementContent();
                                                } else {
                                                    $collection["filtertype"] = $decoder->getElementContent();
                                                }
                                                if (!$decoder->getElementEndTag()) {
                                                    return false;
                                                }
                                                break;
                                            case SYNC_TRUNCATION:
                                                if (isset($collection['optionfoldertype'])) {
                                                    $collection[$collection['optionfoldertype']]["truncation"] = $decoder->getElementContent();
                                                } else {
                                                    $collection["truncation"] = $decoder->getElementContent();
                                                }
                                                if (!$decoder->getElementEndTag()) {
                                                    return false;
                                                }
                                                break;
                                            case SYNC_RTFTRUNCATION:
                                                if (isset($collection['optionfoldertype'])) {
                                                    $collection[$collection['optionfoldertype']]["rtftruncation"] = $decoder->getElementContent();
                                                } else {
                                                    $collection["rtftruncation"] = $decoder->getElementContent();
                                                }
                                                if (!$decoder->getElementEndTag()) {
                                                    return false;
                                                }
                                                break;
                                            case SYNC_MIMESUPPORT:
                                                if (isset($collection['optionfoldertype'])) {
                                                    $collection[$collection['optionfoldertype']]["mimesupport"] = $decoder->getElementContent();
                                                } else {
                                                    $collection["mimesupport"] = $decoder->getElementContent();
                                                }
                                                if (!$decoder->getElementEndTag()) {
                                                    return false;
                                                }
                                                break;
                                            case SYNC_MIMETRUNCATION:
                                                if (isset($collection['optionfoldertype'])) {
                                                    $collection[$collection['optionfoldertype']]["mimetruncation"] = $decoder->getElementContent();
                                                } else {
                                                    $collection["mimetruncation"] = $decoder->getElementContent();
                                                }
                                                if (!$decoder->getElementEndTag()) {
                                                    return false;
                                                }
                                                break;
                                            case SYNC_CONFLICT:
                                                if (isset($collection['optionfoldertype'])) {
                                                    $collection[$collection['optionfoldertype']]["conflict"] = $decoder->getElementContent();
                                                } else {
                                                    $collection["conflict"] = $decoder->getElementContent();
                                                }
                                                if (!$decoder->getElementEndTag()) {
                                                    return false;
                                                }
                                                break;
                                                // START ADDED dw2412 V12.0 Sync Support
                                            // START ADDED dw2412 V12.0 Sync Support
                                            case SYNC_AIRSYNCBASE_BODYPREFERENCE:
                                                if (!isset($bodypreference)) {
                                                    $bodypreference = array();
                                                }
                                                while (($bodypreferencefield = $decoder->getElementStartTag(SYNC_AIRSYNCBASE_TYPE) ? SYNC_AIRSYNCBASE_TYPE : ($decoder->getElementStartTag(SYNC_AIRSYNCBASE_TRUNCATIONSIZE) ? SYNC_AIRSYNCBASE_TRUNCATIONSIZE : ($decoder->getElementStartTag(SYNC_AIRSYNCBASE_PREVIEW) ? SYNC_AIRSYNCBASE_PREVIEW : ($decoder->getElementStartTag(SYNC_AIRSYNCBASE_ALLORNONE) ? SYNC_AIRSYNCBASE_ALLORNONE : -1)))) != -1) {
                                                    switch ($bodypreferencefield) {
                                                        case SYNC_AIRSYNCBASE_TYPE:
                                                            $bodypreference["Type"] = $decoder->getElementContent();
                                                            if (!$decoder->getElementEndTag()) {
                                                                return false;
                                                            }
                                                            break;
                                                        case SYNC_AIRSYNCBASE_TRUNCATIONSIZE:
                                                            $bodypreference["TruncationSize"] = $decoder->getElementContent();
                                                            if (!$decoder->getElementEndTag()) {
                                                                return false;
                                                            }
                                                            break;
                                                        case SYNC_AIRSYNCBASE_PREVIEW:
                                                            $bodypreference["Preview"] = $decoder->getElementContent();
                                                            if (!$decoder->getElementEndTag()) {
                                                                return false;
                                                            }
                                                            break;
                                                        case SYNC_AIRSYNCBASE_ALLORNONE:
                                                            $bodypreference["AllOrNone"] = $decoder->getElementContent();
                                                            if (!$decoder->getElementEndTag()) {
                                                                return false;
                                                            }
                                                            break;
                                                    }
                                                }
                                                $decoder->getElementEndTag();
                                                if (isset($collection['optionfoldertype'])) {
                                                    $collection[$collection['optionfoldertype']]["BodyPreference"][$bodypreference["Type"]] = $bodypreference;
                                                } else {
                                                    $collection["BodyPreference"][$bodypreference["Type"]] = $bodypreference;
                                                }
                                                if ($collection['onlyoptionbodypreference'] == false) {
                                                    $collection['onlyoptionbodypreference'] = $protocolversion >= 14.0 && (!isset($collection["BodyPreference"][1]) && !isset($collection["BodyPreference"][2]) && !isset($collection["BodyPreference"][3]) && !isset($collection["BodyPreference"][4]));
                                                }
                                                break;
                                                // END ADDED dw2412 V12.0 Sync Support
                                            // END ADDED dw2412 V12.0 Sync Support
                                            case SYNC_RIGHTSMANAGEMENT_RIGHTSMANAGEMENTSUPPORT:
                                                if (isset($collection['optionfoldertype'])) {
                                                    $collection[$collection['optionfoldertype']]['RightsManagementSupport'] = $decoder->getElementContent();
                                                } else {
                                                    $collection['RightsManagementSupport'] = $decoder->getElementContent();
                                                }
                                                if (!$decoder->getElementEndTag()) {
                                                    return false;
                                                }
                                                break;
                                        }
                                    }
                                    $decoder->getElementEndTag();
                                    break;
                                case SYNC_PERFORM:
                                    // compatibility mode - get folderid from the state directory
                                    if (!isset($collection["collectionid"])) {
                                        $collection["collectionid"] = _getFolderID($devid, $collection["class"]);
                                    }
                                    // Start error checking
                                    // Since we're not working sequential with the fields we need to do error checking prior actual perform can take place.
                                    // If needed elements are missing we will return error Status to the client
                                    if ($collection["collectionid"] == "" || $collection["collectionid"] == false) {
                                        _HandleSyncError(SYNC_STATUS_INVALID_SYNCKEY);
                                        debugLog("HandleSync: Should do a perform but don't have a collectionid, sending status " . SYNC_STATUS_INVALID_SYNCKEY . " to recover from this ( STATUS = " . SYNC_STATUS_INVALID_SYNCKEY . ")");
                                        return true;
                                    }
                                    if (!isset($collection["synckey"])) {
                                        _HandleSyncError(SYNC_STATUS_PROTOCOL_ERROR);
                                        debugLog("HandleSync: Should do a perform in collection " . $collection["collectionid"] . " without any synckey, sending status " . SYNC_STATUS_PROTOCOL_ERROR . " to recover from this ( STATUS = " . SYNC_STATUS_PROTOCOL_ERROR . ")");
                                        return true;
                                    }
                                    if ($protocolversion >= 12.1 && !isset($collection["class"]) && isset($collection["collectionid"])) {
                                        if (isset($SyncCache['folders'][$collection["collectionid"]]["class"])) {
                                            $collection["class"] = $SyncCache['folders'][$collection["collectionid"]]["class"];
                                            debugLog("HandleSync: Sync folder:{$collection["class"]}");
                                        } else {
                                            _HandleSyncError(SYNC_STATUS_FOLDER_HIERARCHY_CHANGED);
                                            debugLog("HandleSync: No Class even in cache, sending status " . SYNC_STATUS_FOLDER_HIERARCHY_CHANGED . " to recover from this ( STATUS = " . SYNC_STATUS_FOLDER_HIERARCHY_CHANGED . ")");
                                            return true;
                                        }
                                    }
                                    // End error checking, everything seems to be ok until this point. Doing the requested SYNC_PERFORM
                                    // Configure importer with last state
                                    $importer[$collection["collectionid"]] = $backend->GetContentsImporter($collection["collectionid"]);
                                    $filtertype = isset($collection["filtertype"]) ? $collection["filtertype"] : (isset($SyncCache['collections'][$collection["collectionid"]]["filtertype"]) ? $SyncCache['collections'][$collection["collectionid"]]["filtertype"] : 0);
                                    $mclass = isset($collection["class"]) ? $collection["class"] : (isset($SyncCache['collections'][$collection["collectionid"]]["class"]) ? $SyncCache['collections'][$collection["collectionid"]]["class"] : false);
                                    $bodypreference = isset($collection["BodyPreference"]) ? $collection["BodyPreference"] : (isset($SyncCache['collections'][$collection["collectionid"]]["BodyPreference"]) ? $SyncCache['collections'][$collection["collectionid"]]["BodyPreference"] : false);
                                    if (isset($collection["optionfoldertype"])) {
                                        $optionfiltertype = isset($collection[$collection['optionfoldertype']]['filtertype']) ? $collection[$collection['optionfoldertype']]['filtertype'] : (isset($SyncCache['collections'][$collection['collectionid']][$collection['optionfoldertype']]['filtertype']) ? $SyncCache['collections'][$collection['collectionid']][$collection['optionfoldertype']]['filtertype'] : 0);
                                        $optionbodypreference = isset($collection[$collection["optionfoldertype"]]["BodyPreference"]) ? $collection[$collection["optionfoldertype"]]["BodyPreference"] : (isset($SyncCache['collections'][$collection["collectionid"]][$collection["optionfoldertype"]]["BodyPreference"]) ? $SyncCache['collections'][$collection["collectionid"]][$collection["optionfoldertype"]]["BodyPreference"] : false);
                                        $importer[$collection['optionfoldertype'] . $collection["collectionid"]] = $backend->GetContentsImporter($collection["collectionid"]);
                                        $importer[$collection['optionfoldertype'] . $collection["collectionid"]]->Config($collection[$collection['optionfoldertype'] . 'syncstate'], $collection["conflict"], $collection['optionfoldertype'], $optionfiltertype, false, $optionbodypreference);
                                    } else {
                                        $optionbodypreference = false;
                                    }
                                    debugLog("HandleSync: FilterTypes Perform: " . $filtertype . " " . (isset($optionfiltertype) ? $optionfiltertype : ""));
                                    if ($collection['onlyoptionbodypreference'] === false) {
                                        $importer[$collection["collectionid"]]->Config($collection['syncstate'], $collection["conflict"], $mclass, $filtertype, $bodypreference, false);
                                    }
                                    $nchanges = 0;
                                    while (($performtag = $decoder->getElementStartTag(SYNC_ADD) ? SYNC_ADD : ($decoder->getElementStartTag(SYNC_MODIFY) ? SYNC_MODIFY : ($decoder->getElementStartTag(SYNC_REMOVE) ? SYNC_REMOVE : ($decoder->getElementStartTag(SYNC_FETCH) ? SYNC_FETCH : -1)))) != -1) {
                                        $nchanges++;
                                        // dw2412 in as14 this is used to sent SMS type messages
                                        $foldertype = false;
                                        $serverid = false;
                                        $clientid = false;
                                        while (($addmodifyfetchtag = $decoder->getElementStartTag(SYNC_FOLDERTYPE) ? SYNC_FOLDERTYPE : ($decoder->getElementStartTag(SYNC_SERVERENTRYID) ? SYNC_SERVERENTRYID : ($decoder->getElementStartTag(SYNC_CLIENTENTRYID) ? SYNC_CLIENTENTRYID : ($decoder->getElementStartTag(SYNC_DATA) ? SYNC_DATA : -1)))) != -1) {
                                            switch ($addmodifyfetchtag) {
                                                case SYNC_FOLDERTYPE:
                                                    $foldertype = $decoder->getElementContent();
                                                    if (!$decoder->getElementEndTag()) {
                                                        // end foldertype
                                                        return false;
                                                    }
                                                    break;
                                                case SYNC_SERVERENTRYID:
                                                    $serverid = $decoder->getElementContent();
                                                    if (!$decoder->getElementEndTag()) {
                                                        // end serverid
                                                        return false;
                                                    }
                                                    break;
                                                case SYNC_CLIENTENTRYID:
                                                    $clientid = $decoder->getElementContent();
                                                    if (!$decoder->getElementEndTag()) {
                                                        // end clientid
                                                        return false;
                                                    }
                                                    break;
                                                case SYNC_DATA:
                                                    // Get application data if available
                                                    if (!isset($collection["class"])) {
                                                        debugLog("HandleSync: No Class found for collection " . $collection["collectionid"]);
                                                        if (isset($SyncCache["collections"][$collection["collectionid"]]["class"])) {
                                                            debugLog("HandleSync: SyncCache search results in " . $SyncCache["collections"][$collection["collectionid"]]["class"]);
                                                            $collection["class"] = $SyncCache["collections"][$collection["collectionid"]]["class"];
                                                        } else {
                                                            debugLog("HandleSync: SyncCache search results in nothing :-(");
                                                        }
                                                    }
                                                    switch ($collection["class"]) {
                                                        case "Email":
                                                            if ($foldertype) {
                                                                $appdata = new SyncSMS();
                                                            } else {
                                                                $appdata = new SyncMail();
                                                            }
                                                            $appdata->decode($decoder);
                                                            break;
                                                        case "Contacts":
                                                            $appdata = new SyncContact($protocolversion);
                                                            $appdata->decode($decoder);
                                                            break;
                                                        case "Calendar":
                                                            $appdata = new SyncAppointment();
                                                            $appdata->decode($decoder);
                                                            break;
                                                        case "Tasks":
                                                            $appdata = new SyncTask();
                                                            $appdata->decode($decoder);
                                                            break;
                                                        case "Notes":
                                                            $appdata = new SyncNote();
                                                            $appdata->decode($decoder);
                                                            break;
                                                    }
                                                    if (!$decoder->getElementEndTag()) {
                                                        // end applicationdata
                                                        return false;
                                                    }
                                                    break;
                                            }
                                        }
                                        switch ($performtag) {
                                            case SYNC_MODIFY:
                                                if (isset($appdata)) {
                                                    if ($appdata->_setchange == true || $appdata->_setread == false && $appdata->_setflag == false && $appdata->_setcategories == false) {
                                                        if (isset($collection['optionfoldertype']) && $foldertype == $collection['optionfoldertype']) {
                                                            $collection['changeids'][$serverid]['optionfoldertype'] = $foldertype;
                                                            if (!isset($msginfo[$serverid])) {
                                                                $collection[$collection['optionfoldertype'] . 'changeids'][$serverid]['status'] = SYNC_STATUS_OBJECT_NOT_FOUND;
                                                            } else {
                                                                $importer[$collection['optionfoldertype'] . $collection["collectionid"]]->ImportMessageChange($serverid, $appdata);
                                                                $collection[$collection['optionfoldertype'] . 'changeids'][$serverid]['status'] = SYNC_STATUS_SUCCESS;
                                                            }
                                                        } else {
                                                            if (!isset($msginfo[$serverid])) {
                                                                $collection['changeids'][$serverid]['status'] = SYNC_STATUS_OBJECT_NOT_FOUND;
                                                            } else {
                                                                $importer[$collection["collectionid"]]->ImportMessageChange($serverid, $appdata);
                                                                $collection['changeids'][$serverid]['status'] = SYNC_STATUS_SUCCESS;
                                                            }
                                                        }
                                                    } else {
                                                        if ($appdata->_setflag == true) {
                                                            if (isset($collection['optionfoldertype']) && $foldertype == $collection['optionfoldertype']) {
                                                                $collection[$collection['optionfoldertype'] . "flagids"][$serverid]['data'] = $appdata->poommailflag;
                                                                $collection['changeids'][$serverid]['optionfoldertype'] = $foldertype;
                                                                if (!isset($msginfo[$serverid])) {
                                                                    $collection[$collection['optionfoldertype'] . 'changeids'][$serverid]['status'] = SYNC_STATUS_OBJECT_NOT_FOUND;
                                                                } else {
                                                                    $collection[$collection['optionfoldertype'] . "flagids"][$serverid]['status'] = $importer[$collection['optionfoldertype'] . $collection["collectionid"]]->ImportMessageFlag($serverid, $appdata->poommailflag);
                                                                    $collection[$collection['optionfoldertype'] . 'changeids'][$serverid]['status'] = SYNC_STATUS_SUCCESS;
                                                                }
                                                            } else {
                                                                $collection["flagids"][$serverid]['data'] = $appdata->poommailflag;
                                                                if (!isset($msginfo[$serverid])) {
                                                                    $collection['changeids'][$serverid]['status'] = SYNC_STATUS_OBJECT_NOT_FOUND;
                                                                } else {
                                                                    $collection["flagids"][$serverid]['status'] = $importer[$collection["collectionid"]]->ImportMessageFlag($serverid, $appdata->poommailflag);
                                                                    $collection['changeids'][$serverid]['status'] = SYNC_STATUS_SUCCESS;
                                                                }
                                                            }
                                                        }
                                                        if ($appdata->_setread == true) {
                                                            if (isset($collection['optionfoldertype']) && $foldertype == $collection['optionfoldertype']) {
                                                                $collection[$collection['optionfoldertype'] . "readids"][$serverid]['data'] = $appdata->read;
                                                                $collection['changeids'][$serverid]['optionfoldertype'] = $foldertype;
                                                                if (!isset($msginfo[$serverid])) {
                                                                    $collection[$collection['optionfoldertype'] . 'changeids'][$serverid]['status'] = SYNC_STATUS_OBJECT_NOT_FOUND;
                                                                } else {
                                                                    $collection[$collection['optionfoldertype'] . "readids"][$serverid]['status'] = $importer[$collection['optionfoldertype'] . $collection["collectionid"]]->ImportMessageReadFlag($serverid, $appdata->read);
                                                                    $collection[$collection['optionfoldertype'] . 'changeids'][$serverid]['status'] = SYNC_STATUS_SUCCESS;
                                                                }
                                                            } else {
                                                                $collection["readids"][$serverid]['data'] = $appdata->read;
                                                                if (!isset($msginfo[$serverid])) {
                                                                    $collection['changeids'][$serverid]['status'] = SYNC_STATUS_OBJECT_NOT_FOUND;
                                                                } else {
                                                                    $collection["readids"][$serverid]['status'] = $importer[$collection["collectionid"]]->ImportMessageReadFlag($serverid, $appdata->read);
                                                                    $collection['changeids'][$serverid]['status'] = SYNC_STATUS_SUCCESS;
                                                                }
                                                            }
                                                        }
                                                    }
                                                    $collection["importedchanges"] = true;
                                                }
                                                break;
                                            case SYNC_ADD:
                                                if (isset($appdata)) {
                                                    if (isset($collection['optionfoldertype']) && $foldertype == $collection['optionfoldertype']) {
                                                        $id = $importer[$collection['optionfoldertype'] . $collection["collectionid"]]->ImportMessageChange(false, $appdata);
                                                    } else {
                                                        $id = $importer[$collection["collectionid"]]->ImportMessageChange(false, $appdata);
                                                    }
                                                    if ($clientid && $id) {
                                                        $collection["clientids"][$clientid]['serverid'] = $id;
                                                        if ($foldertype) {
                                                            $collection["clientids"][$clientid]['optionfoldertype'] = $foldertype;
                                                            $md5msg = array('datereceived' => isset($appdata->datereceived) ? strval($appdata->datereceived) : '', 'importance' => isset($appdata->importance) ? strval($appdata->importance) : '', 'messageclass' => isset($appdata->messageclass) ? strval($appdata->messageclass) : '', 'to' => isset($appdata->to) ? strval($appdata->to) : '', 'cc' => isset($appdata->cc) ? strval($appdata->cc) : '', 'from' => isset($appdata->from) ? strval($appdata->from) : '', 'internetcpid' => isset($appdata->internetcpid) ? strval($appdata->internetcpid) : '', 'body' => isset($appdata->body) ? strval($appdata->body) : '');
                                                            $md5flags = array('flagstatus' => isset($appdata->poommailflag->flagstatus) ? strval($appdata->poommailflag->flagstatus) : '', 'flagtype' => isset($appdata->poommailflag->flagtype) ? strval($appdata->poommailflag->flagtype) : '', 'startdate' => isset($appdata->poommailflag->startdate) ? strval($appdata->poommailflag->startdate) : '', 'utcstartdate' => isset($appdata->poommailflag->utcstartdate) ? strval($appdata->poommailflag->utcstartdate) : '', 'duedate' => isset($appdata->poommailflag->duedate) ? strval($appdata->poommailflag->duedate) : '', 'utcduedate' => isset($appdata->poommailflag->utcduedate) ? strval($appdata->poommailflag->utcduedate) : '', 'datecomplete' => isset($appdata->poommailflag->datecompleted) ? strval($appdata->poommailflag->datecompleted) : '', 'reminderset' => isset($appdata->poommailflag->reminderset) ? strval($appdata->poommailflag->reminderset) : '', 'subject' => isset($appdata->poommailflag->subject) ? strval($appdata->poommailflag->subject) : '', 'ordinaldate' => isset($appdata->poommailflag->ordinaldate) ? strval($appdata->poommailflag->ordinaldate) : '', 'subordinaldate' => isset($appdata->poommailflag->subordinaldate) ? strval($appdata->poommailflag->subordinaldate) : '', 'completetime' => isset($appdata->poommailflag->completetime) ? strval($appdata->poommailflag->completetime) : '');
                                                            $msginf['md5msg'] = md5(serialize($md5msg));
                                                            $msginf['md5flags'] = md5(serialize($md5flags));
                                                            $msginf['read'] = isset($appdata->read) ? $appdata->read : '';
                                                            $msginf['class'] = "syncsms";
                                                            unset($md5msg);
                                                            unset($md5flags);
                                                            $msginfo[$id['sourcekey']] = $msginf;
                                                            debugLog("HandleSync: Generated msginfos for " . $id['sourcekey'] . " with following values: " . print_r($msginf, true));
                                                            unset($msginf);
                                                        } else {
                                                            $msginfo[$id] = array('md5msg' => 0, 'read' => '', 'md5flags' => '', 'class' => strtolower(get_class($appdata)));
                                                            debugLog("HandleSync: Generated msginfos for " . $id . " with following values: " . print_r($msginfo, true));
                                                        }
                                                        $collection["importedchanges"] = true;
                                                    }
                                                }
                                                break;
                                            case SYNC_REMOVE:
                                                if (isset($collection["deletesasmoves"])) {
                                                    $folderid = $backend->GetWasteBasket();
                                                    if ($folderid) {
                                                        if (isset($collection['optionfoldertype']) && $foldertype == $collection['optionfoldertype']) {
                                                            $importer[$collection['optionfoldertype'] . $collection["collectionid"]]->ImportMessageMove($serverid, $folderid);
                                                        } else {
                                                            $importer[$collection["collectionid"]]->ImportMessageMove($serverid, $folderid);
                                                        }
                                                        $collection["importedchanges"] = true;
                                                        break;
                                                    } else {
                                                        debugLog("HandleSync: SYNC_REMOVE failed because there is no waste basket returned!");
                                                    }
                                                }
                                                if (isset($importer[$collection["collectionid"]])) {
                                                    if (isset($collection['optionfoldertype']) && $foldertype == $collection['optionfoldertype']) {
                                                        $importer[$collection['optionfoldertype'] . $collection["collectionid"]]->ImportMessageDeletion($serverid);
                                                    } else {
                                                        $importer[$collection["collectionid"]]->ImportMessageDeletion($serverid);
                                                    }
                                                } else {
                                                    debugLog("HandleSync: SYNC_REMOVE failed because there is no importer for collection");
                                                }
                                                $collection["importedchanges"] = true;
                                                if (isset($collection['changeids'][$serverid])) {
                                                    $collection['changeids'][$serverid]['status'] = SYNC_STATUS_OBJECT_NOT_FOUND;
                                                }
                                                break;
                                            case SYNC_FETCH:
                                                array_push($collection["fetchids"], $serverid);
                                                break;
                                        }
                                        if (!$decoder->getElementEndTag()) {
                                            // end add/remove/modify/fetch
                                            return false;
                                        }
                                    }
                                    debugLog("HandleSync: Processed {$nchanges} incoming changes");
                                    // Save the updated state, which is used for the exporter later
                                    if (isset($importer[$collection["collectionid"]])) {
                                        $collection['syncstate'] = $importer[$collection["collectionid"]]->getState();
                                    }
                                    if (isset($collection['optionfoldertype']) && isset($importer[$collection['optionfoldertype'] . $collection["collectionid"]])) {
                                        $collection[$collection['optionfoldertype'] . 'syncstate'] = $importer[$collection['optionfoldertype'] . $collection["collectionid"]]->getState();
                                    }
                                    if (isset($collection["importedchanges"]) && $collection["importedchanges"] == true) {
                                        $dataimported = true;
                                    }
                                    if (isset($collection["fetchids"])) {
                                        $fetchitems = true;
                                    }
                                    if (!$decoder->getElementEndTag()) {
                                        // end SYNC_PERFORM
                                        return false;
                                    }
                                    break;
                            }
                        }
                        if (!$decoder->getElementEndTag()) {
                            // end collection
                            return false;
                        }
                        if (isset($msginfo)) {
                            $statemachine->setSyncState('mi' . $collection['synckey'], serialize($msginfo));
                        }
                        array_push($collections, $collection);
                        if (isset($collection['collectionid'])) {
                            $msginfos[$collection['collectionid']] = isset($msginfo) ? $msginfo : array();
                            if (isset($collection['class'])) {
                                $SyncCache['collections'][$collection['collectionid']]['class'] = $collection['class'];
                            }
                            if (isset($collection['maxitems'])) {
                                $SyncCache['collections'][$collection['collectionid']]['maxitems'] = $collection['maxitems'];
                            }
                            if (isset($collection['deletesasmoves'])) {
                                $SyncCache['collections'][$collection['collectionid']]['deletesasmoves'] = $collection['deletesasmoves'];
                            }
                            if (isset($collection['conversationmode'])) {
                                $SyncCache['collections'][$collection['collectionid']]['conversationmode'] = $collection['conversationmode'];
                            }
                            if (isset($collection['filtertype'])) {
                                $SyncCache['collections'][$collection['collectionid']]['filtertype'] = $collection['filtertype'];
                            }
                            if (isset($collection['truncation'])) {
                                $SyncCache['collections'][$collection['collectionid']]['truncation'] = $collection['truncation'];
                            }
                            if (isset($collection['rtftruncation'])) {
                                $SyncCache['collections'][$collection['collectionid']]['rtftruncation'] = $collection['rtftruncation'];
                            }
                            if (isset($collection['mimesupport'])) {
                                $SyncCache['collections'][$collection['collectionid']]['mimesupport'] = $collection['mimesupport'];
                            }
                            if (isset($collection['mimetruncation'])) {
                                $SyncCache['collections'][$collection['collectionid']]['mimetruncation'] = $collection['mimetruncation'];
                            }
                            if (isset($collection['conflict'])) {
                                $SyncCache['collections'][$collection['collectionid']]['conflict'] = $collection['conflict'];
                            }
                            if (isset($collection['BodyPreference'])) {
                                $SyncCache['collections'][$collection['collectionid']]['BodyPreference'] = $collection['BodyPreference'];
                            }
                            if (isset($collection['optionfoldertype'])) {
                                if (isset($collection[$collection['optionfoldertype']]['filtertype'])) {
                                    $SyncCache['collections'][$collection['collectionid']][$collection['optionfoldertype']]['filtertype'] = $collection[$collection['optionfoldertype']]['filtertype'];
                                }
                                if (isset($collection[$collection['optionfoldertype']]['BodyPreference'])) {
                                    $SyncCache['collections'][$collection['collectionid']][$collection['optionfoldertype']]['BodyPreference'] = $collection[$collection['optionfoldertype']]['BodyPreference'];
                                }
                                $SyncCache['collections'][$collection['collectionid']]['optionfoldertype'] = $collection['optionfoldertype'];
                            } elseif (isset($SyncCache['collections'][$collection['collectionid']]['optionfoldertype'])) {
                                $optionfoldertype = $SyncCache['collections'][$collection['collectionid']]['optionfoldertype'];
                                if (isset($SyncCache['collections'][$collection['collectionid']][$optionfoldertype])) {
                                    unset($SyncCache['collections'][$collection['collectionid']][$optionfoldertype]);
                                    unset($SyncCache['collections'][$collection['collectionid']]['optionfoldertype']);
                                }
                            }
                        }
                    }
                    if (!$decoder->getElementEndTag()) {
                        // end collections
                        return false;
                    }
                    break;
            }
        }
        if (!isset($collections)) {
            debugLog("HandleSync:  HERE S " . (isset($SyncCache['lastuntil']) ? strftime("%x %X", $SyncCache['lastuntil'] + $maxcacheage) : "NO LASTUNTIL!"));
            $found = false;
            foreach ($SyncCache['collections'] as $value) {
                if (isset($value['synckey'])) {
                    $found = true;
                    break;
                }
            }
            if ($found == false) {
                $SyncCache['lastuntil'] = time();
                $statemachine->setSyncCache(serialize($SyncCache));
                _HandleSyncError(SYNC_STATUS_REQUEST_INCOMPLETE);
                debugLog("HandleSync: No Collections with SyncKeys. Enforce Full Sync Request (STATUS = " . SYNC_STATUS_REQUEST_INCOMPLETE . ")");
                return true;
            }
        }
        // Fill up collections with values from cache in case they're missing
        foreach ($collections as $key => $values) {
            if (!isset($values["class"]) && isset($SyncCache['folders'][$values["collectionid"]]["class"])) {
                $collections[$key]["class"] = $SyncCache['folders'][$values["collectionid"]]["class"];
            }
            if (!isset($values["filtertype"]) && isset($SyncCache['collections'][$values["collectionid"]]["filtertype"])) {
                $collections[$key]["filtertype"] = $SyncCache['collections'][$values["collectionid"]]["filtertype"];
            }
            if (!isset($values["mimesupport"]) && isset($SyncCache['collections'][$values["collectionid"]]["mimesupport"])) {
                $collections[$key]["mimesupport"] = $SyncCache['collections'][$values["collectionid"]]["mimesupport"];
            }
            if (!isset($values["BodyPreference"]) && isset($SyncCache['collections'][$values["collectionid"]]["BodyPreference"])) {
                $collections[$key]["BodyPreference"] = $SyncCache['collections'][$values["collectionid"]]["BodyPreference"];
            }
            if (isset($value['optionfoldertype'])) {
                if (!isset($values[$value['optionfoldertype']]["filtertype"]) && isset($SyncCache['collections'][$values["collectionid"]][$value['optionfoldertype']]["filtertype"])) {
                    $collections[$key][$value['optionfoldertype']]["filtertype"] = $SyncCache['collections'][$values["collectionid"]][$value['optionfoldertype']]["filtertype"];
                }
                if (!isset($values[$value['optionfoldertype']]["BodyPreference"]) && isset($SyncCache['collections'][$values["collectionid"]][$value['optionfoldertype']]["BodyPreference"])) {
                    $collections[$key][$value['optionfoldertype']]["BodyPreference"] = $SyncCache['collections'][$values["collectionid"]][$value['optionfoldertype']]["BodyPreference"];
                }
            }
            $collections[$key]['onlyoptionbodypreference'] = $protocolversion >= 14.0 && (!isset($SyncCache['collections'][$values["collectionid"]]["BodyPreference"][1]) && !isset($SyncCache['collections'][$values["collectionid"]]["BodyPreference"][2]) && !isset($SyncCache['collections'][$values["collectionid"]]["BodyPreference"][3]) && !isset($SyncCache['collections'][$values["collectionid"]]["BodyPreference"][4]));
            // Set the maxitems (windowsize) to either what is being declared in cache or to 100 if nothing can be found in cache.
            // 100 is according to spec the default if nothing is being sent by the client
            if (!isset($values["maxitems"])) {
                $collections[$key]["maxitems"] = isset($SyncCache['collections'][$values["collectionid"]]['maxitems']) ? $SyncCache['collections'][$values["collectionid"]]['maxitems'] : 100;
            }
            // in case the maxitems (windowsize) is above 512 or 0 it should be interpreted as 512 according to specs.
            if ($collections[$key]["maxitems"] > 512 || $collections[$key]["maxitems"] == 0) {
                $collections[$key]["maxitems"] = 512;
            }
            if (isset($values['synckey']) && $values['synckey'] == '0' && isset($SyncCache['collections'][$values["collectionid"]]['synckey']) && $SyncCache['collections'][$values["collectionid"]]['synckey'] != '0') {
                unset($SyncCache['collections'][$values["collectionid"]]['synckey']);
            }
        }
        // Give up in case we don't have a synched hierarchy synckey!
        if (!isset($SyncCache['hierarchy']['synckey'])) {
            _HandleSyncError(SYNC_STATUS_FOLDER_HIERARCHY_CHANGED);
            debugLog("HandleSync: HandleSync Error No Hierarchy SyncKey in SyncCache... Invalidate! (STATUS = " . SYNC_STATUS_FOLDER_HIERARCHY_CHANGED . ")");
            return true;
        }
        // Just in case some client runs amok. This HB Interval & Wait in one request is not allowed by definition
        if ($SyncCache['hbinterval'] !== false && $SyncCache['wait'] !== false) {
            _HandleSyncError(SYNC_STATUS_PROTOCOL_ERROR);
            debugLog("HandleSync: HandleSync got Found HeartbeatInterval and Wait in request. This violates the protocol spec. (STATUS = " . SYNC_STATUS_PROTOCOL_ERROR . ")");
            return true;
        }
        // Partial sync but with Folders and Options so we need to set collections
        $foundsynckey = false;
        if ($partial === true) {
            debugLog("HandleSync: Partial Sync");
            $TempSyncCache = unserialize($statemachine->getSyncCache());
            // Removing all from TempSyncCache that we already got information on
            $CollectionsUnchanged = 0;
            $CollectionKeys = 0;
            $ConfirmedKeys = 0;
            foreach ($collections as $key => $value) {
                // Discover if any collection got really changed
                $v1 = $collections[$key];
                if (isset($v1['collectionid'])) {
                    unset($v1['collectionid']);
                }
                if (isset($v1['clientids'])) {
                    unset($v1['clientids']);
                }
                if (isset($v1['fetchids'])) {
                    unset($v1['fetchids']);
                }
                if (isset($v1['getchanges'])) {
                    unset($v1['getchanges']);
                }
                if (isset($v1['changeids'])) {
                    unset($v1['changeids']);
                }
                if (isset($v1['onlyoptionbodypreference'])) {
                    unset($v1['onlyoptionbodypreference']);
                }
                if (isset($v1['syncstate'])) {
                    unset($v1['syncstate']);
                }
                if (isset($v1['optionfoldertype'])) {
                    if (isset($v1[$v1['optionfoldertype'] . 'syncstate'])) {
                        unset($v1[$v1['optionfoldertype'] . 'syncstate']);
                    }
                }
                $v2 = $TempSyncCache['collections'][$value['collectionid']];
                ksort($v1);
                if (isset($v1['BodyPreference'])) {
                    ksort($v1['BodyPreference']);
                    foreach ($v1['BodyPreference'] as $key => $v) {
                        ksort($v1['BodyPreference'][$key]);
                    }
                }
                if (isset($v1['optionfoldertype'])) {
                    ksort($v1[$v1['optionfoldertype']]);
                    if (isset($v1[$v1['optionfoldertype']]['BodyPreference'])) {
                        ksort($v1[$v1['optionfoldertype']]['BodyPreference']);
                        foreach ($v1[$v1['optionfoldertype']]['BodyPreference'] as $key => $v) {
                            ksort($v1[$v1['optionfoldertype']]['BodyPreference'][$key]);
                        }
                    }
                }
                if (isset($v1['BodyPreference'])) {
                    ksort($v1['BodyPreference']);
                }
                ksort($v2);
                if (isset($v2['BodyPreference'])) {
                    ksort($v2['BodyPreference']);
                    foreach ($v2['BodyPreference'] as $key => $v) {
                        ksort($v2['BodyPreference'][$key]);
                    }
                }
                if (isset($v2['optionfoldertype'])) {
                    ksort($v2[$v2['optionfoldertype']]);
                    if (isset($v2[$v2['optionfoldertype']]['BodyPreference'])) {
                        ksort($v2[$v2['optionfoldertype']]['BodyPreference']);
                        foreach ($v2[$v2['optionfoldertype']]['BodyPreference'] as $key => $v) {
                            ksort($v2[$v2['optionfoldertype']]['BodyPreference'][$key]);
                        }
                    }
                }
                if (md5(serialize($v1)) == md5(serialize($v2))) {
                    $CollectionsUnchanged++;
                }
                if (isset($v2['optionfoldertype']) && !isset($v1['optionfoldertype']) || isset($v1['optionfoldertype']) && !isset($v2['optionfoldertype'])) {
                    $SyncStatus = SYNC_STATUS_REQUEST_INCOMPLETE;
                }
                unset($v1);
                unset($v2);
                // Unset Collection in TempSyncCache in case we already have it in our collections
                if (isset($TempSyncCache['collections'][$value['collectionid']])) {
                    debugLog("HandleSync: Removing " . $value['collectionid'] . " from TempSyncCache");
                    unset($TempSyncCache['collections'][$value['collectionid']]);
                }
                // Remove keys from confirmed synckeys array and count them
                if (isset($value['synckey'])) {
                    $foundsynckey = true;
                    if (isset($SyncCache['confirmed_synckeys'][$value['synckey']])) {
                        debugLog('HandleSync: Removed ' . $SyncCache['confirmed_synckeys'][$value['synckey']] . ' from confirmed_synckeys array');
                        unset($SyncCache['confirmed_synckeys'][$value['synckey']]);
                        $statemachine->deleteSyncCacheConfirmedSyncKey($SyncCache, $value['synckey']);
                        $ConfirmedKeys++;
                    }
                }
                // Count all current Collections with SyncKey set
                if (isset($value['synckey'])) {
                    $CollectionKeys++;
                }
            }
            $CacheKeys = 0;
            foreach ($SyncCache['collections'] as $value) {
                // Count all cached Collections with SyncKey set
                if (isset($value['synckey'])) {
                    $CacheKeys++;
                }
            }
            debugLog("HandleSync: CollectionKeys vs SyncCacheKeys vs Unchanged Collections vs ConfirmedKeys: " . $CollectionKeys . " / " . $CacheKeys . " / " . $CollectionsUnchanged . " / " . $ConfirmedKeys);
            debugLog("HandleSync: Wait Cache / TempCache: " . $SyncCache['wait'] . " / " . $TempSyncCache['wait']);
            debugLog("HandleSync: Heartbeat Cache / TempCache: " . $SyncCache['hbinterval'] . " / " . $TempSyncCache['hbinterval']);
            debugLog("HandleSync: Time now is <= SyncCache lastuntil (" . time() . " - " . $SyncCache['lastuntil'] . " = " . (time() - $SyncCache['lastuntil']) . ")");
            debugLog("HandleSync: Last HB Sync started vs Last Sync normal end " . $SyncCache['lasthbsyncstarted'] . " / " . $SyncCache['lastsyncendnormal'] . ")");
            if (isset($SyncCache['lasthbsyncstarted']) && $SyncCache['lasthbsyncstarted'] > $SyncCache['lastsyncendnormal']) {
                debugLog("HandleSync: lasthbsyncstarted is larger than lastsyncendnormal. Request a full request now (STATUS = " . SYNC_STATUS_REQUEST_INCOMPLETE . ")");
                _HandleSyncError(SYNC_STATUS_REQUEST_INCOMPLETE);
                return true;
            }
            if (isset($SyncCache['lastuntil']) && isset($SyncCache['lasthbsyncstarted']) && isset($SyncCache['lastsyncendnormal']) && $SyncCache['lasthbsyncstarted'] > $SyncCache['lastsyncendnormal'] && time() < $SyncCache['lastuntil']) {
                debugLog("HandleSync: Current Time is lower than lastuntil. Request a full request now (STATUS = " . SYNC_STATUS_REQUEST_INCOMPLETE . ")");
                _HandleSyncError(SYNC_STATUS_REQUEST_INCOMPLETE);
                return true;
            }
            // If there are no changes within partial sync, send status 13 since sending partial elements without any changes is suspicius
            // (Could be a remove folder from sync...)
            // Logic is:
            // Collection SyncKeys are being send by device
            // No SyncKeys got confirmed
            // Collections in request are equal with Collections in Cache
            // Current Heartbeat/Wait is still running
            // No new Heartbeat/Wait Value is being sent
            if ($CollectionKeys > 0 && $ConfirmedKeys == 0 && $CollectionsUnchanged == $CollectionKeys && time() <= $SyncCache['lastuntil'] && ($SyncCache['wait'] == false && $SyncCache['hbinterval'] == false)) {
                debugLog("HandleSync: Partial Request with completely unchanged collections. Request a full request now (STATUS = " . SYNC_STATUS_REQUEST_INCOMPLETE . ")");
                _HandleSyncError(SYNC_STATUS_REQUEST_INCOMPLETE);
                return true;
            }
            // Updating Collections with all necessary informations that we don't have informations for but with a synckey in foldercache
            foreach ($TempSyncCache['collections'] as $key => $value) {
                if (isset($value['synckey'])) {
                    $collection = $value;
                    $collection['collectionid'] = $key;
                    if (isset($default_maxitems)) {
                        $collection["maxitems"] = $default_maxitems;
                    }
                    $collection['onlyoptionbodypreference'] = $protocolversion >= 14.0 && (!isset($collection["BodyPreference"][1]) && !isset($collection["BodyPreference"][2]) && !isset($collection["BodyPreference"][3]) && !isset($collection["BodyPreference"][4]));
                    $collection['syncstate'] = $statemachine->getSyncState($collection["synckey"]);
                    if (isset($collection['optionfoldertype'])) {
                        $collection[$collection['optionfoldertype'] . 'syncstate'] = $statemachine->getSyncState($collection['optionfoldertype'] . $collection["synckey"]);
                    }
                    if ($collection['synckey'] == "0") {
                        debugLog('HandleSync: Here4 : Setting $msginfos[' . $collection['collectionid'] . '] to array()');
                        $msginfos[$collection['collectionid']] = array();
                    } else {
                        if (!isset($msginfos[$collection['collectionid']])) {
                            $msginfos[$collection['collectionid']] = unserialize($statemachine->getSyncState("mi" . $collection['synckey']));
                        }
                    }
                    if (isset($SyncCache['confirmed_synckeys'][$collection["synckey"]]) && (strlen($collection['syncstate']) == 0 || bin2hex(substr($collection['syncstate'], 4, 4)) == "00000000")) {
                        debugLog("HandleSync: InitialSync determined for collection. No need to confirm this key! " . $collection["synckey"]);
                        unset($SyncCache['confirmed_synckeys'][$collection["synckey"]]);
                    }
                    if ($collection['onlyoptionbodypreference'] === false && $collection['syncstate'] < 0 && strlen($collection['syncstate']) < 8 || isset($collection['optionfoldertype']) && $collection[$collection['optionfoldertype'] . 'syncstate'] < 0 && $collection[$collection['optionfoldertype'] . 'syncstate'] < 8) {
                        _HandleSyncError("3");
                        debugLog("HandleSync: GetSyncState ERROR (Syncstate: " . abs($collection['syncstate']) . ") strlen=" . strlen($collection['syncstate']));
                        return true;
                    }
                    debugLog("HandleSync: Using SyncCache State for " . $TempSyncCache['folders'][$key]['displayname']);
                    array_push($collections, $collection);
                }
            }
            unset($TempSyncCache);
        } else {
            // We got a full sync so we don't need to look after any confirmed synckey in array since device never the less only knows keys that it send now
            $SyncCache['confirmed_synckeys'] = array();
            // Reset the lastuntil heartbeat/wait time to time now since this is the new base for the heartbeat
            $SyncCache['lastuntil'] = time();
            // No Partial Sync so in this case we have to remove all synckeys to prevent syncs in these collections.
            foreach ($SyncCache['collections'] as $key => $value) {
                debugLog("HandleSync: Not a partial sync. Removing SyncCache[synckey] from collection " . $key);
                unset($SyncCache['collections'][$key]['synckey']);
            }
        }
        // Update the SyncCache with values from current collections
        foreach ($collections as $key => $value) {
            if (isset($value['collectionid'])) {
                if (isset($value['synckey'])) {
                    debugLog("HandleSync: Adding SyncCache[synckey] from collection " . $value['collectionid']);
                    $SyncCache['collections'][$value['collectionid']]['synckey'] = $value['synckey'];
                }
                if (isset($value["class"])) {
                    $SyncCache['collections'][$value["collectionid"]]["class"] = $value["class"];
                }
                if (isset($value["maxitems"])) {
                    $SyncCache['collections'][$value["collectionid"]]["maxitems"] = $value["maxitems"];
                }
                if (isset($value["deletesasmoves"])) {
                    $SyncCache['collections'][$value["collectionid"]]["deletesasmoves"] = $value["deletesasmoves"];
                }
                if (isset($value["conversationmode"])) {
                    $SyncCache['collections'][$value["collectionid"]]["conversationmode"] = $value["conversationmode"];
                }
                if (isset($value["filtertype"])) {
                    $SyncCache['collections'][$value["collectionid"]]["filtertype"] = $value["filtertype"];
                }
                if (isset($value["truncation"])) {
                    $SyncCache['collections'][$value["collectionid"]]["truncation"] = $value["truncation"];
                }
                if (isset($value["rtftruncation"])) {
                    $SyncCache['collections'][$value["collectionid"]]["rtftruncation"] = $value["rtftruncation"];
                }
                if (isset($value["mimesupport"])) {
                    $SyncCache['collections'][$value["collectionid"]]["mimesupport"] = $value["mimesupport"];
                }
                if (isset($value["mimetruncation"])) {
                    $SyncCache['collections'][$value["collectionid"]]["mimetruncation"] = $value["mimetruncation"];
                }
                if (isset($value["conflict"])) {
                    $SyncCache['collections'][$value["collectionid"]]["conflict"] = $value["conflict"];
                }
                if (isset($value["BodyPreference"])) {
                    $SyncCache['collections'][$value["collectionid"]]["BodyPreference"] = $value["BodyPreference"];
                }
                if (isset($value['optionfoldertype'])) {
                    if (isset($value[$value['optionfoldertype']]["filtertype"])) {
                        $SyncCache['collections'][$value["collectionid"]][$value['optionfoldertype']]["filtertype"] = $value[$value['optionfoldertype']]["filtertype"];
                    }
                    if (isset($value[$value['optionfoldertype']]["BodyPreference"])) {
                        $SyncCache['collections'][$value["collectionid"]][$value['optionfoldertype']]["BodyPreference"] = $value[$value['optionfoldertype']]["BodyPreference"];
                    }
                    $SyncCache['collections'][$value["collectionid"]]['optionfoldertype'] = $value['optionfoldertype'];
                }
            } else {
                debugLog("HandleSync: Collection without collectionid found: " . print_r($value, true));
            }
        }
        // End Update the synckeys in SyncCache
        if (!$decoder->getElementEndTag()) {
            // end sync
            return false;
        }
        // In case some synckeys didn't get confirmed by device we issue a full sync
        if (isset($SyncCache['confirmed_synckeys']) && sizeof($SyncCache['confirmed_synckeys']) > 0) {
            debugLog("Confirmed Synckeys contains: " . print_r($SyncCache['confirmed_synckeys'], true));
            unset($SyncCache['confirmed_synckeys']);
            $statemachine->setSyncCache(serialize($SyncCache));
            debugLog("HandleSync: Some SyncKeys didn't get confirmed. To ensure sync integrity we request a full request now (STATUS = " . SYNC_STATUS_REQUEST_INCOMPLETE . ")");
            _HandleSyncError(SYNC_STATUS_REQUEST_INCOMPLETE);
            return true;
        } else {
            debugLog("HandleSync: All SyncKeys got confirmed. We continue here...");
            $statemachine->setSyncCache(serialize($SyncCache));
        }
        $i = 0;
        $statemachine->setSyncState('mi' . $collection['synckey'], isset($msginfos[$collection['collectionid']]) ? serialize($msginfos[$collection['collectionid']]) : serialize(array()));
        foreach ($collections as $key => $value) {
            if (isset($value['synckey'])) {
                $i++;
            }
        }
        if ($i == 0) {
            debugLog("HandleSync: We don't have any synckeys in collection. Request a full request now (STATUS = " . SYNC_STATUS_PROTOCOL_ERROR . ")");
            _HandleSyncError(SYNC_STATUS_PROTOCOL_ERROR);
            return true;
        }
    }
    debugLog("HandleSync: SyncStatus is " . $SyncStatus . " hbinterval is " . $SyncCache['hbinterval']);
    if ($protocolversion >= 12.1 && $SyncStatus == 1 && $dataimported == false && ($SyncCache['wait'] !== false || $SyncCache['hbinterval'] !== false || $shortsyncreq === true)) {
        $dataavailable = false;
        $timeout = 5;
        if (isset($SyncCache['wait']) && $SyncCache['wait'] !== false) {
            $until = time() + $SyncCache['wait'] * 60;
        } else {
            if (isset($SyncCache['hbinterval']) && $SyncCache['hbinterval'] !== false) {
                $until = time() + $SyncCache['hbinterval'];
            } else {
                $until = time() + 10;
            }
        }
        debugLog("HandleSync: Looking for changes for " . ($until - time()) . " seconds");
        $SyncCache['lastuntil'] = $until;
        $SyncCache['lasthbsyncstarted'] = time();
        $statemachine->setSyncCache(serialize($SyncCache));
        // Reading current state of the hierarchy state for determining changes during heartbeat/wait
        $hierarchystate = $statemachine->getSyncState($SyncCache['hierarchy']['synckey']);
        $hbrunavrgduration = 0;
        $hbrunmaxduration = 0;
        while (time() + $hbrunavrgduration < $until - $hbrunmaxduration) {
            $hbrunstarttime = microtime(true);
            // we try to find changes as long as time is lower than wait time
            // In case something changed in SyncCache regarding the folder hierarchy exit this function
            $TempSyncCache = unserialize($statemachine->getSyncCache());
            if ($TempSyncCache === false) {
                debugLog("HandleSync: TempSyncCache could not be read and decoded, exiting here.");
                return true;
            }
            if ($TempSyncCache['timestamp'] > $SyncCache['timestamp']) {
                debugLog("HandleSync: Changes in cache determined during Sync Wait/Heartbeat, exiting here.");
                return true;
            }
            if (PROVISIONING === true) {
                $rwstatus = $backend->getDeviceRWStatus($user, $auth_pw, $devid);
                if ($rwstatus == SYNC_PROVISION_RWSTATUS_PENDING || $rwstatus == SYNC_PROVISION_RWSTATUS_WIPED) {
                    //return 12 because it forces folder sync
                    _HandleSyncError(SYNC_STATUS_FOLDER_HIERARCHY_CHANGED);
                    return true;
                }
            }
            if (count($collections) == 0) {
                $error = 1;
                break;
            }
            for ($i = 0; $i < count($collections); $i++) {
                $collection = $collections[$i];
                $class = $collection['onlyoptionbodypreference'] === false ? $collection["class"] : $collection["optionfoldertype"];
                if ($class == "SMS" && !isset($collection['nextsmssync'])) {
                    $collection['nextsmssync'] = 0;
                }
                unset($state);
                unset($exporter);
                if ($class != "SMS" || $class == "SMS" && $collection['nextsmssync'] < time()) {
                    // Checking SMS Folders only once per 5 minutes for changes
                    if ($class == "SMS") {
                        $collections[$i]['nextsmssync'] = time() + 300;
                        debugLog("HandleSync: SMS Items now being synceed");
                    }
                    $state = $collection['syncstate'];
                    $filtertype = isset($collection["filtertype"]) ? $collection["filtertype"] : (isset($SyncCache['collections'][$collection["collectionid"]]["filtertype"]) ? $SyncCache['collections'][$collection["collectionid"]]["filtertype"] : 0);
                    $optionfiltertype = isset($collection["optionfoldertype"]) ? isset($collection[$collection["optionfoldertype"]]["filtertype"]) ? $collection[$collection["optionfoldertype"]]["filtertype"] : (isset($SyncCache['collections'][$collection["collectionid"]][$collection["optionfoldertype"]]["filtertype"]) ? $SyncCache['collections'][$collection["collectionid"]][$collection["optionfoldertype"]]["filtertype"] : 0) : 0;
                    $collection['onlyoptionbodypreference'] = $protocolversion >= 14.0 && (!isset($collection["BodyPreference"][1]) && !isset($collection["BodyPreference"][2]) && !isset($collection["BodyPreference"][3]) && !isset($collection["BodyPreference"][4]));
                    if ($collection['onlyoptionbodypreference'] === false) {
                        $waitimporter = false;
                        $exporter = $backend->GetExporter($collection["collectionid"]);
                        $ret = $exporter->Config($waitimporter, $class, $filtertype, $state, BACKEND_DISCARD_DATA, 0, isset($collection["BodyPreference"]) ? $collection["BodyPreference"] : false, isset($collection["optionfoldertype"]) ? $collection[$collection["optionfoldertype"]]["BodyPreference"] : false);
                        // stop heartbeat if exporter can not be configured (e.g. after Zarafa-server restart)
                        if ($ret === false) {
                            debugLog("HandleSync: Sync Wait/Heartbeat error: Exporter can not be configured. Waiting 30 seconds before sync is retried.");
                            debugLog($collection["collectionid"]);
                            sleep(30);
                        }
                        $changecount = $exporter->GetChangeCount();
                        if ($changecount > 0) {
                            debugLog("HandleSync: Found " . $changecount . " change(s) in folder " . $SyncCache['folders'][$collection["collectionid"]]['displayname']);
                            $dataavailable = true;
                            $collections[$i]["getchanges"] = true;
                        }
                        // Discard any data
                        while (is_array($exporter->Synchronize())) {
                        }
                        usleep(500000);
                    }
                    if (isset($collection['optionfoldertype'])) {
                        $waitimporter = false;
                        $state = $collection[$collection['optionfoldertype'] . 'syncstate'];
                        $exporter = $backend->GetExporter($collection["collectionid"]);
                        $ret = $exporter->Config($waitimporter, $collection['optionfoldertype'], $optionfiltertype, $state, BACKEND_DISCARD_DATA, 0, false, $collection[$collection["optionfoldertype"]]["BodyPreference"]);
                        // stop heartbeat if exporter can not be configured (e.g. after Zarafa-server restart)
                        if ($ret === false) {
                            debugLog("HandleSync: Sync Wait/Heartbeat error: Exporter can not be configured. Waiting 30 seconds before sync is retried.");
                            debugLog("HandleSync: optionfoldertype: " . $collection["collectionid"]);
                            sleep(30);
                        }
                        $changecount = $exporter->GetChangeCount();
                        if ($changecount > 0) {
                            debugLog("HandleSync: Found " . $changecount . " change(s) for optionfoldertype in folder " . $SyncCache['folders'][$collection["collectionid"]]['displayname']);
                            $dataavailable = true;
                            $collections[$i]["getchanges"] = true;
                        }
                        // Discard any data
                        while (is_array($exporter->Synchronize())) {
                        }
                        usleep(500000);
                    }
                }
            }
            if ($dataavailable) {
                debugLog("HandleSync: Found change");
                break;
            }
            // Check for folder Updates
            $hierarchychanged = false;
            if ($hierarchystate >= 0 && !(strlen($hierarchystate) == 0) && !(bin2hex(substr($hierarchystate, 4, 4)) == "00000000")) {
                unset($exporter);
                $exporter = $backend->GetExporter();
                $waitimporter = false;
                $exporter->Config($waitimporter, false, false, $hierarchystate, BACKEND_DISCARD_DATA, 0, false, false);
                if ($exporter->GetChangeCount() > 0) {
                    $hierarchychanged = true;
                }
                while (is_array($exporter->Synchronize())) {
                }
                if ($hierarchychanged) {
                    debugLog("HandleSync: Found hierarchy changes during Wait/Heartbeat Interval... Sending status " . SYNC_STATUS_FOLDER_HIERARCHY_CHANGED . " to get changes (STATUS = " . SYNC_STATUS_FOLDER_HIERARCHY_CHANGED . ")");
                    _HandleSyncError(SYNC_STATUS_FOLDER_HIERARCHY_CHANGED);
                    return true;
                }
            } else {
                debugLog("HandleSync: Error in Syncstate during Wait/Heartbeat Interval... Sending status " . SYNC_STATUS_FOLDER_HIERARCHY_CHANGED . " to enforce hierarchy sync (STATUS = " . SYNC_STATUS_FOLDER_HIERARCHY_CHANGED . ")");
                _HandleSyncError(SYNC_STATUS_FOLDER_HIERARCHY_CHANGED);
                return true;
            }
            // 5 seconds sleep to keep the load low...
            sleep($timeout);
            $hbrunthisduration = microtime(true) - $hbrunstarttime;
            if ($hbrunavrgduration > 0) {
                $hbrunavrgduration = ($hbrunavrgduration + $hbrunthisduration) / 2;
            } else {
                $hbrunavrgduration = $hbrunthisduration;
            }
            if ($hbrunthisduration > $hbrunmaxduration) {
                $hbrunmaxduration = $hbrunthisduration;
            }
        }
        debugLog("HandleSync: Max Heartbeat run duration is " . $hbrunmaxduration);
        debugLog("HandleSync: Average Heartbeat run duration is " . $hbrunavrgduration);
        // Even in case we found a change, better check that no other Sync already started... If so,
        // we exit here and let the other process do the export.
        $TempSyncCache = unserialize($statemachine->getSyncCache());
        if ($TempSyncCache['timestamp'] > $SyncCache['timestamp']) {
            debugLog("HandleSync: Changes in cache determined during Sync Wait/Heartbeat, exiting here.");
            return true;
        }
    }
    // Do a short answer to allow short sync requests
    debugLog("HandleSync: dataavailable: " . ($dataavailable == true ? "Yes" : "No") . " dataimported: " . ($dataimported == true ? "Yes" : "No"));
    if ($protocolversion >= 12.1 && $SyncStatus == SYNC_STATUS_SUCCESS && $dataavailable == false && $dataimported == false && ($SyncCache['wait'] !== false || $SyncCache['hbinterval'] !== false)) {
        debugLog("HandleSync: Doing a short reply since no data is available, no data was imported and syncstatus is " . $SyncStatus);
        $SyncCache['lastsyncendnormal'] = time();
        $statemachine->setSyncCache(serialize($SyncCache));
        debugLog("HandleSync: Sync runtime = " . (microtime(true) - $sessionstarttime));
        return true;
    }
    // So there was either a change in heartbeat or a normal sync request.
    // Lets go through all collections and set getchanges to true in case collection has a valid synckey and it is not set at all in collection
    // since if omitted it should be true according to spec
    // Since we don't want to get continuesly new sync keys for collections without changes we only set it to true in case a real change is being there...
    debugLog("Looking for collections not having the getChanges option being set");
    foreach ($collections as $key => $collection) {
        if (isset($collection['synckey']) && $collection['synckey'] != '0' && !isset($collection['getchanges'])) {
            $state = $collection['syncstate'];
            $filtertype = isset($collection["filtertype"]) ? $collection["filtertype"] : (isset($SyncCache['collections'][$collection["collectionid"]]["filtertype"]) ? $SyncCache['collections'][$collection["collectionid"]]["filtertype"] : 0);
            $optionfiltertype = isset($collection["optionfoldertype"]) ? isset($collection[$collection["optionfoldertype"]]["filtertype"]) ? $collection[$collection["optionfoldertype"]]["filtertype"] : (isset($SyncCache['collections'][$collection["collectionid"]][$collection["optionfoldertype"]]["filtertype"]) ? $SyncCache['collections'][$collection["collectionid"]][$collection["optionfoldertype"]]["filtertype"] : 0) : 0;
            $collection['onlyoptionbodypreference'] = $protocolversion >= 14.0 && (!isset($collection["BodyPreference"][1]) && !isset($collection["BodyPreference"][2]) && !isset($collection["BodyPreference"][3]) && !isset($collection["BodyPreference"][4]));
            if ($collection['onlyoptionbodypreference'] === false) {
                $waitimporter = false;
                $exporter = $backend->GetExporter($collection['collectionid']);
                $ret = $exporter->Config($waitimporter, $collection['class'], $filtertype, $state, BACKEND_DISCARD_DATA, 0, isset($collection["BodyPreference"]) ? $collection["BodyPreference"] : false, isset($collection["optionfoldertype"]) ? $collection[$collection["optionfoldertype"]]["BodyPreference"] : false);
                $changecount = $exporter->GetChangeCount();
                if ($changecount > 0) {
                    $dataavailable = true;
                    $collections[$key]['getchanges'] = true;
                }
                debugLog("HandleSync: Found " . $changecount . " change(s) for foldertype in folder " . $SyncCache['folders'][$collection["collectionid"]]['displayname']);
                // Discard any data
                while (is_array($exporter->Synchronize())) {
                }
            }
            // just take care about the optionfoldertype in case the main folder class a no changes...
            if (isset($collection['optionfoldertype']) && !isset($collections[$key]['getchanges'])) {
                $waitimporter = false;
                $state = $collection[$collection['optionfoldertype'] . 'syncstate'];
                $exporter = $backend->GetExporter($collection['collectionid']);
                $ret = $exporter->Config($waitimporter, $collection['optionfoldertype'], $optionfiltertype, $state, BACKEND_DISCARD_DATA, 0, false, $collection[$collection["optionfoldertype"]]["BodyPreference"]);
                $changecount = $exporter->GetChangeCount();
                debugLog("HandleSync: Found " . $changecount . " change(s) for optionfoldertype in folder " . $SyncCache['folders'][$collection["collectionid"]]['displayname']);
                if ($changecount > 0) {
                    $dataavailable = true;
                    $collections[$key]["getchanges"] = true;
                }
                // Discard any data
                while (is_array($exporter->Synchronize())) {
                }
            }
        }
    }
    if ($dataimported == false && $fetchitems == false && ($SyncCache['wait'] === false && $SyncCache['hbinterval'] === false)) {
        unset($foundchange);
        foreach ($collections as $key => $collection) {
            if (isset($collection["getchanges"]) && $collection["getchanges"] != 0 && !isset($collection["importedchanges"]) && $collection["synckey"] != "0") {
                $foundchange = false;
                // Try to get the exporter. In case it is not possible (i.e. folder removed) set
                // status according.
                $exporter = $backend->GetExporter($collection["collectionid"]);
                debugLog("HandleSync: Exporter Value: " . is_object($exporter) . " " . (isset($exporter->exporter) ? $exporter->exporter : ""));
                $filtertype = isset($collection["filtertype"]) ? $collection["filtertype"] : (isset($SyncCache['collections'][$collection["collectionid"]]["filtertype"]) ? $SyncCache['collections'][$collection["collectionid"]]["filtertype"] : 0);
                $optionfiltertype = isset($collection["optionfoldertype"]) ? isset($collection[$collection["optionfoldertype"]]["filtertype"]) ? $collection[$collection["optionfoldertype"]]["filtertype"] : (isset($SyncCache['collections'][$collection["collectionid"]][$collection["optionfoldertype"]]["filtertype"]) ? $SyncCache['collections'][$collection["collectionid"]][$collection["optionfoldertype"]]["filtertype"] : 0) : 0;
                debugLog("HandleSync: FilterType GetChanges : " . $filtertype . " " . $optionfiltertype);
                $changecount = 0;
                if ($collection['onlyoptionbodypreference'] === false) {
                    $exporter = $backend->GetExporter($collection["collectionid"]);
                    debugLog("HandleSync: Messageclass for Export: " . $collection["class"]);
                    $exporter->Config($importer[$collection["collectionid"]], $collection["class"], $filtertype, $collection['syncstate'], 0, $collection["truncation"], $collection["BodyPreference"], false, isset($collection["mimesupport"]) ? $collection['mimesupport'] : 0);
                    $changecount = $exporter->GetChangeCount();
                }
                // Optionfoldertype
                if (isset($collection['optionfoldertype'])) {
                    $optionexporter = $backend->GetExporter($collection["collectionid"]);
                    debugLog("HandleSync: Messageclass for Export: " . $collection["optionfoldertype"]);
                    $optionexporter->Config($importer[$collection['optionfoldertype'] . $collection["collectionid"]], $collection['optionfoldertype'], $optionfiltertype, $collection[$collection['optionfoldertype'] . 'syncstate'], 0, 9, false, $collection[$collection["optionfoldertype"]]["BodyPreference"], isset($collection["mimesupport"]) ? $collection['mimesupport'] : 0);
                    $changecount = $changecount + $optionexporter->GetChangeCount();
                }
                $collections[$key]['changecount'] = $changecount;
                if ($changecount > 0) {
                    $foundchange = true;
                }
            }
        }
        if (isset($foundchange) && $foundchange == false) {
            if ($protocolversion >= 14.0) {
                debugLog("HandleSync: No changes although devices requested them. Exit silently!");
                // E2K10 Behaviour! Undocumented in Open Protocols!
                return true;
            }
        }
    }
    $encoder = new WBXMLEncoder($output, $zpushdtd);
    $encoder->startWBXML();
    $answerstarttime = microtime(true);
    $encoder->startTag(SYNC_SYNCHRONIZE);
    if ($protocolversion >= 14.0) {
        $encoder->startTag(SYNC_STATUS);
        $encoder->content($SyncStatus);
        $encoder->endTag();
    }
    $encoder->startTag(SYNC_FOLDERS);
    foreach ($collections as $collection) {
        // START ADDED dw2412 Protocol Version 12 Support
        if (isset($collection["BodyPreference"])) {
            $encoder->_bodypreference = $collection["BodyPreference"];
        }
        // END ADDED dw2412 Protocol Version 12 Support
        $folderstatus = 1;
        // dw2412 ensure that no older exporter definition exists and could be used
        // figthing against that some folder get content of another folder...
        if (isset($exporter)) {
            unset($exporter);
        }
        if (isset($optionexporter)) {
            unset($optionexporter);
        }
        if (isset($collection["getchanges"]) && $collection["getchanges"] != 0) {
            if (isset($collection['optionfoldertype'])) {
                $optionexporter = $backend->GetExporter($collection["collectionid"]);
                debugLog("HandleSync: OptionExporter Value: " . is_object($optionexporter) . " " . (isset($optionexporter->exporter) ? $optionexporter->exporter : ""));
            }
            if (isset($exporter->exporter) && $exporter->exporter === false || isset($optionexporter->exporter) && $optionexporter->exporter === false) {
                $folderstatus = SYNC_STATUS_OBJECT_NOT_FOUND;
            }
        }
        // Get a new sync key to output to the client if any changes have been requested or have been sent
        if (isset($collection["importedchanges"]) || isset($collection["getchanges"]) && $collection["getchanges"] != 0 || $collection["synckey"] == "0") {
            if (isset($collection['changecount']) && $collection['changecount'] > 0) {
                $collection["newsynckey"] = $statemachine->getNewSyncKey($collection["synckey"]);
                debugLog("HandleSync: New Synckey generated because importedchanges: " . isset($collection["importedchanges"]) . " getchanges: " . (isset($collection["getchanges"]) && $collection["getchanges"] != 0) . " changecount: " . $collection['changecount'] . " initialsync: " . ($collection["synckey"] == "0"));
            } else {
                if (!isset($collection['changecount'])) {
                    $collection["newsynckey"] = $statemachine->getNewSyncKey($collection["synckey"]);
                    debugLog("HandleSync: New Synckey generated because importedchanges: " . isset($collection["importedchanges"]) . " getchanges: " . (isset($collection["getchanges"]) && $collection["getchanges"] != 0) . " initialsync: " . ($collection["synckey"] == "0"));
                }
            }
        }
        $encoder->startTag(SYNC_FOLDER);
        // FolderType/Class is only being returned by AS up to 12.0.
        // In 12.1 it could break the sync.
        if (isset($collection["class"]) && $protocolversion <= 12.0) {
            $encoder->startTag(SYNC_FOLDERTYPE);
            $encoder->content($collection["class"]);
            $encoder->endTag();
        }
        $encoder->startTag(SYNC_SYNCKEY);
        if (isset($collection["newsynckey"])) {
            $encoder->content($collection["newsynckey"]);
        } else {
            $encoder->content($collection["synckey"]);
        }
        $encoder->endTag();
        $encoder->startTag(SYNC_FOLDERID);
        $encoder->content($collection["collectionid"]);
        $encoder->endTag();
        $encoder->startTag(SYNC_STATUS);
        $encoder->content($folderstatus);
        $encoder->endTag();
        //check the mimesupport because we need it for advanced emails
        $mimesupport = isset($collection['mimesupport']) ? $collection['mimesupport'] : 0;
        // Output server IDs for new items we received from the PDA
        if (isset($collection["clientids"]) || isset($collection["fetchids"]) && count($collection["fetchids"]) > 0 || isset($collection["changeids"])) {
            $encoder->startTag(SYNC_REPLIES);
            /*					if (isset($collection["changeids"])) {
            	                    foreach($collection["changeids"] as $serverid => $servervals) {
                	                    $encoder->startTag(SYNC_MODIFY);
            							if (isset($servervals['optionfoldertype'])) {
            		    	          	    $encoder->startTag(SYNC_FOLDERTYPE);
            							    $encoder->content($collection['optionfoldertype']);
            			    				$encoder->endTag();
            							}
            	                        $encoder->startTag(SYNC_SERVERENTRYID);
                	                    $encoder->content($serverid);
                    	                $encoder->endTag();
                        	            $encoder->startTag(SYNC_STATUS);
                            	        $encoder->content($servervals['status']);
                                	    $encoder->endTag();
                                    	$encoder->endTag();
            	                    }
            					}
            */
            foreach ($collection["clientids"] as $clientid => $servervals) {
                $encoder->startTag(SYNC_ADD);
                if (isset($clientid['optionfoldertype']) && is_array($servervals['serverid'])) {
                    $encoder->startTag(SYNC_FOLDERTYPE);
                    $encoder->content($collection['optionfoldertype']);
                    $encoder->endTag();
                }
                $encoder->startTag(SYNC_CLIENTENTRYID);
                $encoder->content($clientid);
                $encoder->endTag();
                if (is_array($servervals['serverid'])) {
                    $encoder->startTag(SYNC_SERVERENTRYID);
                    $encoder->content($servervals['serverid']['sourcekey']);
                    $encoder->endTag();
                } else {
                    $encoder->startTag(SYNC_SERVERENTRYID);
                    $encoder->content($servervals['serverid']);
                    $encoder->endTag();
                }
                $encoder->startTag(SYNC_STATUS);
                $encoder->content(1);
                $encoder->endTag();
                if (is_array($servervals['serverid'])) {
                    $encoder->startTag(SYNC_DATA);
                    $encoder->startTag(SYNC_POOMMAIL2_CONVERSATIONID);
                    $encoder->contentopaque($servervals['serverid']['convid']);
                    $encoder->endTag();
                    $encoder->startTag(SYNC_POOMMAIL2_CONVERSATIONINDEX);
                    $encoder->contentopaque($servervals['serverid']['convidx']);
                    $encoder->endTag();
                    $encoder->endTag();
                }
                $encoder->endTag();
            }
            foreach ($collection["fetchids"] as $id) {
                // CHANGED dw2412 to support bodypreference
                $data = $backend->Fetch($collection["collectionid"], $id, isset($collection["BodyPreference"]) ? $collection["BodyPreference"] : false, isset($collection["optionfoldertype"]) ? $collection[$collection["optionfoldertype"]]["BodyPreference"] : false, $mimesupport);
                if ($data !== false) {
                    $encoder->startTag(SYNC_FETCH);
                    $encoder->startTag(SYNC_SERVERENTRYID);
                    $encoder->content($id);
                    $encoder->endTag();
                    $encoder->startTag(SYNC_STATUS);
                    $encoder->content(1);
                    $encoder->endTag();
                    $encoder->startTag(SYNC_DATA);
                    $data->encode($encoder);
                    $encoder->endTag();
                    $encoder->endTag();
                } else {
                    debugLog("HandleSync: unable to fetch {$id}");
                }
            }
            $encoder->endTag();
        }
        if (isset($collection["getchanges"]) && $collection["getchanges"] != 0 || isset($collection["readids"]) || isset($collection["flagids"]) || isset($collection['optionfoldertype']) && (isset($collection[$collection['optionfoldertype'] . "readids"]) || isset($collection[$collection['optionfoldertype'] . "flagids"]))) {
            // Use the state from the importer, as changes may have already happened
            $filtertype = isset($collection["filtertype"]) ? $collection["filtertype"] : (isset($SyncCache['collections'][$collection["collectionid"]]["filtertype"]) ? $SyncCache['collections'][$collection["collectionid"]]["filtertype"] : 0);
            $optionfiltertype = isset($collection["optionfoldertype"]) ? isset($collection[$collection["optionfoldertype"]]["filtertype"]) ? $collection[$collection["optionfoldertype"]]["filtertype"] : (isset($SyncCache['collections'][$collection["collectionid"]][$collection["optionfoldertype"]]["filtertype"]) ? $SyncCache['collections'][$collection["collectionid"]][$collection["optionfoldertype"]]["filtertype"] : 0) : 0;
            debugLog("HandleSync: FilterType GetChanges : " . $filtertype . " " . $optionfiltertype);
            $changecount = 0;
            if ($collection['onlyoptionbodypreference'] === false) {
                $exporter = $backend->GetExporter($collection["collectionid"]);
                debugLog("HandleSync: Messageclass for Export: " . $collection["class"]);
                $exporter->Config($importer[$collection["collectionid"]], $collection["class"], $filtertype, $collection['syncstate'], 0, $collection["truncation"], $collection["BodyPreference"], false, isset($collection["mimesupport"]) ? $collection['mimesupport'] : 0);
                $changecount = $exporter->GetChangeCount();
            }
            // Optionfoldertype
            if (isset($collection['optionfoldertype'])) {
                $optionexporter = $backend->GetExporter($collection["collectionid"]);
                debugLog("HandleSync: Messageclass for Export: " . $collection["optionfoldertype"]);
                $optionexporter->Config($importer[$collection['optionfoldertype'] . $collection["collectionid"]], $collection['optionfoldertype'], $optionfiltertype, $collection[$collection['optionfoldertype'] . 'syncstate'], 0, 9, false, $collection[$collection["optionfoldertype"]]["BodyPreference"], isset($collection["mimesupport"]) ? $collection['mimesupport'] : 0);
                $changecount = $changecount + $optionexporter->GetChangeCount();
            }
            debugLog("HandleSync: Changecount vs maxitems: " . $changecount . " " . $collection["maxitems"]);
            if ($changecount > $collection["maxitems"]) {
                $encoder->startTag(SYNC_MOREAVAILABLE, false, true);
            }
            // Output message changes per folder
            $encoder->startTag(SYNC_PERFORM);
            $n = 0;
            // Stream the changes to the PDA
            if ($collection['onlyoptionbodypreference'] === false) {
                $ids = array("readids" => isset($collection["readids"]) ? $collection["readids"] : array(), "flagids" => isset($collection["flagids"]) ? $collection["flagids"] : array());
                $importer[$collection["collectionid"]] = new ImportContentsChangesStream($encoder, GetObjectClassFromFolderClass($collection["class"]), $ids, $msginfos[$collection["collectionid"]]);
                while (1) {
                    $progress = $exporter->Synchronize();
                    if (!is_array($progress)) {
                        break;
                    }
                    if ($importer[$collection["collectionid"]]->_lastObjectStatus == 1) {
                        $n++;
                    }
                    debugLog("HandleSync: _lastObjectStatus = " . $importer[$collection["collectionid"]]->_lastObjectStatus);
                    if ($n >= $collection["maxitems"]) {
                        debugLog("HandleSync: Exported maxItems of messages: " . $collection["maxitems"] . " - more available");
                        break;
                    }
                }
                $msginfos[$collection["collectionid"]] = $importer[$collection["collectionid"]]->_msginfos;
            }
            if (isset($collection['optionfoldertype'])) {
                $ids = array("readids" => isset($collection[$collection['optionfoldertype'] . "readids"]) ? $collection[$collection['optionfoldertype'] . "readids"] : array(), "flagids" => isset($collection[$collection['optionfoldertype'] . "flagids"]) ? $collection[$collection['optionfoldertype'] . "flagids"] : array());
                $importer[$collection['optionfoldertype'] . $collection["collectionid"]] = new ImportContentsChangesStream($encoder, GetObjectClassFromFolderClass($collection["optionfoldertype"]), $ids, $msginfos[$collection["collectionid"]]);
                while (1) {
                    $progress = $optionexporter->Synchronize();
                    if (!is_array($progress)) {
                        break;
                    }
                    if ($importer[$collection['optionfoldertype'] . $collection["collectionid"]]->_lastObjectStatus == 1) {
                        $n++;
                    }
                    debugLog("HandleSync: _lastObjectStatus = " . $importer[$collection['optionfoldertype'] . $collection["collectionid"]]->_lastObjectStatus);
                    if ($n >= $collection["maxitems"]) {
                        debugLog("HandleSync: Exported maxItems of messages: " . $collection["maxitems"] . " - more available");
                        break;
                    }
                }
                $msginfos[$collection["collectionid"]] = $importer[$collection['optionfoldertype'] . $collection["collectionid"]]->_msginfos;
            }
            // START HACK: CURRENT ICS EXPORTER DOES NOT PROVIDE READ STATE AND FLAG UPDATES IF SEND FROM DEVICE. THIS WE DO HERE JUST BECAUSE OF THIS!
            if ($collection['onlyoptionbodypreference'] === false) {
                $array_rf = array_unique(array_merge(array_keys(isset($importer[$collection["collectionid"]]) ? $importer[$collection["collectionid"]]->_readids : array()), array_keys(isset($importer[$collection["collectionid"]]) ? $importer[$collection["collectionid"]]->_flagids : array())));
                debugLog("HandleSync: After Exporting Changes we still have following array_rf in importer: " . print_r($array_rf, true));
                $class = GetObjectClassFromFolderClass($collection["class"]);
                foreach ($array_rf as $rfid) {
                    $encoder->startTag(SYNC_MODIFY);
                    if (!isset($msginfos[$collection["collectionid"]][$rfid])) {
                        unset($importer[$collection["collectionid"]]->_readids[$rfid]);
                        unset($importer[$collection["collectionid"]]->_flagids[$rfid]);
                        $encoder->startTag(SYNC_SERVERENTRYID);
                        $encoder->content($rfid);
                        $encoder->endTag();
                        $encoder->startTag(SYNC_STATUS);
                        $encoder->content(SYNC_STATUS_OBJECT_NOT_FOUND);
                        $encoder->endTag();
                        $encoder->endTag();
                        continue;
                    }
                    if ($msginfos[$collection["collectionid"]][$rfid]['class'] != $class) {
                        continue;
                    }
                    $encoder->startTag(SYNC_SERVERENTRYID);
                    $encoder->content($rfid);
                    $encoder->endTag();
                    $encoder->startTag(SYNC_DATA);
                    if (isset($importer[$collection["collectionid"]]->_readids[$rfid]) && $importer[$collection["collectionid"]]->_readids[$rfid]['status'] == true) {
                        $encoder->startTag(SYNC_POOMMAIL_READ);
                        $encoder->content($importer[$collection["collectionid"]]->_readids[$rfid]['data']);
                        $encoder->endTag();
                        unset($importer[$collection["collectionid"]]->_readids[$rfid]);
                    }
                    if (isset($importer[$collection["collectionid"]]->_flagids[$rfid]) && $importer[$collection["collectionid"]]->_flagids[$rfid]['status'] == true) {
                        if (!isset($importer[$collection["collectionid"]]->_flagids[$rfid]['data']->flagstatus) || $importer[$collection["collectionid"]]->_flagids[$rfid]['data']->flagstatus == 0 || $importer[$collection["collectionid"]]->_flagids[$rfid]['data']->flagstatus == "") {
                            $encoder->startTag(SYNC_POOMMAIL_FLAG, false, true);
                        } else {
                            $encoder->startTag(SYNC_POOMMAIL_FLAG);
                            $importer[$collection["collectionid"]]->_flagids[$rfid]['data']->encode($importer[$collection["collectionid"]]->_encoder);
                            $encoder->endTag();
                        }
                        unset($importer[$collection["collectionid"]]->_flagids[$rfid]);
                    }
                    $encoder->endTag();
                    $encoder->endTag();
                }
                unset($array_rf);
                $array_rf = array_keys(array_merge(isset($importer[$collection["collectionid"]]) ? $importer[$collection["collectionid"]]->_readids : array(), isset($importer[$collection["collectionid"]]) ? $importer[$collection["collectionid"]]->_flagids : array()));
                debugLog("HandleSync: After manual export of read and flag changes we still have following array_rf in importer: " . print_r($array_rf, true));
                unset($array_rf);
            }
            if (isset($collection["optionfoldertype"])) {
                $array_rf = array_unique(array_merge(array_keys(isset($importer[$collection["optionfoldertype"] . $collection["collectionid"]]) ? $importer[$collection["optionfoldertype"] . $collection["collectionid"]]->_flagids : array()), array_keys(isset($importer[$collection["optionfoldertype"] . $collection["collectionid"]]) ? $importer[$collection["optionfoldertype"] . $collection["collectionid"]]->_readids : array())));
                debugLog("HandleSync: After Exporting Changes we still have following array_rf in importer for optionfoldertype: " . print_r($array_rf, true));
                $class = GetObjectClassFromFolderClass($collection["optionfoldertype"]);
                foreach ($array_rf as $rfid) {
                    $encoder->startTag(SYNC_MODIFY);
                    $encoder->startTag(SYNC_FOLDERTYPE);
                    $encoder->content($collection["optionfoldertype"]);
                    $encoder->endTag();
                    if (!isset($msginfos[$collection["collectionid"]][$rfid])) {
                        unset($importer[$collection["optionfoldertype"] . $collection["collectionid"]]->_readids[$rfid]);
                        unset($importer[$collection["optionfoldertype"] . $collection["collectionid"]]->_flagids[$rfid]);
                        $encoder->startTag(SYNC_SERVERENTRYID);
                        $encoder->content($rfid);
                        $encoder->endTag();
                        $encoder->startTag(SYNC_STATUS);
                        $encoder->content(SYNC_STATUS_OBJECT_NOT_FOUND);
                        $encoder->endTag();
                        $encoder->endTag();
                        continue;
                    }
                    if ($msginfos[$collection["collectionid"]][$rfid]['class'] != $class) {
                        continue;
                    }
                    $encoder->startTag(SYNC_SERVERENTRYID);
                    $encoder->content($rfid);
                    $encoder->endTag();
                    $encoder->startTag(SYNC_DATA);
                    if (isset($importer[$collection["optionfoldertype"] . $collection["collectionid"]]->_readids[$rfid]) && $importer[$collection["optionfoldertype"] . $collection["collectionid"]]->_readids[$rfid]['status'] == true) {
                        $encoder->startTag(SYNC_POOMMAIL_READ);
                        $encoder->content($importer[$collection["optionfoldertype"] . $collection["collectionid"]]->_readids[$rfid]['data']);
                        $encoder->endTag();
                        unset($importer[$collection["optionfoldertype"] . $collection["collectionid"]]->_readids[$rfid]);
                    }
                    if (isset($importer[$collection["optionfoldertype"] . $collection["collectionid"]]->_flagids[$rfid]) && $importer[$collection["optionfoldertype"] . $collection["collectionid"]]->_flagids[$rfid]['status'] == true) {
                        if (!isset($importer[$collection["optionfoldertype"] . $collection["collectionid"]]->_flagids[$rfid]['data']->flagstatus) || $importer[$collection["optionfoldertype"] . $collection["collectionid"]]->_flagids[$rfid]['data']->flagstatus == 0 || $importer[$collection["optionfoldertype"] . $collection["collectionid"]]->_flagids[$rfid]['data']->flagstatus == "") {
                            $encoder->startTag(SYNC_POOMMAIL_FLAG, false, true);
                        } else {
                            $encoder->startTag(SYNC_POOMMAIL_FLAG);
                            $importer[$collection["optionfoldertype"] . $collection["collectionid"]]->_flagids[$rfid]['data']->encode($importer[$collection["optionfoldertype"] . $collection["collectionid"]]->_encoder);
                            $encoder->endTag();
                        }
                        unset($importer[$collection["optionfoldertype"] . $collection["collectionid"]]->_flagids[$rfid]);
                    }
                    $encoder->endTag();
                    $encoder->endTag();
                }
                unset($array_rf);
                $array_rf = array_keys(array_merge($importer[$collection["optionfoldertype"] . $collection["collectionid"]]->_readids, $importer[$collection["optionfoldertype"] . $collection["collectionid"]]->_flagids));
                debugLog("HandleSync: After manual export of read and flag changes we still have following array_rf in importer for optionfoldertype: " . print_r($array_rf, true));
                unset($array_rf);
            }
            // END HACK: CURRENT ICS EXPORTER DOES NOT PROVIDE READ STATE AND FLAG UPDATES IF SEND FROM DEVICE. THIS WE DO HERE JUST BECAUSE OF THIS!
            $encoder->endTag();
        }
        $encoder->endTag();
        // Save the sync state for the next time
        if (isset($collection["newsynckey"])) {
            unset($state);
            unset($optionstate);
            if (isset($exporter) && $exporter) {
                $state = $exporter->GetState();
            } else {
                if (isset($importer[$collection["collectionid"]]) && $importer[$collection["collectionid"]]) {
                    $state = $importer[$collection["collectionid"]]->GetState();
                } else {
                    if ($collection["synckey"] == "0") {
                        $state = "";
                    }
                }
            }
            if (isset($optionexporter) && $optionexporter) {
                $optionstate = $optionexporter->GetState();
            } else {
                if (isset($collection['optionfoldertype']) && isset($importer[$collection['optionfoldertype'] . $collection["collectionid"]]) && is_object($importer[$collection['optionfoldertype'] . $collection["collectionid"]])) {
                    $optionstate = $importer[$collection['optionfoldertype'] . $collection["collectionid"]]->GetState();
                } else {
                    if ($collection["synckey"] == "0") {
                        $optionstate = "";
                    }
                }
            }
            if (isset($state)) {
                $statemachine->setSyncState($collection["newsynckey"], $state);
            } else {
                debugLog("HandleSync: error saving " . $collection["newsynckey"] . " - no state information available");
            }
            if (isset($optionstate) && isset($collection['optionfoldertype'])) {
                $statemachine->setSyncState($collection['optionfoldertype'] . $collection["newsynckey"], $optionstate);
            } else {
                if (isset($collection['optionfoldertype'])) {
                    debugLog("HandleSync: error saving " . $collection['optionfoldertype'] . $collection["newsynckey"] . " - no state information available");
                }
            }
            if (trim($collection['newsynckey']) != trim($collection['synckey'])) {
                debugLog("HandleSync: Current Synckey: " . $collection['synckey'] . " New Synckey: " . $collection['newsynckey']);
                $SyncCache['confirmed_synckeys'][$collection['newsynckey']] = true;
                $statemachine->setSyncState('mi' . $collection['newsynckey'], isset($msginfos[$collection['collectionid']]) ? serialize($msginfos[$collection['collectionid']]) : serialize(array()));
            }
        }
        if (isset($collection['collectionid'])) {
            if (isset($collection['newsynckey'])) {
                $SyncCache['collections'][$collection['collectionid']]['synckey'] = $collection['newsynckey'];
            } else {
                $SyncCache['collections'][$collection['collectionid']]['synckey'] = $collection['synckey'];
            }
            if (isset($collection['class'])) {
                $SyncCache['collections'][$collection['collectionid']]['class'] = $collection['class'];
            }
            if (isset($collection['maxitems'])) {
                $SyncCache['collections'][$collection['collectionid']]['maxitems'] = $collection['maxitems'];
            }
            if (isset($collection['deletesasmoves'])) {
                $SyncCache['collections'][$collection['collectionid']]['deletesasmoves'] = $collection['deletesasmoves'];
            }
            if (isset($collection['conversationmode'])) {
                $SyncCache['collections'][$collection['collectionid']]['conversationmode'] = $collection['conversationmode'];
            }
            if (isset($SyncCache['collections'][$collection['collectionid']]['getchanges'])) {
                unset($SyncCache['collections'][$collection['collectionid']]['getchanges']);
            }
            if (isset($collection['filtertype'])) {
                $SyncCache['collections'][$collection['collectionid']]['filtertype'] = $collection['filtertype'];
            }
            if (isset($collection['truncation'])) {
                $SyncCache['collections'][$collection['collectionid']]['truncation'] = $collection['truncation'];
            }
            if (isset($collection['rtftruncation'])) {
                $SyncCache['collections'][$collection['collectionid']]['rtftruncation'] = $collection['rtftruncation'];
            }
            if (isset($collection['mimesupport'])) {
                $SyncCache['collections'][$collection['collectionid']]['mimesupport'] = $collection['mimesupport'];
            }
            if (isset($collection['mimetruncation'])) {
                $SyncCache['collections'][$collection['collectionid']]['mimetruncation'] = $collection['mimetruncation'];
            }
            if (isset($collection['conflict'])) {
                $SyncCache['collections'][$collection['collectionid']]['conflict'] = $collection['conflict'];
            }
            if (isset($collection['BodyPreference'])) {
                $SyncCache['collections'][$collection['collectionid']]['BodyPreference'] = $collection['BodyPreference'];
            }
            if (isset($collection['optionfoldertype'])) {
                $SyncCache['collections'][$collection['collectionid']]['optionfoldertype'] = $collection['optionfoldertype'];
                if (isset($collection[$collection['optionfoldertype']]['filtertype'])) {
                    $SyncCache['collections'][$collection['collectionid']][$collection['optionfoldertype']]['filtertype'] = $collection[$collection['optionfoldertype']]['filtertype'];
                }
                if (isset($collection[$collection['optionfoldertype']]['BodyPreference'])) {
                    $SyncCache['collections'][$collection['collectionid']][$collection['optionfoldertype']]['BodyPreference'] = $collection[$collection['optionfoldertype']]['BodyPreference'];
                }
            }
        }
    }
    $encoder->endTag();
    $encoder->endTag();
    debugLog("HandleSync: Answer prepare duration run " . (microtime(true) - $answerstarttime));
    $TempSyncCache = unserialize($statemachine->getSyncCache());
    if (isset($SyncCache['timestamp']) && $TempSyncCache['timestamp'] > $SyncCache['timestamp']) {
        debugLog("HandleSync: Changes in cache determined during Sync Wait/Heartbeat, exiting here. SyncCache not updated!");
        debugLog("HandleSync: Sync runtime = " . (microtime(true) - $sessionstarttime));
        return true;
    } else {
        $SyncCache['lastsyncendnormal'] = time();
        $statemachine->setSyncCache(serialize($SyncCache));
        debugLog("HandleSync: Sync runtime = " . (microtime(true) - $sessionstarttime));
    }
    return true;
}