function HandleSync($backend, $protocolversion, $devid) { global $zpushdtd; global $input, $output; // Contains all containers requested $collections = array(); // Init WBXML decoder $decoder = new WBXMLDecoder($input, $zpushdtd); // Init state machine $statemachine = new StateMachine(); // Start decode if (!$decoder->getElementStartTag(SYNC_SYNCHRONIZE)) { return false; } if (!$decoder->getElementStartTag(SYNC_FOLDERS)) { return false; } while ($decoder->getElementStartTag(SYNC_FOLDER)) { $collection = array(); $collection["truncation"] = SYNC_TRUNCATION_ALL; $collection["clientids"] = array(); $collection["fetchids"] = array(); if (!$decoder->getElementStartTag(SYNC_FOLDERTYPE)) { return false; } $collection["class"] = $decoder->getElementContent(); debugLog("Sync folder:{$collection["class"]}"); if (!$decoder->getElementEndTag()) { return false; } if (!$decoder->getElementStartTag(SYNC_SYNCKEY)) { return false; } $collection["synckey"] = $decoder->getElementContent(); if (!$decoder->getElementEndTag()) { return false; } if ($decoder->getElementStartTag(SYNC_FOLDERID)) { $collection["collectionid"] = $decoder->getElementContent(); if (!$decoder->getElementEndTag()) { return false; } } if ($decoder->getElementStartTag(SYNC_SUPPORTED)) { while (1) { $el = $decoder->getElement(); if ($el[EN_TYPE] == EN_TYPE_ENDTAG) { break; } } } if ($decoder->getElementStartTag(SYNC_DELETESASMOVES)) { $collection["deletesasmoves"] = true; } if ($decoder->getElementStartTag(SYNC_GETCHANGES)) { $collection["getchanges"] = true; } if ($decoder->getElementStartTag(SYNC_MAXITEMS)) { $collection["maxitems"] = $decoder->getElementContent(); if (!$decoder->getElementEndTag()) { return false; } } if ($decoder->getElementStartTag(SYNC_OPTIONS)) { while (1) { if ($decoder->getElementStartTag(SYNC_FILTERTYPE)) { $collection["filtertype"] = $decoder->getElementContent(); if (!$decoder->getElementEndTag()) { return false; } } if ($decoder->getElementStartTag(SYNC_TRUNCATION)) { $collection["truncation"] = $decoder->getElementContent(); if (!$decoder->getElementEndTag()) { return false; } } if ($decoder->getElementStartTag(SYNC_RTFTRUNCATION)) { $collection["rtftruncation"] = $decoder->getElementContent(); if (!$decoder->getElementEndTag()) { return false; } } if ($decoder->getElementStartTag(SYNC_MIMESUPPORT)) { $collection["mimesupport"] = $decoder->getElementContent(); if (!$decoder->getElementEndTag()) { return false; } } if ($decoder->getElementStartTag(SYNC_MIMETRUNCATION)) { $collection["mimetruncation"] = $decoder->getElementContent(); if (!$decoder->getElementEndTag()) { return false; } } if ($decoder->getElementStartTag(SYNC_CONFLICT)) { $collection["conflict"] = $decoder->getElementContent(); if (!$decoder->getElementEndTag()) { return false; } } $e = $decoder->peek(); if ($e[EN_TYPE] == EN_TYPE_ENDTAG) { $decoder->getElementEndTag(); break; } } } // compatibility mode - get folderid from the state directory if (!isset($collection["collectionid"])) { $collection["collectionid"] = _getFolderID($devid, $collection["class"]); } // compatibility mode - set default conflict behavior if no conflict resolution algorithm is set (OVERWRITE_PIM) if (!isset($collection["conflict"])) { $collection["conflict"] = 1; } //compatibility mode - set maxitems if the client doesn't send it as it breaks some devices if (!isset($collection["maxitems"])) { $collection["maxitems"] = 100; } // Get our sync state for this collection $collection["syncstate"] = $statemachine->getSyncState($collection["synckey"]); if ($decoder->getElementStartTag(SYNC_PERFORM)) { // Configure importer with last state $importer = $backend->GetContentsImporter($collection["collectionid"]); $importer->Config($collection["syncstate"], $collection["conflict"]); $nchanges = 0; while (1) { $element = $decoder->getElement(); // MODIFY or REMOVE or ADD or FETCH if ($element[EN_TYPE] != EN_TYPE_STARTTAG) { $decoder->ungetElement($element); break; } $nchanges++; if ($decoder->getElementStartTag(SYNC_SERVERENTRYID)) { $serverid = $decoder->getElementContent(); if (!$decoder->getElementEndTag()) { // end serverid return false; } } else { $serverid = false; } if ($decoder->getElementStartTag(SYNC_CLIENTENTRYID)) { $clientid = $decoder->getElementContent(); if (!$decoder->getElementEndTag()) { // end clientid return false; } } else { $clientid = false; } // Get application data if available if ($decoder->getElementStartTag(SYNC_DATA)) { switch ($collection["class"]) { case "Email": $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; } if (!$decoder->getElementEndTag()) { // end applicationdata return false; } } switch ($element[EN_TAG]) { case SYNC_MODIFY: if (isset($appdata)) { if (isset($appdata->read)) { // Currently, 'read' is only sent by the PDA when it is ONLY setting the read flag. $importer->ImportMessageReadFlag($serverid, $appdata->read); } else { $importer->ImportMessageChange($serverid, $appdata); } $collection["importedchanges"] = true; } break; case SYNC_ADD: if (isset($appdata)) { $id = $importer->ImportMessageChange(false, $appdata); if ($clientid && $id) { $collection["clientids"][$clientid] = $id; $collection["importedchanges"] = true; } } break; case SYNC_REMOVE: if (isset($collection["deletesasmoves"])) { $folderid = $backend->GetWasteBasket(); if ($folderid) { $importer->ImportMessageMove($serverid, $folderid); $collection["importedchanges"] = true; break; } } $importer->ImportMessageDeletion($serverid); $collection["importedchanges"] = true; break; case SYNC_FETCH: array_push($collection["fetchids"], $serverid); break; } if (!$decoder->getElementEndTag()) { // end change/delete/move return false; } } debugLog("Processed {$nchanges} incoming changes"); // Save the updated state, which is used for the exporter later $collection["syncstate"] = $importer->getState(); if (!$decoder->getElementEndTag()) { // end commands return false; } } if (!$decoder->getElementEndTag()) { // end collection return false; } array_push($collections, $collection); } if (!$decoder->getElementEndTag()) { // end collections return false; } if (!$decoder->getElementEndTag()) { // end sync return false; } $encoder = new WBXMLEncoder($output, $zpushdtd); $encoder->startWBXML(); $encoder->startTag(SYNC_SYNCHRONIZE); $encoder->startTag(SYNC_FOLDERS); foreach ($collections as $collection) { // initialize exporter to get changecount $changecount = 0; if (isset($collection["getchanges"])) { // Use the state from the importer, as changes may have already happened $exporter = $backend->GetExporter($collection["collectionid"]); $filtertype = isset($collection["filtertype"]) ? $collection["filtertype"] : false; $exporter->Config($importer, $collection["class"], $filtertype, $collection["syncstate"], 0, $collection["truncation"]); $changecount = $exporter->GetChangeCount(); } // Get a new sync key to output to the client if any changes have been requested or will be send if (isset($collection["importedchanges"]) || $changecount > 0 || $collection["synckey"] == "0") { $collection["newsynckey"] = $statemachine->getNewSyncKey($collection["synckey"]); } $encoder->startTag(SYNC_FOLDER); $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(1); $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"]) || count($collection["fetchids"]) > 0) { $encoder->startTag(SYNC_REPLIES); foreach ($collection["clientids"] as $clientid => $serverid) { $encoder->startTag(SYNC_ADD); $encoder->startTag(SYNC_CLIENTENTRYID); $encoder->content($clientid); $encoder->endTag(); $encoder->startTag(SYNC_SERVERENTRYID); $encoder->content($serverid); $encoder->endTag(); $encoder->startTag(SYNC_STATUS); $encoder->content(1); $encoder->endTag(); $encoder->endTag(); } foreach ($collection["fetchids"] as $id) { $data = $backend->Fetch($collection["collectionid"], $id, $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("unable to fetch {$id}"); } } $encoder->endTag(); } if (isset($collection["getchanges"])) { // exporter already intialized if ($changecount > $collection["maxitems"]) { $encoder->startTag(SYNC_MOREAVAILABLE, false, true); } // Output message changes per folder $encoder->startTag(SYNC_PERFORM); // Stream the changes to the PDA $importer = new ImportContentsChangesStream($encoder, GetObjectClassFromFolderClass($collection["class"])); $filtertype = isset($collection["filtertype"]) ? $collection["filtertype"] : 0; $n = 0; while (1) { $progress = $exporter->Synchronize(); if (!is_array($progress)) { break; } $n++; if ($n >= $collection["maxitems"]) { debugLog("Exported maxItems of messages: " . $collection["maxitems"] . " - more available"); break; } } $encoder->endTag(); } $encoder->endTag(); // Save the sync state for the next time if (isset($collection["newsynckey"])) { if (isset($exporter) && $exporter) { $state = $exporter->GetState(); } else { if (isset($importer) && $importer) { $state = $importer->GetState(); } else { if ($collection["synckey"] == "0") { $state = ""; } } } if (isset($state)) { $statemachine->setSyncState($collection["newsynckey"], $state); } else { debugLog("error saving " . $collection["newsynckey"] . " - no state information available"); } } } $encoder->endTag(); $encoder->endTag(); return true; }