/** * 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; }