function HandleFolderSync($backend, $devid, $protocolversion) { global $zpushdtd; global $input, $output; global $useragent; global $user; // Maps serverid -> clientid for items that are received from the PIM $map = array(); $decoder = new WBXMLDecoder($input, $zpushdtd); $encoder = new WBXMLEncoder($output, $zpushdtd); // Parse input if (!$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_FOLDERSYNC)) { return false; } if (!$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_SYNCKEY)) { return false; } $synckey = $decoder->getElementContent(); if (!$decoder->getElementEndTag()) { return false; } // First, get the syncstate that is associated with this synckey $statemachine = new StateMachine($devid, $user); // The state machine will discard any sync states before this one, as they are no // longer required $syncstate = $statemachine->getSyncState($synckey); // Get Foldercache $SyncCache = unserialize($statemachine->getSyncCache()); if (isset($SyncCache['folders']) && is_array($SyncCache['folders'])) { foreach ($SyncCache['folders'] as $key => $value) { if (!isset($value['class'])) { $statemachine->deleteSyncCache(); _ErrorHandleFolderSync("9"); return true; } $exporter = $backend->GetExporter($key); if (isset($exporter->exporter) && $exporter->exporter === false) { unset($SyncCache['folders'][$key]); } } } // additional information about already seen folders if ($synckey != "0") { $seenfolders = $statemachine->getSyncState("s" . $synckey); } // if we have any error with one of the requests bail out here! if ($synckey != "0" && is_numeric($seenfolders) && $seenfolders < 0 || is_numeric($syncstate) && $syncstate < 0) { // if we get a numeric syncstate back it means we have an error... debugLog("GetSyncState ERROR (Seenfolders: " . abs($seenfolders) . ", Syncstate: " . abs($syncstate) . ")"); if ($seenfolders < 0) { $status = abs($seenfolders); } if ($syncstate < 0) { $status = abs($syncstate); } // Output our WBXML reply now _ErrorHandleFolderSync(abs($status)); return true; } else { $foldercache = unserialize($statemachine->getSyncCache()); // Clear the foldercache in SyncCache in case the SyncKey = 0 if ($synckey == "0") { // $statemachine->deleteSyncCache(); unset($foldercache['folders']); debugLog("Clean the folders in foldercache"); } debugLog("GetSyncState OK"); } if ($synckey == "0" && (!isset($seenfolders) || is_numeric($seenfolders) && $seenfolders < 0)) { $seenfolders = false; } $seenfolders = unserialize($seenfolders); if (!$seenfolders) { $seenfolders = array(); } if (!$foldercache || !is_array($foldercache)) { $foldercache = array(); } // lets clean the old state files away... if ($synckey != "0") { $statemachine->cleanOldSyncState("s" . $synckey); if (($delstatus = $statemachine->cleanOldSyncState($synckey)) !== true) { _ErrorHandleFolderSync(abs($delstatus)); return true; } } // We will be saving the sync state under 'newsynckey' $newsynckey = $statemachine->getNewSyncKey($synckey); $changes = false; if ($decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_CHANGES)) { // Ignore <Count> if present if ($decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_COUNT)) { $decoder->getElementContent(); if (!$decoder->getElementEndTag()) { return false; } } // Process the changes (either <Add>, <Modify>, or <Remove>) $element = $decoder->getElement(); if ($element[EN_TYPE] != EN_TYPE_STARTTAG) { return false; } while (1) { $folder = new SyncFolder(); if (!$folder->decode($decoder)) { break; } // Configure importer with last state $importer = $backend->GetHierarchyImporter(); $importer->Config($syncstate); switch ($element[EN_TAG]) { case SYNC_ADD: case SYNC_MODIFY: $serverid = $importer->ImportFolderChange($folder); $statemachine->updateSyncCacheFolder($foldercache, $serverid, $folder->parentid, $folder->displayname, $folder->type); // add folder to the serverflags $seenfolders[] = $serverid; $changes = true; break; case SYNC_REMOVE: $serverid = $importer->ImportFolderDeletion($folder); // remove folder from the folderflags array $changes = true; if (($sid = array_search($serverid, $seenfolders)) !== false) { unset($seenfolders[$sid]); $seenfolders = array_values($seenfolders); } $statemachine->deleteSyncCacheFolder($foldercache, $serverid); break; } if ($serverid) { $map[$serverid] = $folder->clientid; } } if (!$decoder->getElementEndTag()) { return false; } } if (!$decoder->getElementEndTag()) { return false; } // We have processed incoming foldersync requests, now send the PIM // our changes // The MemImporter caches all imports in-memory, so we can send a change count // before sending the actual data. As the amount of data done in this operation // is rather low, this is not memory problem. Note that this is not done when // sync'ing messages - we let the exporter write directly to WBXML. $importer = new ImportHierarchyChangesMem($encoder); // Request changes from backend, they will be sent to the MemImporter passed as the first // argument, which stores them in $importer. Returns the new sync state for this exporter. $exporter = $backend->GetExporter(); $exporter->Config($importer, false, false, $syncstate, 0, 0, false, false); while (is_array($exporter->Synchronize())) { } // Output our WBXML reply now $encoder->StartWBXML(); $encoder->startTag(SYNC_FOLDERHIERARCHY_FOLDERSYNC); $encoder->startTag(SYNC_FOLDERHIERARCHY_STATUS); $encoder->content(1); $encoder->endTag(); $encoder->startTag(SYNC_FOLDERHIERARCHY_SYNCKEY); $encoder->content($changes || $importer->count > 0 ? $newsynckey : $synckey); $foldercache['hierarchy']['synckey'] = $changes || $importer->count > 0 ? $newsynckey : $synckey; $encoder->endTag(); $encoder->startTag(SYNC_FOLDERHIERARCHY_CHANGES); // remove unnecessary updates where details between cache and real folder are equal if (count($importer->changed) > 0) { foreach ($importer->changed as $key => $folder) { if (isset($folder->serverid) && in_array($folder->serverid, $seenfolders) && isset($foldercache['folders'][$folder->serverid]) && $foldercache['folders'][$folder->serverid]['parentid'] == $folder->parentid && $foldercache['folders'][$folder->serverid]['displayname'] == $folder->displayname && $foldercache['folders'][$folder->serverid]['type'] == $folder->type) { debugLog("Ignoring " . $folder->serverid . " from importer->changed because it is folder update requests!"); unset($importer->changed[$key]); $importer->count--; } } } // remove unnecessary deletes where folders never got sent to the device if (count($importer->deleted) > 0) { foreach ($importer->deleted as $key => $folder) { if (($sid = array_search($folder, $seenfolders)) === false) { debugLog("Removing {$folder} from importer->deleted because sid {$sid} (not in seenfolders)!"); unset($importer->deleted[$key]); $importer->count--; } } } $encoder->startTag(SYNC_FOLDERHIERARCHY_COUNT); $encoder->content($importer->count); $encoder->endTag(); if (count($importer->changed) > 0) { foreach ($importer->changed as $folder) { if (isset($folder->serverid) && in_array($folder->serverid, $seenfolders)) { $encoder->startTag(SYNC_FOLDERHIERARCHY_UPDATE); } else { $seenfolders[] = $folder->serverid; $encoder->startTag(SYNC_FOLDERHIERARCHY_ADD); } $statemachine->updateSyncCacheFolder($foldercache, $folder->serverid, $folder->parentid, $folder->displayname, $folder->type); $folder->encode($encoder); $encoder->endTag(); } } if (count($importer->deleted) > 0) { foreach ($importer->deleted as $folder) { if (($sid = array_search($folder, $seenfolders)) !== false) { $encoder->startTag(SYNC_FOLDERHIERARCHY_REMOVE); $encoder->startTag(SYNC_FOLDERHIERARCHY_SERVERENTRYID); $encoder->content($folder); $encoder->endTag(); $encoder->endTag(); // remove folder from the folderflags array unset($seenfolders[$sid]); $statemachine->deleteSyncCacheFolder($foldercache, $folder); $seenfolders = array_values($seenfolders); } else { debugLog("Don't send {$folder} because sid {$sid} (not in seenfolders)!"); } } } $encoder->endTag(); $encoder->endTag(); // Save the sync state for the next time $syncstate = $exporter->GetState(); $statemachine->setSyncState($newsynckey, $syncstate); $statemachine->setSyncState("s" . $newsynckey, serialize($seenfolders)); // Remove collections from foldercache for that no folder exists if (isset($foldercache['collections'])) { foreach ($foldercache['collections'] as $key => $value) { if (!isset($foldercache['folders'][$key])) { unset($foldercache['collections'][$key]); } } } $statemachine->setSyncCache(serialize($foldercache), true); return true; }