コード例 #1
0
ファイル: synccollections.php プロジェクト: EGroupware/z-push
 /**
  * Returns how the current folder should be called in the PING comment.
  *
  * @param SyncParameters $spa
  *
  * @access public
  * @return string
  */
 private function getPingClass($spa)
 {
     $class = $spa->GetContentClass();
     if ($class == "Calendar" && strpos($spa->GetFolderId(), DeviceManager::FLD_ORIGIN_GAB) === 0) {
         $class = "GAB";
     }
     return $class;
 }
コード例 #2
0
ファイル: sync.php プロジェクト: SvKn/Z-Push-contrib
 /**
  * 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;
     // 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;
                 //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();
                     } else {
                         if ($synckey !== false) {
                             $spa->SetSyncKey($synckey);
                         }
                     }
                 } catch (StateInvalidException $stie) {
                     $spa = new SyncParameters();
                     $status = SYNC_STATUS_INVALIDSYNCKEY;
                     self::$topCollector->AnnounceInformation("State invalid - Resync folder", true);
                     self::$deviceManager->ForceFolderResync($folderid);
                 }
                 // update folderid.. this might be a new object
                 $spa->SetFolderId($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()), true);
                 } 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();
                         while (1) {
                             $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 > 512) {
                         $ws = 512;
                     }
                     $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);
                 while (self::$decoder->getElementStartTag(SYNC_OPTIONS)) {
                     $firstOption = true;
                     while (1) {
                         // 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);
                             // 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();
                                 // 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);
                 }
                 // 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;
                     while (1) {
                         // 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 (self::$decoder->getElementStartTag(SYNC_DATA)) {
                             $message = ZPush::getSyncObjectFromFolderClass($spa->GetContentClass());
                             $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("Sync->Handle(): Processed %d incoming changes", $nchanges));
                         if (!$actiondata["fetchids"]) {
                             self::$topCollector->AnnounceInformation(sprintf("%d incoming", $nchanges), true);
                         }
                         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());
             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 and check permissions
         try {
             $sc->LoadAllCollections(false, true, true);
         } catch (StateNotFoundException $snfex) {
             $status = SYNC_STATUS_INVALIDSYNCKEY;
             self::$topCollector->AnnounceInformation("StateNotFoundException", true);
         } catch (StatusException $stex) {
             $status = SYNC_STATUS_FOLDERHIERARCHYCHANGED;
             self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), true);
         }
         // update a few values
         foreach ($sc as $folderid => $spa) {
             // manually set getchanges parameter for this collection
             $sc->AddParameter($spa, "getchanges", true);
             // set new global windowsize without marking the SPA as changed
             if ($sc->GetGlobalWindowSize()) {
                 $spa->SetWindowSize($sc->GetGlobalWindowSize(), false);
             }
             // announce WindowSize to DeviceManager
             self::$deviceManager->SetWindowSize($folderid, $spa->GetWindowSize());
         }
         if (!$sc->HasCollections()) {
             $status = SYNC_STATUS_SYNCREQUESTINCOMPLETE;
         }
     }
     // 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), true);
                 }
             }
             // 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));
                     }
                 }
             }
         }
     }
     ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Start Output"));
     // Start the output
     self::$encoder->startWBXML();
     self::$encoder->startTag(SYNC_SYNCHRONIZE);
     // global status
     // SYNC_COMMONSTATUS_* start with values from 101
     if ($status != SYNC_COMMONSTATUS_SUCCESS && $status > 100) {
         self::$encoder->startTag(SYNC_STATUS);
         self::$encoder->content($status);
         self::$encoder->endTag();
     } else {
         self::$encoder->startTag(SYNC_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;
             if (isset($exporter)) {
                 unset($exporter);
             }
             // 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())) {
                 //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->GetFolderId()))) {
                             throw new StatusException(sprintf("HandleSync() could not Setup() the backend for folder id '%s'", $spa->GetFolderId()), SYNC_STATUS_FOLDERHIERARCHYCHANGED);
                         }
                         // Use the state from the importer, as changes may have already happened
                         $exporter = self::$backend->GetExporter($spa->GetFolderId());
                         if ($exporter === false) {
                             throw new StatusException(sprintf("HandleSync() could not get an exporter for folder id '%s'", $spa->GetFolderId()), 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->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), true);
                         // 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), true);
                         }
                     }
                 }
             }
             if (isset($hbinterval) && $changecount == 0 && $status == SYNC_STATUS_SUCCESS) {
                 ZLog::Write(LOGLEVEL_DEBUG, "No changes found for heartbeat folder. Omitting empty output.");
                 continue;
             }
             // Get a new sync key to output to the client if any changes have been send or will are available
             if (!empty($actiondata["modifyids"]) || !empty($actiondata["clientids"]) || !empty($actiondata["removeids"]) || $changecount > 0 || !$spa->HasSyncKey() && $status == SYNC_STATUS_SUCCESS) {
                 $spa->SetNewSyncKey(self::$deviceManager->GetStateManager()->GetNewSyncKey($spa->GetSyncKey()));
             }
             self::$encoder->startTag(SYNC_FOLDER);
             if ($spa->HasContentClass()) {
                 ZLog::Write(LOGLEVEL_DEBUG, sprintf("Folder type: %s", $spa->GetContentClass()));
                 // AS 12.0 devices require content class
                 if (Request::GetProtocolVersion() < 12.1) {
                     self::$encoder->startTag(SYNC_FOLDERTYPE);
                     self::$encoder->content($spa->GetContentClass());
                     self::$encoder->endTag();
                 }
             }
             self::$encoder->startTag(SYNC_SYNCKEY);
             if ($status == SYNC_STATUS_SUCCESS && $spa->HasNewSyncKey()) {
                 self::$encoder->content($spa->GetNewSyncKey());
             } else {
                 self::$encoder->content($spa->GetSyncKey());
             }
             self::$encoder->endTag();
             self::$encoder->startTag(SYNC_FOLDERID);
             self::$encoder->content($spa->GetFolderId());
             self::$encoder->endTag();
             self::$encoder->startTag(SYNC_STATUS);
             self::$encoder->content($status);
             self::$encoder->endTag();
             // announce failing status to the process loop detection
             if ($status !== SYNC_STATUS_SUCCESS) {
                 self::$deviceManager->AnnounceProcessStatus($spa->GetFolderId(), $status);
             }
             // Output IDs and status for incoming items & requests
             if ($status == SYNC_STATUS_SUCCESS && (!empty($actiondata["clientids"]) || !empty($actiondata["modifyids"]) || !empty($actiondata["removeids"]) || !empty($actiondata["fetchids"]))) {
                 self::$encoder->startTag(SYNC_REPLIES);
                 // output result of all new incoming items
                 foreach ($actiondata["clientids"] as $clientid => $serverid) {
                     self::$encoder->startTag(SYNC_ADD);
                     self::$encoder->startTag(SYNC_CLIENTENTRYID);
                     self::$encoder->content($clientid);
                     self::$encoder->endTag();
                     if ($serverid) {
                         self::$encoder->startTag(SYNC_SERVERENTRYID);
                         self::$encoder->content($serverid);
                         self::$encoder->endTag();
                     }
                     self::$encoder->startTag(SYNC_STATUS);
                     self::$encoder->content(isset($actiondata["statusids"][$clientid]) ? $actiondata["statusids"][$clientid] : SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR);
                     self::$encoder->endTag();
                     self::$encoder->endTag();
                 }
                 // loop through modify operations which were not a success, send status
                 foreach ($actiondata["modifyids"] as $serverid) {
                     if (isset($actiondata["statusids"][$serverid]) && $actiondata["statusids"][$serverid] !== SYNC_STATUS_SUCCESS) {
                         self::$encoder->startTag(SYNC_MODIFY);
                         self::$encoder->startTag(SYNC_SERVERENTRYID);
                         self::$encoder->content($serverid);
                         self::$encoder->endTag();
                         self::$encoder->startTag(SYNC_STATUS);
                         self::$encoder->content($actiondata["statusids"][$serverid]);
                         self::$encoder->endTag();
                         self::$encoder->endTag();
                     }
                 }
                 // loop through remove operations which were not a success, send status
                 foreach ($actiondata["removeids"] as $serverid) {
                     if (isset($actiondata["statusids"][$serverid]) && $actiondata["statusids"][$serverid] !== SYNC_STATUS_SUCCESS) {
                         self::$encoder->startTag(SYNC_REMOVE);
                         self::$encoder->startTag(SYNC_SERVERENTRYID);
                         self::$encoder->content($serverid);
                         self::$encoder->endTag();
                         self::$encoder->startTag(SYNC_STATUS);
                         self::$encoder->content($actiondata["statusids"][$serverid]);
                         self::$encoder->endTag();
                         self::$encoder->endTag();
                     }
                 }
                 if (!empty($actiondata["fetchids"])) {
                     self::$topCollector->AnnounceInformation(sprintf("Fetching %d objects ", count($actiondata["fetchids"])), true);
                 }
                 foreach ($actiondata["fetchids"] as $id) {
                     $data = false;
                     try {
                         $fetchstatus = SYNC_STATUS_SUCCESS;
                         // if this is an additional folder the backend has to be setup correctly
                         if (!self::$backend->Setup(ZPush::GetAdditionalSyncFolderStore($spa->GetFolderId()))) {
                             throw new StatusException(sprintf("HandleSync(): could not Setup() the backend to fetch in folder id '%s'", $spa->GetFolderId()), SYNC_STATUS_OBJECTNOTFOUND);
                         }
                         $data = self::$backend->Fetch($spa->GetFolderId(), $id, $spa->GetCPO());
                         // check if the message is broken
                         if (ZPush::GetDeviceManager(false) && ZPush::GetDeviceManager()->DoNotStreamMessage($id, $data)) {
                             ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): message not to be streamed as requested by DeviceManager, id = %s", $id));
                             $fetchstatus = SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR;
                         }
                     } catch (StatusException $stex) {
                         $fetchstatus = $stex->getCode();
                     }
                     self::$encoder->startTag(SYNC_FETCH);
                     self::$encoder->startTag(SYNC_SERVERENTRYID);
                     self::$encoder->content($id);
                     self::$encoder->endTag();
                     self::$encoder->startTag(SYNC_STATUS);
                     self::$encoder->content($fetchstatus);
                     self::$encoder->endTag();
                     if ($data !== false && $status == SYNC_STATUS_SUCCESS) {
                         self::$encoder->startTag(SYNC_DATA);
                         $data->Encode(self::$encoder);
                         self::$encoder->endTag();
                     } else {
                         ZLog::Write(LOGLEVEL_WARN, sprintf("Unable to Fetch '%s'", $id));
                     }
                     self::$encoder->endTag();
                 }
                 self::$encoder->endTag();
             }
             if ($sc->GetParameter($spa, "getchanges") && $spa->HasFolderId() && $spa->HasContentClass() && $spa->HasSyncKey()) {
                 $windowSize = self::$deviceManager->GetWindowSize($spa->GetFolderId(), $spa->GetContentClass(), $spa->GetUuid(), $spa->GetUuidCounter(), $changecount);
                 if ($changecount > $windowSize) {
                     self::$encoder->startTag(SYNC_MOREAVAILABLE, false, true);
                 }
             }
             // Stream outgoing changes
             if ($status == SYNC_STATUS_SUCCESS && $sc->GetParameter($spa, "getchanges") == true && $windowSize > 0) {
                 self::$topCollector->AnnounceInformation(sprintf("Streaming data of %d objects", $changecount > $windowSize ? $windowSize : $changecount));
                 // Output message changes per folder
                 self::$encoder->startTag(SYNC_PERFORM);
                 $n = 0;
                 while (1) {
                     try {
                         $progress = $exporter->Synchronize();
                         if (!is_array($progress)) {
                             break;
                         }
                         $n++;
                     } catch (SyncObjectBrokenException $mbe) {
                         $brokenSO = $mbe->GetSyncObject();
                         if (!$brokenSO) {
                             ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): Catched SyncObjectBrokenException but broken SyncObject not available. This should be fixed in the backend."));
                         } else {
                             if (!isset($brokenSO->id)) {
                                 $brokenSO->id = "Unknown ID";
                                 ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): Catched SyncObjectBrokenException but no ID of object set. This should be fixed in the backend."));
                             }
                             self::$deviceManager->AnnounceIgnoredMessage($spa->GetFolderId(), $brokenSO->id, $brokenSO);
                         }
                     } catch (StatusException $stex) {
                         $status = $stex->getCode();
                         // during export we found out that the states should be thrown away (ZP-623)
                         if ($status == SYNC_STATUS_INVALIDSYNCKEY) {
                             self::$deviceManager->ForceFolderResync($spa->GetFolderId());
                             break;
                         }
                     }
                     if ($n >= $windowSize) {
                         ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleSync(): Exported maxItems of messages: %d / %d", $n, $changecount));
                         break;
                     }
                 }
                 // $progress is not an array when exporting the last message
                 // so we get the number to display from the streamimporter
                 if (isset($streamimporter)) {
                     $n = $streamimporter->GetImportedMessages();
                 }
                 self::$encoder->endTag();
                 self::$topCollector->AnnounceInformation(sprintf("Outgoing %d objects%s", $n, $n >= $windowSize ? " of " . $changecount : ""), true);
                 // update folder status, if there is something set
                 if ($spa->GetFolderSyncRemaining() && $changecount > 0) {
                     $spa->SetFolderSyncRemaining($changecount);
                 }
                 // changecount is initialized with 'false', so 0 means no changes!
                 if ($changecount === 0 || $changecount !== false && $changecount <= $windowSize) {
                     self::$deviceManager->SetFolderSyncStatus($folderid, DeviceManager::FLD_SYNC_COMPLETED);
                 } else {
                     self::$deviceManager->SetFolderSyncStatus($folderid, DeviceManager::FLD_SYNC_INPROGRESS);
                 }
             }
             self::$encoder->endTag();
             // Save the sync state for the next time
             if ($spa->HasNewSyncKey()) {
                 self::$topCollector->AnnounceInformation("Saving state");
                 try {
                     if (isset($exporter) && $exporter) {
                         $state = $exporter->GetState();
                     } else {
                         if ($sc->GetParameter($spa, "state") !== null) {
                             $state = $sc->GetParameter($spa, "state");
                         } else {
                             if (!$spa->HasSyncKey()) {
                                 $state = "";
                             }
                         }
                     }
                 } catch (StatusException $stex) {
                     $status = $stex->getCode();
                 }
                 if (isset($state) && $status == SYNC_STATUS_SUCCESS) {
                     self::$deviceManager->GetStateManager()->SetSyncState($spa->GetNewSyncKey(), $state, $spa->GetFolderId());
                 } else {
                     ZLog::Write(LOGLEVEL_ERROR, sprintf("HandleSync(): error saving '%s' - no state information available", $spa->GetNewSyncKey()));
                 }
             }
             // save SyncParameters
             if ($status == SYNC_STATUS_SUCCESS && empty($actiondata["fetchids"])) {
                 $sc->SaveCollection($spa);
             }
             // reset status for the next folder
             $status = SYNC_STATUS_SUCCESS;
         }
         // END foreach collection
         self::$encoder->endTag();
         //SYNC_FOLDERS
     }
     self::$encoder->endTag();
     //SYNC_SYNCHRONIZE
     return true;
 }
コード例 #3
0
ファイル: sync.php プロジェクト: EGroupware/z-push
 /**
  * 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;
 }
コード例 #4
0
 /**
  * Handles the GetItemEstimate command
  * Returns an estimation of how many items will be synchronized at the next sync
  * This is mostly used to show something in the progress bar
  *
  * @param int       $commandCode
  *
  * @access public
  * @return boolean
  */
 public function Handle($commandCode)
 {
     $sc = new SyncCollections();
     if (!self::$decoder->getElementStartTag(SYNC_GETITEMESTIMATE_GETITEMESTIMATE)) {
         return false;
     }
     if (!self::$decoder->getElementStartTag(SYNC_GETITEMESTIMATE_FOLDERS)) {
         return false;
     }
     while (self::$decoder->getElementStartTag(SYNC_GETITEMESTIMATE_FOLDER)) {
         $spa = new SyncParameters();
         $spastatus = false;
         if (Request::GetProtocolVersion() >= 14.0) {
             if (self::$decoder->getElementStartTag(SYNC_SYNCKEY)) {
                 try {
                     $spa->SetSyncKey(self::$decoder->getElementContent());
                 } catch (StateInvalidException $siex) {
                     $spastatus = SYNC_GETITEMESTSTATUS_SYNCSTATENOTPRIMED;
                 }
                 if (!self::$decoder->getElementEndTag()) {
                     return false;
                 }
             }
             if (self::$decoder->getElementStartTag(SYNC_GETITEMESTIMATE_FOLDERID)) {
                 $spa->SetFolderId(self::$decoder->getElementContent());
                 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;
                     }
                 }
             }
             if (self::$decoder->getElementStartTag(SYNC_OPTIONS)) {
                 while (1) {
                     if (self::$decoder->getElementStartTag(SYNC_FILTERTYPE)) {
                         $spa->SetFilterType(self::$decoder->getElementContent());
                         if (!self::$decoder->getElementEndTag()) {
                             return false;
                         }
                     }
                     if (self::$decoder->getElementStartTag(SYNC_FOLDERTYPE)) {
                         $spa->SetContentClass(self::$decoder->getElementContent());
                         if (!self::$decoder->getElementEndTag()) {
                             return false;
                         }
                     }
                     if (self::$decoder->getElementStartTag(SYNC_MAXITEMS)) {
                         $spa->SetWindowSize($maxitems = self::$decoder->getElementContent());
                         if (!self::$decoder->getElementEndTag()) {
                             return false;
                         }
                     }
                     $e = self::$decoder->peek();
                     if ($e[EN_TYPE] == EN_TYPE_ENDTAG) {
                         self::$decoder->getElementEndTag();
                         break;
                     }
                 }
             }
         } else {
             //get items estimate does not necessarily send the folder type
             if (self::$decoder->getElementStartTag(SYNC_GETITEMESTIMATE_FOLDERTYPE)) {
                 $spa->SetContentClass(self::$decoder->getElementContent());
                 if (!self::$decoder->getElementEndTag()) {
                     return false;
                 }
             }
             if (self::$decoder->getElementStartTag(SYNC_GETITEMESTIMATE_FOLDERID)) {
                 $spa->SetFolderId(self::$decoder->getElementContent());
                 if (!self::$decoder->getElementEndTag()) {
                     return false;
                 }
             }
             if (!self::$decoder->getElementStartTag(SYNC_FILTERTYPE)) {
                 return false;
             }
             $spa->SetFilterType(self::$decoder->getElementContent());
             if (!self::$decoder->getElementEndTag()) {
                 return false;
             }
             if (!self::$decoder->getElementStartTag(SYNC_SYNCKEY)) {
                 return false;
             }
             try {
                 $spa->SetSyncKey(self::$decoder->getElementContent());
             } catch (StateInvalidException $siex) {
                 $spastatus = SYNC_GETITEMESTSTATUS_SYNCSTATENOTPRIMED;
             }
             if (!self::$decoder->getElementEndTag()) {
                 return false;
             }
         }
         if (!self::$decoder->getElementEndTag()) {
             return false;
         }
         //SYNC_GETITEMESTIMATE_FOLDER
         // Process folder data
         //In AS 14 request only collectionid is sent, without class
         if (!$spa->HasContentClass() && $spa->HasFolderId()) {
             try {
                 $spa->SetContentClass(self::$deviceManager->GetFolderClassFromCacheByID($spa->GetFolderId()));
             } catch (NoHierarchyCacheAvailableException $nhca) {
                 $spastatus = SYNC_GETITEMESTSTATUS_COLLECTIONINVALID;
             }
         }
         // compatibility mode AS 1.0 - get folderid which was sent during GetHierarchy()
         if (!$spa->HasFolderId() && $spa->HasContentClass()) {
             $spa->SetFolderId(self::$deviceManager->GetFolderIdFromCacheByClass($spa->GetContentClass()));
         }
         // Add collection to SC and load state
         $sc->AddCollection($spa);
         if ($spastatus) {
             // the CPO has a folder id now, so we can set the status
             $sc->AddParameter($spa, "status", $spastatus);
         } else {
             try {
                 $sc->AddParameter($spa, "state", self::$deviceManager->GetStateManager()->GetSyncState($spa->GetSyncKey()));
                 // if this is an additional folder the backend has to be setup correctly
                 if (!self::$backend->Setup(ZPush::GetAdditionalSyncFolderStore($spa->GetFolderId()))) {
                     throw new StatusException(sprintf("HandleGetItemEstimate() could not Setup() the backend for folder id '%s'", $spa->GetFolderId()), SYNC_GETITEMESTSTATUS_COLLECTIONINVALID);
                 }
             } catch (StateNotFoundException $snfex) {
                 // ok, the key is invalid. Question is, if the hierarchycache is still ok
                 //if not, we have to issue SYNC_GETITEMESTSTATUS_COLLECTIONINVALID which triggers a FolderSync
                 try {
                     self::$deviceManager->GetFolderClassFromCacheByID($spa->GetFolderId());
                     // we got here, so the HierarchyCache is ok
                     $sc->AddParameter($spa, "status", SYNC_GETITEMESTSTATUS_SYNCKKEYINVALID);
                 } catch (NoHierarchyCacheAvailableException $nhca) {
                     $sc->AddParameter($spa, "status", SYNC_GETITEMESTSTATUS_COLLECTIONINVALID);
                 }
                 self::$topCollector->AnnounceInformation("StateNotFoundException " . $sc->GetParameter($spa, "status"), true);
             } catch (StatusException $stex) {
                 if ($stex->getCode() == SYNC_GETITEMESTSTATUS_COLLECTIONINVALID) {
                     $sc->AddParameter($spa, "status", SYNC_GETITEMESTSTATUS_COLLECTIONINVALID);
                 } else {
                     $sc->AddParameter($spa, "status", SYNC_GETITEMESTSTATUS_SYNCSTATENOTPRIMED);
                 }
                 self::$topCollector->AnnounceInformation("StatusException " . $sc->GetParameter($spa, "status"), true);
             }
         }
     }
     if (!self::$decoder->getElementEndTag()) {
         return false;
     }
     //SYNC_GETITEMESTIMATE_FOLDERS
     if (!self::$decoder->getElementEndTag()) {
         return false;
     }
     //SYNC_GETITEMESTIMATE_GETITEMESTIMATE
     self::$encoder->startWBXML();
     self::$encoder->startTag(SYNC_GETITEMESTIMATE_GETITEMESTIMATE);
     $status = SYNC_GETITEMESTSTATUS_SUCCESS;
     // look for changes in all collections
     try {
         $sc->CountChanges();
     } catch (StatusException $ste) {
         $status = SYNC_GETITEMESTSTATUS_COLLECTIONINVALID;
     }
     $changes = $sc->GetChangedFolderIds();
     foreach ($sc as $folderid => $spa) {
         self::$encoder->startTag(SYNC_GETITEMESTIMATE_RESPONSE);
         if ($sc->GetParameter($spa, "status")) {
             $status = $sc->GetParameter($spa, "status");
         }
         self::$encoder->startTag(SYNC_GETITEMESTIMATE_STATUS);
         self::$encoder->content($status);
         self::$encoder->endTag();
         self::$encoder->startTag(SYNC_GETITEMESTIMATE_FOLDER);
         self::$encoder->startTag(SYNC_GETITEMESTIMATE_FOLDERTYPE);
         self::$encoder->content($spa->GetContentClass());
         self::$encoder->endTag();
         self::$encoder->startTag(SYNC_GETITEMESTIMATE_FOLDERID);
         self::$encoder->content($spa->GetFolderId());
         self::$encoder->endTag();
         if (isset($changes[$folderid]) && $changes[$folderid] !== false) {
             self::$encoder->startTag(SYNC_GETITEMESTIMATE_ESTIMATE);
             self::$encoder->content($changes[$folderid]);
             self::$encoder->endTag();
             if ($changes[$folderid] > 0) {
                 self::$topCollector->AnnounceInformation(sprintf("%s %d changes", $spa->GetContentClass(), $changes[$folderid]), true);
             }
         }
         self::$encoder->endTag();
         self::$encoder->endTag();
     }
     if (array_sum($changes) == 0) {
         self::$topCollector->AnnounceInformation("No changes found", true);
     }
     self::$encoder->endTag();
     return true;
 }
コード例 #5
0
ファイル: getitemestimate.php プロジェクト: EGroupware/z-push
 /**
  * Handles the GetItemEstimate command
  * Returns an estimation of how many items will be synchronized at the next sync
  * This is mostly used to show something in the progress bar
  *
  * @param int       $commandCode
  *
  * @access public
  * @return boolean
  */
 public function Handle($commandCode)
 {
     $sc = new SyncCollections();
     if (!self::$decoder->getElementStartTag(SYNC_GETITEMESTIMATE_GETITEMESTIMATE)) {
         return false;
     }
     if (!self::$decoder->getElementStartTag(SYNC_GETITEMESTIMATE_FOLDERS)) {
         return false;
     }
     while (self::$decoder->getElementStartTag(SYNC_GETITEMESTIMATE_FOLDER)) {
         $spa = new SyncParameters();
         $spastatus = false;
         // read the folder properties
         WBXMLDecoder::ResetInWhile("getItemEstimateFolders");
         while (WBXMLDecoder::InWhile("getItemEstimateFolders")) {
             if (self::$decoder->getElementStartTag(SYNC_SYNCKEY)) {
                 try {
                     $spa->SetSyncKey(self::$decoder->getElementContent());
                 } catch (StateInvalidException $siex) {
                     $spastatus = SYNC_GETITEMESTSTATUS_SYNCSTATENOTPRIMED;
                 }
                 if (!self::$decoder->getElementEndTag()) {
                     return false;
                 }
             } elseif (self::$decoder->getElementStartTag(SYNC_GETITEMESTIMATE_FOLDERID)) {
                 $fid = self::$decoder->getElementContent();
                 $spa->SetFolderId($fid);
                 $spa->SetBackendFolderId(self::$deviceManager->GetBackendIdForFolderId($fid));
                 if (!self::$decoder->getElementEndTag()) {
                     return false;
                 }
             } elseif (self::$decoder->getElementStartTag(SYNC_CONVERSATIONMODE)) {
                 $spa->SetConversationMode(true);
                 if (($conversationmode = self::$decoder->getElementContent()) !== false) {
                     $spa->SetConversationMode((bool) $conversationmode);
                     if (!self::$decoder->getElementEndTag()) {
                         return false;
                     }
                 }
             } elseif (self::$decoder->getElementStartTag(SYNC_GETITEMESTIMATE_FOLDERTYPE)) {
                 $spa->SetContentClass(self::$decoder->getElementContent());
                 if (!self::$decoder->getElementEndTag()) {
                     return false;
                 }
             } elseif (self::$decoder->getElementStartTag(SYNC_FILTERTYPE)) {
                 $spa->SetFilterType(self::$decoder->getElementContent());
                 if (!self::$decoder->getElementEndTag()) {
                     return false;
                 }
             }
             while (self::$decoder->getElementStartTag(SYNC_OPTIONS)) {
                 WBXMLDecoder::ResetInWhile("getItemEstimateOptions");
                 while (WBXMLDecoder::InWhile("getItemEstimateOptions")) {
                     $firstOption = true;
                     // foldertype definition
                     if (self::$decoder->getElementStartTag(SYNC_FOLDERTYPE)) {
                         $foldertype = self::$decoder->getElementContent();
                         ZLog::Write(LOGLEVEL_DEBUG, sprintf("HandleGetItemEstimate(): specified options block with foldertype '%s'", $foldertype));
                         // switch the foldertype for the next options
                         $spa->UseCPO($foldertype);
                         // 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();
                             // 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_MAXITEMS)) {
                         $spa->SetWindowSize($maxitems = self::$decoder->getElementContent());
                         if (!self::$decoder->getElementEndTag()) {
                             return false;
                         }
                     }
                     $e = self::$decoder->peek();
                     if ($e[EN_TYPE] == EN_TYPE_ENDTAG) {
                         self::$decoder->getElementEndTag();
                         break;
                     }
                 }
             }
             $e = self::$decoder->peek();
             if ($e[EN_TYPE] == EN_TYPE_ENDTAG) {
                 self::$decoder->getElementEndTag();
                 //SYNC_GETITEMESTIMATE_FOLDER
                 break;
             }
         }
         // Process folder data
         //In AS 14 request only collectionid is sent, without class
         if (!$spa->HasContentClass() && $spa->HasFolderId()) {
             try {
                 $spa->SetContentClass(self::$deviceManager->GetFolderClassFromCacheByID($spa->GetFolderId()));
             } catch (NoHierarchyCacheAvailableException $nhca) {
                 $spastatus = SYNC_GETITEMESTSTATUS_COLLECTIONINVALID;
             }
         }
         // compatibility mode AS 1.0 - get folderid which was sent during GetHierarchy()
         if (!$spa->HasFolderId() && $spa->HasContentClass()) {
             $spa->SetFolderId(self::$deviceManager->GetFolderIdFromCacheByClass($spa->GetContentClass()));
         }
         // Add collection to SC and load state
         $sc->AddCollection($spa);
         if ($spastatus) {
             // the CPO has a folder id now, so we can set the status
             $sc->AddParameter($spa, "status", $spastatus);
         } else {
             try {
                 $sc->AddParameter($spa, "state", self::$deviceManager->GetStateManager()->GetSyncState($spa->GetSyncKey()));
                 // 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("HandleGetItemEstimate() could not Setup() the backend for folder id %s/%s", $spa->GetFolderId(), $spa->GetBackendFolderId()), SYNC_GETITEMESTSTATUS_COLLECTIONINVALID);
                 }
             } catch (StateNotFoundException $snfex) {
                 // ok, the key is invalid. Question is, if the hierarchycache is still ok
                 //if not, we have to issue SYNC_GETITEMESTSTATUS_COLLECTIONINVALID which triggers a FolderSync
                 try {
                     self::$deviceManager->GetFolderClassFromCacheByID($spa->GetFolderId());
                     // we got here, so the HierarchyCache is ok
                     $sc->AddParameter($spa, "status", SYNC_GETITEMESTSTATUS_SYNCKKEYINVALID);
                 } catch (NoHierarchyCacheAvailableException $nhca) {
                     $sc->AddParameter($spa, "status", SYNC_GETITEMESTSTATUS_COLLECTIONINVALID);
                 }
                 self::$topCollector->AnnounceInformation("StateNotFoundException " . $sc->GetParameter($spa, "status"), true);
             } catch (StatusException $stex) {
                 if ($stex->getCode() == SYNC_GETITEMESTSTATUS_COLLECTIONINVALID) {
                     $sc->AddParameter($spa, "status", SYNC_GETITEMESTSTATUS_COLLECTIONINVALID);
                 } else {
                     $sc->AddParameter($spa, "status", SYNC_GETITEMESTSTATUS_SYNCSTATENOTPRIMED);
                 }
                 self::$topCollector->AnnounceInformation("StatusException " . $sc->GetParameter($spa, "status"), true);
             }
         }
     }
     if (!self::$decoder->getElementEndTag()) {
         return false;
     }
     //SYNC_GETITEMESTIMATE_FOLDERS
     if (!self::$decoder->getElementEndTag()) {
         return false;
     }
     //SYNC_GETITEMESTIMATE_GETITEMESTIMATE
     self::$encoder->startWBXML();
     self::$encoder->startTag(SYNC_GETITEMESTIMATE_GETITEMESTIMATE);
     $status = SYNC_GETITEMESTSTATUS_SUCCESS;
     // look for changes in all collections
     try {
         $sc->CountChanges();
     } catch (StatusException $ste) {
         $status = SYNC_GETITEMESTSTATUS_COLLECTIONINVALID;
     }
     $changes = $sc->GetChangedFolderIds();
     foreach ($sc as $folderid => $spa) {
         self::$encoder->startTag(SYNC_GETITEMESTIMATE_RESPONSE);
         if ($sc->GetParameter($spa, "status")) {
             $status = $sc->GetParameter($spa, "status");
         }
         self::$encoder->startTag(SYNC_GETITEMESTIMATE_STATUS);
         self::$encoder->content($status);
         self::$encoder->endTag();
         self::$encoder->startTag(SYNC_GETITEMESTIMATE_FOLDER);
         self::$encoder->startTag(SYNC_GETITEMESTIMATE_FOLDERTYPE);
         self::$encoder->content($spa->GetContentClass());
         self::$encoder->endTag();
         self::$encoder->startTag(SYNC_GETITEMESTIMATE_FOLDERID);
         self::$encoder->content($spa->GetFolderId());
         self::$encoder->endTag();
         if (isset($changes[$folderid]) && $changes[$folderid] !== false) {
             self::$encoder->startTag(SYNC_GETITEMESTIMATE_ESTIMATE);
             self::$encoder->content($changes[$folderid]);
             self::$encoder->endTag();
             if ($changes[$folderid] > 0) {
                 self::$topCollector->AnnounceInformation(sprintf("%s %d changes", $spa->GetContentClass(), $changes[$folderid]), true);
             }
             // update the device data to mark folders as complete when synching with WM
             if ($changes[$folderid] == 0) {
                 self::$deviceManager->SetFolderSyncStatus($folderid, DeviceManager::FLD_SYNC_COMPLETED);
             }
         }
         self::$encoder->endTag();
         self::$encoder->endTag();
     }
     if (array_sum($changes) == 0) {
         self::$topCollector->AnnounceInformation("No changes found", true);
     }
     self::$encoder->endTag();
     return true;
 }