/**
  * generate MoveItems response
  */
 public function getResponse()
 {
     $moves = $this->_outputDom->documentElement;
     foreach ($this->_moves as $move) {
         $response = $moves->appendChild($this->_outputDom->createElementNS('uri:Move', 'Response'));
         $response->appendChild($this->_outputDom->createElementNS('uri:Move', 'SrcMsgId', $move['srcMsgId']));
         try {
             $sourceFolder = $this->_folderBackend->getFolder($this->_device, $move['srcFldId']);
         } catch (Syncope_Exception_NotFound $senf) {
             $sourceFolder = null;
         }
         try {
             $destinationFolder = $this->_folderBackend->getFolder($this->_device, $move['dstFldId']);
         } catch (Syncope_Exception_NotFound $senf) {
             $destinationFolder = null;
         }
         if ($sourceFolder === null) {
             $response->appendChild($this->_outputDom->createElementNS('uri:Move', 'Status', Syncope_Command_MoveItems::STATUS_INVALID_SOURCE));
         } else {
             if ($destinationFolder === null) {
                 $response->appendChild($this->_outputDom->createElementNS('uri:Move', 'Status', Syncope_Command_MoveItems::STATUS_INVALID_DESTINATION));
             } else {
                 $dataController = Syncope_Data_Factory::factory($sourceFolder->class, $this->_device, $this->_syncTimeStamp);
                 $newId = $dataController->moveItem($move['srcFldId'], $move['srcMsgId'], $move['dstFldId']);
                 $response->appendChild($this->_outputDom->createElementNS('uri:Move', 'Status', Syncope_Command_MoveItems::STATUS_SUCCESS));
                 $response->appendChild($this->_outputDom->createElementNS('uri:Move', 'DstMsgId', $newId));
             }
         }
     }
     return $this->_outputDom;
 }
 /**
  * generate ItemOperations response
  */
 public function getResponse()
 {
     // add aditional namespaces
     $this->_outputDom->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:AirSyncBase', 'uri:AirSyncBase');
     $this->_outputDom->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:AirSync', 'uri:AirSync');
     $itemOperations = $this->_outputDom->documentElement;
     $itemOperations->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Status', Syncope_Command_ItemOperations::STATUS_SUCCESS));
     $response = $itemOperations->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Response'));
     foreach ($this->_fetches as $fetch) {
         $fetchTag = $response->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Fetch'));
         try {
             $dataController = Syncope_Data_Factory::factory($fetch['store'], $this->_device, $this->_syncTimeStamp);
             if (isset($fetch['collectionId'])) {
                 $properties = $this->_outputDom->createElementNS('uri:ItemOperations', 'Properties');
                 $dataController->appendXML($properties, $fetch, $fetch['serverId']);
                 $fetchTag->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Status', Syncope_Command_ItemOperations::STATUS_SUCCESS));
                 $fetchTag->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'CollectionId', $fetch['collectionId']));
                 $fetchTag->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ServerId', $fetch['serverId']));
                 $fetchTag->appendChild($properties);
             } elseif (isset($fetch['fileReference'])) {
                 $properties = $this->_outputDom->createElementNS('uri:ItemOperations', 'Properties');
                 $dataController->appendFileReference($properties, $fetch['fileReference']);
                 $fetchTag->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Status', Syncope_Command_ItemOperations::STATUS_SUCCESS));
                 $fetchTag->appendChild($this->_outputDom->createElementNS('uri:AirSyncBase', 'FileReference', $fetch['fileReference']));
                 $fetchTag->appendChild($properties);
             }
         } catch (Tinebase_Exception_NotFound $e) {
             $response->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Status', Syncope_Command_ItemOperations::STATUS_ITEM_FAILED_CONVERSION));
         } catch (Exception $e) {
             $response->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Status', Syncope_Command_ItemOperations::STATUS_SERVER_ERROR));
         }
     }
     return $this->_outputDom;
 }
 /**
  * parse FolderDelete request
  *
  */
 public function handle()
 {
     $xml = simplexml_import_dom($this->_inputDom);
     $syncKey = (int) $xml->SyncKey;
     $folderId = (string) $xml->ServerId;
     if ($this->_logger instanceof Zend_Log) {
         $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " synckey is {$syncKey}");
     }
     $this->_syncState = $this->_syncStateBackend->validate($this->_device, 'FolderSync', $syncKey);
     try {
         $this->_folder = $this->_folderBackend->getFolder($this->_device, $folderId);
         $dataController = Syncope_Data_Factory::factory($this->_folder->class, $this->_device, $this->_syncTimeStamp);
         $dataController->deleteFolder($this->_folder);
         $this->_folderBackend->delete($this->_folder);
     } catch (Syncope_Exception_NotFound $senf) {
         if ($this->_logger instanceof Zend_Log) {
             $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " " . $senf->getMessage());
         }
     }
 }
 /**
  * generate FolderCreate response
  */
 public function getResponse()
 {
     $folderCreate = $this->_outputDom->documentElement;
     if ($this->_syncState == false) {
         if ($this->_logger instanceof Zend_Log) {
             $this->_logger->info(__METHOD__ . '::' . __LINE__ . " INVALID synckey");
         }
         $folderCreate->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Status', Syncope_Command_FolderSync::STATUS_INVALID_SYNC_KEY));
     } else {
         $this->_syncState->counter++;
         $dataController = Syncope_Data_Factory::factory($this->_class, $this->_device, $this->_syncTimeStamp);
         $folder = $dataController->createFolder($this->_parentId, $this->_displayName, $this->_type);
         // create xml output
         $folderCreate->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Status', Syncope_Command_FolderSync::STATUS_SUCCESS));
         $folderCreate->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'SyncKey', $this->_syncState->counter));
         $folderCreate->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'ServerId', $folder['folderId']));
         $folder = new Syncope_Model_Folder(array('device_id' => $this->_device, 'class' => $this->_class, 'folderid' => $folder['folderId'], 'parentid' => $folder['parentId'], 'displayname' => $folder['displayName'], 'type' => $folder['type'], 'creation_time' => $this->_syncTimeStamp, 'lastfiltertype' => null));
         // store folder in state backend
         $this->_folderBackend->create($folder);
         $this->_syncStateBackend->update($this->_syncState);
     }
     return $this->_outputDom;
 }
 /**
  * parse FolderUpdate request
  *
  */
 public function handle()
 {
     $xml = simplexml_import_dom($this->_inputDom);
     $this->_syncKey = (int) $xml->SyncKey;
     $this->_parentId = (string) $xml->ParentId;
     $this->_displayName = (string) $xml->DisplayName;
     $this->_serverId = (string) $xml->ServerId;
     if ($this->_logger instanceof Zend_Log) {
         $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " synckey is {$this->_syncKey} parentId {$this->_parentId} name {$this->_displayName}");
     }
     $defaultAccountId = Tinebase_Core::getPreference('Felamimail')->{Felamimail_Preference::DEFAULTACCOUNT};
     $this->_syncState = $this->_syncStateBackend->validate($this->_device, 'FolderSync', $this->_syncKey);
     try {
         $this->_folder = $this->_folderBackend->getFolder($this->_device, $this->_serverId);
         $dataController = Syncope_Data_Factory::factory($this->_folder->class, $this->_device, $this->_syncTimeStamp);
         $felamimail_model_folder = Felamimail_Controller_Folder::getInstance()->get($this->_folder->folderid);
         $dataController->updateFolder($defaultAccountId, trim($this->_displayName), $felamimail_model_folder['globalname']);
         $this->_folderBackend->update($this->_folder);
     } catch (Syncope_Exception_NotFound $senf) {
         if ($this->_logger instanceof Zend_Log) {
             $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " " . $senf->getMessage());
         }
     }
 }
 /**
  * (non-PHPdoc)
  * @see Syncope_Command_Wbxml::getResponse()
  */
 public function getResponse()
 {
     $itemEstimate = $this->_outputDom->documentElement;
     foreach ($this->_collections as $collectionData) {
         $response = $itemEstimate->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Response'));
         // invalid collectionid provided
         if (empty($collectionData['folder'])) {
             if ($this->_logger instanceof Zend_Log) {
                 $this->_logger->warn(__METHOD__ . '::' . __LINE__ . " folder does not exist");
             }
             $response->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Status', self::STATUS_INVALID_COLLECTION));
             $collection = $response->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Collection'));
             $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Class', $collectionData['class']));
             $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'CollectionId', $collectionData['collectionId']));
             $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Estimate', 0));
         } elseif (!$collectionData['syncState'] instanceof Syncope_Model_ISyncState) {
             if ($this->_logger instanceof Zend_Log) {
                 $this->_logger->warn(__METHOD__ . '::' . __LINE__ . " invalid synckey {$collectionData['syncKey']} provided");
             }
             /*
             * Android phones (and maybe others) don't take care about status 4(INVALID_SYNC_KEY)
             * To solve the problem we always return status 1(SUCCESS) even the sync key is invalid with Estimate set to 1.
             * This way the phone gets forced to sync. Handling invalid synckeys during sync command works without any problems.
             * 
                $response->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Status', self::STATUS_INVALID_SYNC_KEY));
                $collection = $response->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Collection'));
                $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Class', $collectionData['class']));
                $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'CollectionId', $collectionData['collectionId']));  
                $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Estimate', 0));
             */
             $response->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Status', self::STATUS_SUCCESS));
             $collection = $response->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Collection'));
             $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Class', $collectionData['class']));
             $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'CollectionId', $collectionData['collectionId']));
             $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Estimate', 1));
         } else {
             $dataController = Syncope_Data_Factory::factory($collectionData['folder']->class, $this->_device, $this->_syncTimeStamp);
             $response->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Status', self::STATUS_SUCCESS));
             $collection = $response->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Collection'));
             $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Class', $collectionData['class']));
             $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'CollectionId', $collectionData['collectionId']));
             if ($collectionData['syncState']->counter === 0) {
                 // this is the first sync. in most cases there are data on the server.
                 $count = count($dataController->getServerEntries($collectionData['collectionId'], $collectionData['filterType']));
             } else {
                 $count = $dataController->getCountOfChanges($this->_contentStateBackend, $collectionData['folder'], $collectionData['syncState']);
             }
             $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Estimate', $count));
         }
         // folderState can be NULL in case of not existing folder
         if (isset($collectionData['folder'])) {
             $this->_folderBackend->update($collectionData['folder']);
         }
     }
     return $this->_outputDom;
 }
Beispiel #7
0
 /**
  * process the XML file and add, change, delete or fetches data 
  *
  * @todo can we get rid of LIBXML_NOWARNING
  * @todo we need to stored the initial data for folders and lifetime as the phone is sending them only when they change
  * @return resource
  */
 public function handle()
 {
     $intervalStart = time();
     $status = self::STATUS_NO_CHANGES_FOUND;
     // the client does not send a wbxml document, if the Ping parameters did not change compared with the last request
     if ($this->_inputDom instanceof DOMDocument) {
         $xml = simplexml_import_dom($this->_inputDom);
         $xml->registerXPathNamespace('Ping', 'Ping');
         if (isset($xml->HeartBeatInterval)) {
             $this->_device->pinglifetime = (int) $xml->HeartBeatInterval;
         }
         if (isset($xml->Folders->Folder)) {
             $folders = array();
             foreach ($xml->Folders->Folder as $folderXml) {
                 try {
                     // does the folder exist?
                     $folder = $this->_folderBackend->getFolder($this->_device, (string) $folderXml->Id);
                     $folders[] = $folder;
                 } catch (Syncope_Exception_NotFound $senf) {
                     if ($this->_logger instanceof Zend_Log) {
                         $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " " . $senf->getMessage());
                     }
                     $status = self::STATUS_FOLDER_NOT_FOUND;
                     break;
                 }
             }
             $this->_device->pingfolder = serialize($folders);
         }
         $this->_device = $this->_deviceBackend->update($this->_device);
     }
     $lifeTime = $this->_device->pinglifetime;
     #Tinebase_Core::setExecutionLifeTime($lifeTime);
     $intervalEnd = $intervalStart + $lifeTime;
     $secondsLeft = $intervalEnd;
     $folders = unserialize($this->_device->pingfolder);
     if ($this->_logger instanceof Zend_Log) {
         $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " Folders to monitor({$lifeTime} / {$intervalStart} / {$intervalEnd} / {$status}): " . print_r($folders, true));
     }
     if ($status === self::STATUS_NO_CHANGES_FOUND) {
         $folderWithChanges = array();
         do {
             foreach ((array) $folders as $folder) {
                 $dataController = Syncope_Data_Factory::factory($folder->class, $this->_device, $this->_syncTimeStamp);
                 try {
                     $syncState = $this->_syncStateBackend->getSyncState($this->_device, $folder);
                     $foundChanges = !!$dataController->getCountOfChanges($this->_contentStateBackend, $folder, $syncState);
                 } catch (Syncope_Exception_NotFound $e) {
                     // folder got never synchronized to client
                     if ($this->_logger instanceof Zend_Log) {
                         $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " " . $e->getMessage());
                     }
                     if ($this->_logger instanceof Zend_Log) {
                         $this->_logger->info(__METHOD__ . '::' . __LINE__ . ' syncstate not found. enforce sync for folder: ' . $folder->folderid);
                     }
                     $foundChanges = true;
                 }
                 if ($foundChanges == true) {
                     $this->_foldersWithChanges[] = $folder;
                     $status = self::STATUS_CHANGES_FOUND;
                 }
             }
             if ($status === self::STATUS_CHANGES_FOUND) {
                 break;
             }
             // another process synchronized data already
             if (isset($syncState) && $syncState->lastsync > $this->_syncTimeStamp) {
                 if ($this->_logger instanceof Zend_Log) {
                     $this->_logger->info(__METHOD__ . '::' . __LINE__ . " terminate ping process. Some other process updated data already.");
                 }
                 break;
             }
             sleep(self::PING_TIMEOUT);
             $secondsLeft = $intervalEnd - time();
             //if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " DeviceId: " . $this->_device->deviceid . " seconds left: " . $secondsLeft);
         } while ($secondsLeft > 0);
     }
     if ($this->_logger instanceof Zend_Log) {
         $this->_logger->info(__METHOD__ . '::' . __LINE__ . " DeviceId: " . $this->_device->deviceid . " Lifetime: {$lifeTime} SecondsLeft: {$secondsLeft} Status: {$status})");
     }
     $ping = $this->_outputDom->documentElement;
     $ping->appendChild($this->_outputDom->createElementNS('uri:Ping', 'Status', $status));
     if ($status === self::STATUS_CHANGES_FOUND) {
         $folders = $ping->appendChild($this->_outputDom->createElementNS('uri:Ping', 'Folders'));
         foreach ($this->_foldersWithChanges as $changedFolder) {
             $folder = $folders->appendChild($this->_outputDom->createElementNS('uri:Ping', 'Folder', $changedFolder->folderid));
             if ($this->_logger instanceof Zend_Log) {
                 $this->_logger->info(__METHOD__ . '::' . __LINE__ . " DeviceId: " . $this->_device->deviceid . " changes in folder: " . $changedFolder->folderid);
             }
         }
     }
 }
 public function testDeleteEntry()
 {
     $device = $this->_deviceBackend->create(Syncope_Backend_DeviceTests::getTestDevice(Syncope_Model_Device::TYPE_IPHONE));
     $dataController = Syncope_Data_Factory::factory(Syncope_Data_Factory::CLASS_CONTACTS, $device, new DateTime(null, new DateTimeZone('UTC')));
     Syncope_Data_AData::$entries['Syncope_Data_Contacts']['addressbookFolderId']['foobar'] = array();
     $this->assertArrayHasKey('foobar', Syncope_Data_Contacts::$entries['Syncope_Data_Contacts']['addressbookFolderId']);
     $dataController->deleteEntry('addressbookFolderId', 'foobar', array());
     $this->assertArrayNotHasKey('foobar', Syncope_Data_Contacts::$entries['Syncope_Data_Contacts']['addressbookFolderId']);
 }
Beispiel #9
0
 /**
  * (non-PHPdoc)
  * @see Syncope_Command_Wbxml::getResponse()
  */
 public function getResponse()
 {
     $this->_outputDom->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:AirSyncBase', 'uri:AirSyncBase');
     $sync = $this->_outputDom->documentElement;
     $collections = $sync->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Collections'));
     foreach ($this->_collections as $collectionData) {
         // invalid collectionid provided
         if (!$collectionData['folder'] instanceof Syncope_Model_IFolder) {
             $collection = $collections->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Collection'));
             $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'SyncKey', 0));
             $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'CollectionId', $collectionData['collectionId']));
             $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_FOLDER_HIERARCHY_HAS_CHANGED));
             // invalid synckey provided
         } elseif (!$collectionData['syncState'] instanceof Syncope_Model_ISyncState) {
             // set synckey to 0
             $collection = $collections->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Collection'));
             $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'SyncKey', 0));
             $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'CollectionId', $collectionData['collectionId']));
             $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_INVALID_SYNC_KEY));
             // initial sync
         } elseif ($collectionData['syncState']->counter === 0) {
             $collectionData['syncState']->counter++;
             // initial sync
             // send back a new SyncKey only
             $collection = $collections->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Collection'));
             if (!empty($collectionData['folder']->class)) {
                 $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Class', $collectionData['folder']->class));
             }
             $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'SyncKey', $collectionData['syncState']->counter));
             $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'CollectionId', $collectionData['collectionId']));
             $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_SUCCESS));
         } else {
             $dataController = Syncope_Data_Factory::factory($collectionData['folder']->class, $this->_device, $this->_syncTimeStamp);
             $serverAdds = array();
             $serverChanges = array();
             $serverDeletes = array();
             $moreAvailable = false;
             if ($collectionData['getChanges'] === true) {
                 // continue sync session?
                 if (is_array($collectionData['syncState']->pendingdata)) {
                     if ($this->_logger instanceof Zend_Log) {
                         $this->_logger->info(__METHOD__ . '::' . __LINE__ . " restored from sync state ");
                     }
                     $serverAdds = $collectionData['syncState']->pendingdata['serverAdds'];
                     $serverChanges = $collectionData['syncState']->pendingdata['serverChanges'];
                     $serverDeletes = $collectionData['syncState']->pendingdata['serverDeletes'];
                 } else {
                     // fetch entries added since last sync
                     $allClientEntries = $this->_contentStateBackend->getFolderState($this->_device, $collectionData['folder']);
                     $allServerEntries = $dataController->getServerEntries($collectionData['collectionId'], $collectionData['filterType']);
                     // add entries
                     $serverDiff = array_diff($allServerEntries, $allClientEntries);
                     // add entries which produced problems during delete from client
                     $serverAdds = $collectionData['forceAdd'];
                     // add entries not yet sent to client
                     $serverAdds = array_unique(array_merge($serverAdds, $serverDiff));
                     # @todo still needed?
                     foreach ($serverAdds as $id => $serverId) {
                         // skip entries added by client during this sync session
                         if (isset($collectionData['added'][$serverId]) && !isset($collectionData['forceAdd'][$serverId])) {
                             if ($this->_logger instanceof Zend_Log) {
                                 $this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped added entry: " . $serverId);
                             }
                             unset($serverAdds[$id]);
                         }
                     }
                     // entries to be deleted
                     $serverDeletes = array_diff($allClientEntries, $allServerEntries);
                     // fetch entries changed since last sync
                     $serverChanges = $dataController->getChangedEntries($collectionData['collectionId'], $collectionData['syncState']->lastsync, $this->_syncTimeStamp);
                     $serverChanges = array_merge($serverChanges, $collectionData['forceChange']);
                     foreach ($serverChanges as $id => $serverId) {
                         // skip entry, if it got changed by client during current sync
                         if (isset($collectionData['changed'][$serverId]) && !isset($collectionData['forceChange'][$serverId])) {
                             if ($this->_logger instanceof Zend_Log) {
                                 $this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped changed entry: " . $serverId);
                             }
                             unset($serverChanges[$id]);
                         }
                     }
                     // entries comeing in scope are already in $serverAdds and do not need to
                     // be send with $serverCanges
                     $serverChanges = array_diff($serverChanges, $serverAdds);
                 }
                 if ($this->_logger instanceof Zend_Log) {
                     $this->_logger->info(__METHOD__ . '::' . __LINE__ . " found (added/changed/deleted) " . count($serverAdds) . '/' . count($serverChanges) . '/' . count($serverDeletes) . ' entries for sync from server to client');
                 }
             }
             if (!empty($collectionData['added']) || !empty($collectionData['changed']) || !empty($collectionData['deleted']) || !empty($serverAdds) || !empty($serverChanges) || !empty($serverDeletes)) {
                 $collectionData['syncState']->counter++;
             }
             // collection header
             $collection = $collections->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Collection'));
             if (!empty($collectionData['folder']->class)) {
                 $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Class', $collectionData['folder']->class));
             }
             $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'SyncKey', $collectionData['syncState']->counter));
             $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'CollectionId', $collectionData['collectionId']));
             $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_SUCCESS));
             $responses = $this->_outputDom->createElementNS('uri:AirSync', 'Responses');
             // send reponse for newly added entries
             if (!empty($collectionData['added'])) {
                 foreach ($collectionData['added'] as $entryData) {
                     $add = $responses->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Add'));
                     $add->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ClientId', $entryData['clientId']));
                     // we have no serverId is the add failed
                     if (isset($entryData['serverId'])) {
                         $add->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ServerId', $entryData['serverId']));
                     }
                     $add->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', $entryData['status']));
                 }
             }
             // send reponse for changed entries
             if (!empty($collectionData['changed'])) {
                 foreach ($collectionData['changed'] as $serverId => $status) {
                     if ($status !== Syncope_Command_Sync::STATUS_SUCCESS) {
                         $change = $responses->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Change'));
                         $change->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ServerId', $serverId));
                         $change->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', $status));
                     }
                 }
             }
             // send response for to be fetched entries
             if (!empty($collectionData['toBeFetched'])) {
                 foreach ($collectionData['toBeFetched'] as $serverId) {
                     $fetch = $responses->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Fetch'));
                     $fetch->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ServerId', $serverId));
                     try {
                         $applicationData = $this->_outputDom->createElementNS('uri:AirSync', 'ApplicationData');
                         $dataController->appendXML($applicationData, $collectionData, $serverId);
                         $fetch->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_SUCCESS));
                         $fetch->appendChild($applicationData);
                     } catch (Exception $e) {
                         if ($this->_logger instanceof Zend_Log) {
                             $this->_logger->warn(__METHOD__ . '::' . __LINE__ . " unable to convert entry to xml: " . $e->getMessage());
                         }
                         $fetch->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_OBJECT_NOT_FOUND));
                     }
                 }
             }
             if ($responses->hasChildNodes() === true) {
                 $collection->appendChild($responses);
             }
             if (count($serverAdds) + count($serverChanges) + count($serverDeletes) > $collectionData['windowSize']) {
                 $moreAvailable = true;
                 $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'MoreAvailable'));
             }
             $commands = $this->_outputDom->createElementNS('uri:AirSync', 'Commands');
             /**
              * process entries added on server side
              */
             $newContentStates = array();
             foreach ($serverAdds as $id => $serverId) {
                 if ($this->_totalCount === $collectionData['windowSize']) {
                     break;
                 }
                 #/**
                 # * somewhere is a problem in the logic for handling moreAvailable
                 # *
                 # * it can happen, that we have a contentstate (which means we sent the entry to the client
                 # * and that this entry is yet in $collectionData['syncState']->pendingdata['serverAdds']
                 # * I have no idea how this can happen, but the next lines of code work around this problem
                 # */
                 #try {
                 #    $this->_contentStateBackend->getContentState($this->_device, $collectionData['folder'], $serverId);
                 #
                 #    if ($this->_logger instanceof Zend_Log)
                 #        $this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped an entry($serverId) which is already on the client");
                 #
                 #    unset($serverAdds[$id]);
                 #    continue;
                 #
                 #} catch (Syncope_Exception_NotFound $senf) {
                 #    // do nothing => content state should not exist yet
                 #}
                 try {
                     $add = $this->_outputDom->createElementNS('uri:AirSync', 'Add');
                     $add->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ServerId', $serverId));
                     $applicationData = $add->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ApplicationData'));
                     $dataController->appendXML($applicationData, $collectionData, $serverId);
                     $commands->appendChild($add);
                     $this->_totalCount++;
                 } catch (Exception $e) {
                     if ($this->_logger instanceof Zend_Log) {
                         $this->_logger->warn(__METHOD__ . '::' . __LINE__ . " unable to convert entry to xml: " . $e->getMessage());
                     }
                 }
                 // mark as send to the client, even the conversion to xml might have failed
                 $newContentStates[] = new Syncope_Model_Content(array('device_id' => $this->_device, 'folder_id' => $collectionData['folder'], 'contentid' => $serverId, 'creation_time' => $this->_syncTimeStamp, 'creation_synckey' => $collectionData['syncState']->counter));
                 unset($serverAdds[$id]);
             }
             /**
              * process entries changed on server side
              */
             foreach ($serverChanges as $id => $serverId) {
                 if ($this->_totalCount === $collectionData['windowSize']) {
                     break;
                 }
                 try {
                     $change = $this->_outputDom->createElementNS('uri:AirSync', 'Change');
                     $change->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ServerId', $serverId));
                     $applicationData = $change->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ApplicationData'));
                     $dataController->appendXML($applicationData, $collectionData, $serverId);
                     $commands->appendChild($change);
                     $this->_totalCount++;
                 } catch (Exception $e) {
                     if ($this->_logger instanceof Zend_Log) {
                         $this->_logger->warn(__METHOD__ . '::' . __LINE__ . " unable to convert entry to xml: " . $e->getMessage());
                     }
                 }
                 unset($serverChanges[$id]);
             }
             /**
              * process entries deleted on server side
              */
             $deletedContentStates = array();
             foreach ($serverDeletes as $id => $serverId) {
                 if ($this->_totalCount === $collectionData['windowSize']) {
                     break;
                 }
                 try {
                     // check if we have sent this entry to the phone
                     $state = $this->_contentStateBackend->getContentState($this->_device, $collectionData['folder'], $serverId);
                     $delete = $this->_outputDom->createElementNS('uri:AirSync', 'Delete');
                     $delete->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ServerId', $serverId));
                     $deletedContentStates[] = $state;
                     $commands->appendChild($delete);
                     $this->_totalCount++;
                 } catch (Exception $e) {
                     if ($this->_logger instanceof Zend_Log) {
                         $this->_logger->warn(__METHOD__ . '::' . __LINE__ . " unable to convert entry to xml: " . $e->getMessage());
                     }
                 }
                 unset($serverDeletes[$id]);
             }
             if ($commands->hasChildNodes() === true) {
                 $collection->appendChild($commands);
             }
             if ($this->_logger instanceof Zend_Log) {
                 $this->_logger->info(__METHOD__ . '::' . __LINE__ . " new synckey is " . $collectionData['syncState']->counter);
             }
         }
         if (isset($collectionData['syncState']) && $collectionData['syncState'] instanceof Syncope_Model_ISyncState && $collectionData['syncState']->counter != $collectionData['syncKey']) {
             // increment sync timestamp by 1 second
             $this->_syncTimeStamp->modify('+1 sec');
             // store pending data in sync state when needed
             if (isset($moreAvailable) && $moreAvailable === true) {
                 $collectionData['syncState']->pendingdata = array('serverAdds' => (array) $serverAdds, 'serverChanges' => (array) $serverChanges, 'serverDeletes' => (array) $serverDeletes);
             } else {
                 $collectionData['syncState']->pendingdata = null;
             }
             if (!empty($collectionData['added'])) {
                 if ($this->_logger instanceof Zend_Log) {
                     $this->_logger->info(__METHOD__ . '::' . __LINE__ . " remove previous synckey as client added new entries");
                 }
                 $keepPreviousSyncKey = false;
             } else {
                 $keepPreviousSyncKey = true;
             }
             $collectionData['syncState']->lastsync = $this->_syncTimeStamp;
             try {
                 $transactionId = Syncope_Registry::getTransactionManager()->startTransaction(Syncope_Registry::getDatabase());
                 // store new synckey
                 $this->_syncStateBackend->create($collectionData['syncState'], $keepPreviousSyncKey);
                 // store contentstates for new entries added to client
                 if (isset($newContentStates)) {
                     foreach ($newContentStates as $state) {
                         $this->_contentStateBackend->create($state);
                     }
                 }
                 // remove contentstates for entries to be deleted on client
                 if (isset($deletedContentStates)) {
                     foreach ($deletedContentStates as $state) {
                         $this->_contentStateBackend->delete($state);
                     }
                 }
                 Syncope_Registry::getTransactionManager()->commitTransaction($transactionId);
             } catch (Zend_Db_Statement_Exception $zdse) {
                 // something went wrong
                 // maybe another parallel request added a new synckey
                 // we must remove data added from client
                 if (!empty($collectionData['added'])) {
                     foreach ($collectionData['added'] as $added) {
                         $this->_contentStateBackend->delete($added['contentState']);
                         $dataController->deleteEntry($collectionData['collectionId'], $added['serverId'], array());
                     }
                 }
                 Syncope_Registry::getTransactionManager()->rollBack();
                 throw $zdse;
             }
             // store current filter type
             try {
                 $folderState = $this->_folderBackend->getFolder($this->_device, $collectionData['collectionId']);
                 $folderState->lastfiltertype = $collectionData['filterType'];
                 $this->_folderBackend->update($folderState);
             } catch (Syncope_Exception_NotFound $senf) {
                 // failed to get folderstate => should not happen but is also no problem in this state
                 if ($this->_logger instanceof Zend_Log) {
                     $this->_logger->crit(__METHOD__ . '::' . __LINE__ . ' failed to get content state for: ' . $collectionData['collectionId']);
                 }
             }
         }
     }
     return $this->_outputDom;
 }
 /**
  * generate FolderSync response
  * 
  * @todo changes are missing in response (folder got renamed for example)
  */
 public function getResponse()
 {
     $folderSync = $this->_outputDom->documentElement;
     if ($this->_syncState === false) {
         if ($this->_logger instanceof Zend_Log) {
             $this->_logger->info(__METHOD__ . '::' . __LINE__ . " INVALID synckey provided");
         }
         $folderSync->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Status', self::STATUS_INVALID_SYNC_KEY));
     } else {
         $folderSync->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Status', self::STATUS_SUCCESS));
         $adds = array();
         $deletes = array();
         $updates = array();
         foreach ($this->_classes as $class) {
             try {
                 $dataController = Syncope_Data_Factory::factory($class, $this->_device, $this->_syncTimeStamp);
             } catch (Zend_Exception $ze) {
                 // backend not defined
                 if ($this->_logger instanceof Zend_Log) {
                     $this->_logger->info(__METHOD__ . '::' . __LINE__ . " no data backend defined for class: " . $class);
                 }
                 continue;
             }
             // retrieve all folders available in data backend
             $serverFolders = $dataController->getAllFolders();
             // retrieve all folders sent to client
             $clientFolders = $this->_folderBackend->getFolderState($this->_device, $class);
             $serverFoldersIds = array_keys($serverFolders);
             // is this the first sync?
             if ($this->_syncState->counter == 0) {
                 $clientFoldersIds = array();
             } else {
                 $clientFoldersIds = array_keys($clientFolders);
             }
             // calculate added entries
             $serverDiff = array_diff($serverFoldersIds, $clientFoldersIds);
             foreach ($serverDiff as $serverFolderId) {
                 if (isset($clientFolders[$serverFolderId])) {
                     $adds[] = $clientFolders[$serverFolderId];
                 } else {
                     $adds[] = new Syncope_Model_Folder(array('device_id' => $this->_device, 'class' => $class, 'folderid' => $serverFolders[$serverFolderId]['folderId'], 'parentid' => $serverFolders[$serverFolderId]['parentId'], 'displayname' => $serverFolders[$serverFolderId]['displayName'], 'type' => $serverFolders[$serverFolderId]['type'], 'creation_time' => $this->_syncTimeStamp, 'lastfiltertype' => null));
                 }
             }
             // calculate deleted entries
             $serverDiff = array_diff($clientFoldersIds, $serverFoldersIds);
             foreach ($serverDiff as $serverFolderId) {
                 $deletes[] = $clientFolders[$serverFolderId];
             }
             // calculate changed entries
             $serverIntersect = array_intersect($clientFoldersIds, $serverFoldersIds);
             foreach ($serverIntersect as $serverIntersectId) {
                 if ($serverFolders[$serverIntersectId][parentId] != $clientFolders[$serverIntersectId]->parentid or $serverFolders[$serverIntersectId][displayName] != $clientFolders[$serverIntersectId]->displayname) {
                     $updates[] = new Syncope_Model_Folder(array('id' => $clientFolders[$serverIntersectId]->id, 'device_id' => $this->_device, 'class' => $class, 'folderid' => $serverFolders[$serverIntersectId]['folderId'], 'parentid' => $serverFolders[$serverIntersectId]['parentId'], 'displayname' => $serverFolders[$serverIntersectId]['displayName'], 'type' => $serverFolders[$serverIntersectId]['type'], 'creation_time' => $this->_syncTimeStamp, 'lastfiltertype' => null));
                 }
             }
         }
         $count = count($adds) + count($updates) + count($deletes);
         if ($count > 0) {
             $this->_syncState->counter++;
         }
         // create xml output
         $folderSync->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'SyncKey', $this->_syncState->counter));
         $changes = $folderSync->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Changes'));
         $changes->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Count', $count));
         foreach ($adds as $folder) {
             $add = $changes->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Add'));
             $add->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'ServerId', $folder->folderid));
             $add->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'ParentId', $folder->parentid));
             $displayName = $this->_outputDom->createElementNS('uri:FolderHierarchy', 'DisplayName');
             $displayName->appendChild($this->_outputDom->createTextNode($folder->displayname));
             $add->appendChild($displayName);
             $add->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Type', $folder->type));
             // store folder in backend
             if (empty($folder->id)) {
                 $this->_folderBackend->create($folder);
             }
         }
         foreach ($updates as $folder) {
             $update = $changes->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Update'));
             $update->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'ServerId', $folder->folderid));
             $update->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'ParentId', $folder->parentid));
             $displayName = $this->_outputDom->createElementNS('uri:FolderHierarchy', 'DisplayName');
             $displayName->appendChild($this->_outputDom->createTextNode($folder->displayname));
             $update->appendChild($displayName);
             $update->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Type', $folder->type));
             $this->_folderBackend->update($folder);
         }
         foreach ($deletes as $folder) {
             $delete = $changes->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Delete'));
             $delete->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'ServerId', $folder->folderid));
             $this->_folderBackend->delete($folder);
         }
         if (empty($this->_syncState->id)) {
             $this->_syncStateBackend->create($this->_syncState);
         } else {
             $this->_syncStateBackend->update($this->_syncState);
         }
     }
     return $this->_outputDom;
 }