/**
  * Returns a previousily set non-permanent value for a SyncParameters object
  *
  * @param SyncParameters    $spa    target SyncParameters
  * @param string            $key
  *
  * @access public
  * @return mixed            returns 'null' if nothing set
  */
 public function GetParameter($spa, $key)
 {
     if (!$spa->HasFolderId()) {
         return null;
     }
     if (isset($this->addparms[$spa->GetFolderId()]) && isset($this->addparms[$spa->GetFolderId()][$key])) {
         return $this->addparms[$spa->GetFolderId()][$key];
     } else {
         return null;
     }
 }
Example #2
0
 /**
  * Remove folder statistics from a SyncParameter object.
  *
  * @param SyncParameters $spa
  *
  * @access public
  * @return
  */
 private function invalidateFolderStat($spa)
 {
     if ($spa->HasFolderStat()) {
         ZLog::Write(LOGLEVEL_DEBUG, sprintf("SyncCollections->invalidateFolderStat(): removing folder stat '%s' for folderid '%s'", $spa->GetFolderStat(), $spa->GetFolderId()));
         $spa->DelFolderStat();
         $this->SaveCollection($spa);
         return true;
     }
     return false;
 }
Example #3
0
 /**
  * Imports a message
  *
  * @param SyncParameters    $spa            SyncParameters object
  * @param array             $actiondata     Actiondata array
  * @param integer           $todo           WBXML flag indicating how message should be imported.
  *                                          Valid values: SYNC_ADD, SYNC_MODIFY, SYNC_REMOVE
  * @param SyncObject        $message        SyncObject message to be imported
  * @param string            $clientid       Client message identifier
  * @param string            $serverid       Server message identifier
  * @param string            $foldertype     On sms sync, this says "SMS", else false
  * @param integer           $messageCount   Counter of already imported messages
  *
  * @access private
  * @throws StatusException  in case the importer is not available
  * @return -                Message related status are returned in the actiondata.
  */
 private function importMessage($spa, &$actiondata, $todo, $message, $clientid, $serverid, $foldertype, $messageCount)
 {
     // the importer needs to be available!
     if ($this->importer == false) {
         throw StatusException("Sync->importMessage(): importer not available", SYNC_STATUS_SERVERERROR);
     }
     // mark this state as used, e.g. for HeartBeat
     self::$deviceManager->SetHeartbeatStateIntegrity($spa->GetFolderId(), $spa->GetUuid(), $spa->GetUuidCounter());
     // Detect incoming loop
     // messages which were created/removed before will not have the same action executed again
     // if a message is edited we perform this action "again", as the message could have been changed on the mobile in the meantime
     $ignoreMessage = false;
     if ($actiondata["failstate"]) {
         // message was ADDED before, do NOT add it again
         if ($todo == SYNC_ADD && isset($actiondata["failstate"]["clientids"][$clientid])) {
             $ignoreMessage = true;
             // make sure no messages are sent back
             self::$deviceManager->SetWindowSize($spa->GetFolderId(), 0);
             $actiondata["clientids"][$clientid] = $actiondata["failstate"]["clientids"][$clientid];
             $actiondata["statusids"][$clientid] = $actiondata["failstate"]["statusids"][$clientid];
             ZLog::Write(LOGLEVEL_WARN, sprintf("Mobile loop detected! Incoming new message '%s' was created on the server before. Replying with known new server id: %s", $clientid, $actiondata["clientids"][$clientid]));
         }
         // message was REMOVED before, do NOT attemp to remove it again
         if ($todo == SYNC_REMOVE && isset($actiondata["failstate"]["removeids"][$serverid])) {
             $ignoreMessage = true;
             // make sure no messages are sent back
             self::$deviceManager->SetWindowSize($spa->GetFolderId(), 0);
             $actiondata["removeids"][$serverid] = $actiondata["failstate"]["removeids"][$serverid];
             $actiondata["statusids"][$serverid] = $actiondata["failstate"]["statusids"][$serverid];
             ZLog::Write(LOGLEVEL_WARN, sprintf("Mobile loop detected! Message '%s' was deleted by the mobile before. Replying with known status: %s", $clientid, $actiondata["statusids"][$serverid]));
         }
     }
     if (!$ignoreMessage) {
         switch ($todo) {
             case SYNC_MODIFY:
                 self::$topCollector->AnnounceInformation(sprintf("Saving modified message %d", $messageCount));
                 try {
                     $actiondata["modifyids"][] = $serverid;
                     // ignore sms messages
                     if ($foldertype == "SMS" || stripos($serverid, self::ZPUSHIGNORESMS) !== false) {
                         ZLog::Write(LOGLEVEL_DEBUG, "SMS sync are not supported. Ignoring message.");
                         // TODO we should update the SMS
                         $actiondata["statusids"][$serverid] = SYNC_STATUS_SUCCESS;
                     } else {
                         if (!$message instanceof SyncObject || !$message->Check(true)) {
                             $actiondata["statusids"][$serverid] = SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR;
                         } else {
                             if (isset($message->read)) {
                                 // Currently, 'read' is only sent by the PDA when it is ONLY setting the read flag.
                                 $this->importer->ImportMessageReadFlag($serverid, $message->read);
                             } elseif (!isset($message->flag)) {
                                 $this->importer->ImportMessageChange($serverid, $message);
                             }
                             // email todoflags - some devices send todos flags together with read flags,
                             // so they have to be handled separately
                             if (isset($message->flag)) {
                                 $this->importer->ImportMessageChange($serverid, $message);
                             }
                             $actiondata["statusids"][$serverid] = SYNC_STATUS_SUCCESS;
                         }
                     }
                 } catch (StatusException $stex) {
                     $actiondata["statusids"][$serverid] = $stex->getCode();
                 }
                 break;
             case SYNC_ADD:
                 self::$topCollector->AnnounceInformation(sprintf("Creating new message from mobile %d", $messageCount));
                 try {
                     // ignore sms messages
                     if ($foldertype == "SMS") {
                         ZLog::Write(LOGLEVEL_DEBUG, "SMS sync are not supported. Ignoring message.");
                         // TODO we should create the SMS
                         // return a fake serverid which we can identify later
                         $actiondata["clientids"][$clientid] = self::ZPUSHIGNORESMS . $clientid;
                         $actiondata["statusids"][$clientid] = SYNC_STATUS_SUCCESS;
                     } else {
                         if (!$message instanceof SyncObject || !$message->Check(true)) {
                             $actiondata["clientids"][$clientid] = false;
                             $actiondata["statusids"][$clientid] = SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR;
                         } else {
                             $actiondata["clientids"][$clientid] = false;
                             $actiondata["clientids"][$clientid] = $this->importer->ImportMessageChange(false, $message);
                             $actiondata["statusids"][$clientid] = SYNC_STATUS_SUCCESS;
                         }
                     }
                 } catch (StatusException $stex) {
                     $actiondata["statusids"][$clientid] = $stex->getCode();
                 }
                 break;
             case SYNC_REMOVE:
                 self::$topCollector->AnnounceInformation(sprintf("Deleting message removed on mobile %d", $messageCount));
                 try {
                     $actiondata["removeids"][] = $serverid;
                     // ignore sms messages
                     if ($foldertype == "SMS" || stripos($serverid, self::ZPUSHIGNORESMS) !== false) {
                         ZLog::Write(LOGLEVEL_DEBUG, "SMS sync are not supported. Ignoring message.");
                         // TODO we should delete the SMS
                         $actiondata["statusids"][$serverid] = SYNC_STATUS_SUCCESS;
                     } else {
                         // if message deletions are to be moved, move them
                         if ($spa->GetDeletesAsMoves()) {
                             $folderid = self::$backend->GetWasteBasket();
                             if ($folderid) {
                                 $this->importer->ImportMessageMove($serverid, $folderid);
                                 $actiondata["statusids"][$serverid] = SYNC_STATUS_SUCCESS;
                                 break;
                             } else {
                                 ZLog::Write(LOGLEVEL_WARN, "Message should be moved to WasteBasket, but the Backend did not return a destination ID. Message is hard deleted now!");
                             }
                         }
                         $this->importer->ImportMessageDeletion($serverid);
                         $actiondata["statusids"][$serverid] = SYNC_STATUS_SUCCESS;
                     }
                 } catch (StatusException $stex) {
                     $actiondata["statusids"][$serverid] = $stex->getCode();
                 }
                 break;
         }
         ZLog::Write(LOGLEVEL_DEBUG, "Sync->importMessage(): message imported");
     }
 }
 /**
  * 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;
 }
Example #5
0
 /**
  * Imports a message
  *
  * @param SyncParameters    $spa            SyncParameters object
  * @param array             $actiondata     Actiondata array
  * @param integer           $todo           WBXML flag indicating how message should be imported.
  *                                          Valid values: SYNC_ADD, SYNC_MODIFY, SYNC_REMOVE
  * @param SyncObject        $message        SyncObject message to be imported
  * @param string            $clientid       Client message identifier
  * @param string            $serverid       Server message identifier
  *
  * @access private
  * @throws StatusException  in case the importer is not available
  * @return -                Message related status are returned in the actiondata.
  */
 private function importMessage($spa, &$actiondata, $todo, $message, $clientid, $serverid)
 {
     // the importer needs to be available!
     if ($this->importer == false) {
         throw StatusException(sprintf("Sync->importMessage(): importer not available", SYNC_STATUS_SERVERERROR));
     }
     // Detect incoming loop
     // messages which were created/removed before will not have the same action executed again
     // if a message is edited we perform this action "again", as the message could have been changed on the mobile in the meantime
     $ignoreMessage = false;
     if ($actiondata["failstate"]) {
         // message was ADDED before, do NOT add it again
         if ($todo == SYNC_ADD && $actiondata["failstate"]["clientids"][$clientid]) {
             $ignoreMessage = true;
             // make sure no messages are sent back
             self::$deviceManager->SetWindowSize($spa->GetFolderId(), 0);
             $actiondata["clientids"][$clientid] = $actiondata["failstate"]["clientids"][$clientid];
             $actiondata["statusids"][$clientid] = $actiondata["failstate"]["statusids"][$clientid];
             ZLog::Write(LOGLEVEL_WARN, sprintf("Mobile loop detected! Incoming new message '%s' was created on the server before. Replying with known new server id: %s", $clientid, $actiondata["clientids"][$clientid]));
         }
         // message was REMOVED before, do NOT attemp to remove it again
         if ($todo == SYNC_REMOVE && $actiondata["failstate"]["removeids"][$serverid]) {
             $ignoreMessage = true;
             // make sure no messages are sent back
             self::$deviceManager->SetWindowSize($spa->GetFolderId(), 0);
             $actiondata["removeids"][$serverid] = $actiondata["failstate"]["removeids"][$serverid];
             $actiondata["statusids"][$serverid] = $actiondata["failstate"]["statusids"][$serverid];
             ZLog::Write(LOGLEVEL_WARN, sprintf("Mobile loop detected! Message '%s' was deleted by the mobile before. Replying with known status: %s", $clientid, $actiondata["statusids"][$serverid]));
         }
     }
     if (!$ignoreMessage) {
         switch ($todo) {
             case SYNC_MODIFY:
                 try {
                     $actiondata["modifyids"][] = $serverid;
                     // check incoming message without logging WARN messages about errors
                     if (!$message instanceof SyncObject || !$message->Check(true)) {
                         $actiondata["statusids"][$serverid] = SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR;
                     } else {
                         if (isset($message->read)) {
                             // Currently, 'read' is only sent by the PDA when it is ONLY setting the read flag.
                             $this->importer->ImportMessageReadFlag($serverid, $message->read);
                         } elseif (!isset($message->flag)) {
                             $this->importer->ImportMessageChange($serverid, $message);
                         }
                         // email todoflags - some devices send todos flags together with read flags,
                         // so they have to be handled separately
                         if (isset($message->flag)) {
                             $this->importer->ImportMessageChange($serverid, $message);
                         }
                         $actiondata["statusids"][$serverid] = SYNC_STATUS_SUCCESS;
                     }
                 } catch (StatusException $stex) {
                     $actiondata["statusids"][$serverid] = $stex->getCode();
                 }
                 break;
             case SYNC_ADD:
                 try {
                     // check incoming message without logging WARN messages about errors
                     if (!$message instanceof SyncObject || !$message->Check(true)) {
                         $actiondata["clientids"][$clientid] = false;
                         $actiondata["statusids"][$clientid] = SYNC_STATUS_CLIENTSERVERCONVERSATIONERROR;
                     } else {
                         $actiondata["clientids"][$clientid] = false;
                         $actiondata["clientids"][$clientid] = $this->importer->ImportMessageChange(false, $message);
                         $actiondata["statusids"][$clientid] = SYNC_STATUS_SUCCESS;
                     }
                 } catch (StatusException $stex) {
                     $actiondata["statusids"][$clientid] = $stex->getCode();
                 }
                 break;
             case SYNC_REMOVE:
                 try {
                     $actiondata["removeids"][] = $serverid;
                     // if message deletions are to be moved, move them
                     if ($spa->GetDeletesAsMoves()) {
                         $folderid = self::$backend->GetWasteBasket();
                         if ($folderid) {
                             $this->importer->ImportMessageMove($serverid, $folderid);
                             $actiondata["statusids"][$serverid] = SYNC_STATUS_SUCCESS;
                             break;
                         } else {
                             ZLog::Write(LOGLEVEL_WARN, "Message should be moved to WasteBasket, but the Backend did not return a destination ID. Message is hard deleted now!");
                         }
                     }
                     $this->importer->ImportMessageDeletion($serverid);
                     $actiondata["statusids"][$serverid] = SYNC_STATUS_SUCCESS;
                 } catch (StatusException $stex) {
                     $actiondata["statusids"][$serverid] = $stex->getCode();
                 }
                 break;
         }
         ZLog::Write(LOGLEVEL_DEBUG, "Sync->importMessage(): message imported");
     }
 }
Example #6
0
 /**
  * Sets the new folderstat and calculates & sets an expiration date for the folder stat.
  *
  * @param SyncParameters $spa
  * @param string $newFolderStat
  *
  * @access private
  * @return
  */
 private function setFolderStat($spa, $newFolderStat)
 {
     $spa->SetFolderStat($newFolderStat);
     $maxTimeout = 60 * 60 * 24 * 31;
     // one month
     $interval = Utils::GetFiltertypeInterval($spa->GetFilterType());
     $timeout = time() + ($interval && $interval < $maxTimeout ? $interval : $maxTimeout);
     ZLog::Write(LOGLEVEL_DEBUG, sprintf("Sync()->setFolderStat() on %s: %s expiring %s", $spa->getFolderId(), $newFolderStat, date('Y-m-d H:i:s', $timeout)));
     $spa->SetFolderStatTimeout($timeout);
 }
Example #7
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;
         // 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;
 }