/**
  * parse FolderUpdate request
  *
  */
 public function handle()
 {
     $xml = simplexml_import_dom($this->_requestBody);
     $syncKey = (int) $xml->SyncKey;
     if ($this->_logger instanceof Syncroton_Log) {
         $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " synckey is {$syncKey}");
     }
     if (!($this->_syncState = $this->_syncStateBackend->validate($this->_device, 'FolderSync', $syncKey)) instanceof Syncroton_Model_SyncState) {
         return;
     }
     $updatedFolder = new Syncroton_Model_Folder($xml);
     if ($this->_logger instanceof Syncroton_Log) {
         $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " parentId: {$updatedFolder->parentId} displayName: {$updatedFolder->displayName}");
     }
     try {
         $folder = $this->_folderBackend->getFolder($this->_device, $updatedFolder->serverId);
         $folder->displayName = $updatedFolder->displayName;
         $folder->parentId = $updatedFolder->parentId;
         $dataController = Syncroton_Data_Factory::factory($folder->class, $this->_device, $this->_syncTimeStamp);
         // update folder in data backend
         $dataController->updateFolder($folder);
     } catch (Syncroton_Exception_NotFound $senf) {
         if ($this->_logger instanceof Syncroton_Log) {
             $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " " . $senf->getMessage());
         }
         return;
     }
     // update folder status in Syncroton backend
     $this->_folder = $this->_folderBackend->update($folder);
 }
 /**
  * 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 {
             if ($move['srcFldId'] === $move['dstFldId']) {
                 throw new Syncroton_Exception_Status_MoveItems(Syncroton_Exception_Status_MoveItems::SAME_FOLDER);
             }
             try {
                 $sourceFolder = $this->_folderBackend->getFolder($this->_device, $move['srcFldId']);
             } catch (Syncroton_Exception_NotFound $e) {
                 throw new Syncroton_Exception_Status_MoveItems(Syncroton_Exception_Status_MoveItems::INVALID_SOURCE);
             }
             try {
                 $destinationFolder = $this->_folderBackend->getFolder($this->_device, $move['dstFldId']);
             } catch (Syncroton_Exception_NotFound $senf) {
                 throw new Syncroton_Exception_Status_MoveItems(Syncroton_Exception_Status_MoveItems::INVALID_DESTINATION);
             }
             $dataController = Syncroton_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', Syncroton_Command_MoveItems::STATUS_SUCCESS));
             $response->appendChild($this->_outputDom->createElementNS('uri:Move', 'DstMsgId', $newId));
         } catch (Syncroton_Exception_Status $e) {
             $response->appendChild($this->_outputDom->createElementNS('uri:Move', 'Status', $e->getCode()));
         } catch (Exception $e) {
             $response->appendChild($this->_outputDom->createElementNS('uri:Move', 'Status', Syncroton_Exception_Status::SERVER_ERROR));
         }
     }
     return $this->_outputDom;
 }
Exemple #3
0
 /**
  * generate search command response
  *
  */
 public function getResponse()
 {
     $dataController = Syncroton_Data_Factory::factory($this->_store->name, $this->_device, new DateTime());
     if (!$dataController instanceof Syncroton_Data_IDataSearch) {
         throw new RuntimeException('class must be instanceof Syncroton_Data_IDataSearch');
     }
     try {
         // Search
         $storeResponse = $dataController->search($this->_store);
         $storeResponse->status = self::STATUS_SUCCESS;
     } catch (Exception $e) {
         if ($this->_logger instanceof Syncroton_Log) {
             $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " search exception: " . $e->getMessage());
         }
         if ($this->_logger instanceof Syncroton_Log) {
             $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " saerch exception trace : " . $e->getTraceAsString());
         }
         $storeResponse = new Syncroton_Model_StoreResponse(array('status' => self::STATUS_SERVER_ERROR));
     }
     $search = $this->_outputDom->documentElement;
     $search->appendChild($this->_outputDom->createElementNS($this->_defaultNameSpace, 'Status', self::STATUS_SUCCESS));
     $response = $search->appendChild($this->_outputDom->createElementNS($this->_defaultNameSpace, 'Response'));
     $store = $response->appendChild($this->_outputDom->createElementNS($this->_defaultNameSpace, 'Store'));
     $storeResponse->appendXML($store, $this->_device);
     return $this->_outputDom;
 }
 /**
  * parse FolderDelete request
  */
 public function handle()
 {
     $xml = simplexml_import_dom($this->_requestBody);
     // parse xml request
     $syncKey = (int) $xml->SyncKey;
     $serverId = (string) $xml->ServerId;
     if ($this->_logger instanceof Syncroton_Log) {
         $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " synckey is {$syncKey}");
     }
     if (!($this->_syncState = $this->_syncStateBackend->validate($this->_device, 'FolderSync', $syncKey)) instanceof Syncroton_Model_SyncState) {
         return;
     }
     try {
         $folder = $this->_folderBackend->getFolder($this->_device, $serverId);
         $dataController = Syncroton_Data_Factory::factory($folder->class, $this->_device, $this->_syncTimeStamp);
         // delete folder in data backend
         $dataController->deleteFolder($folder);
     } catch (Syncroton_Exception_NotFound $senf) {
         if ($this->_logger instanceof Syncroton_Log) {
             $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " " . $senf->getMessage());
         }
         return;
     }
     // delete folder in syncState backend
     $this->_folderBackend->delete($folder);
     $this->_folder = $folder;
 }
 /**
  * this function generates the response for the client
  *
  * @return void
  */
 public function getResponse()
 {
     $dataController = Syncroton_Data_Factory::factory(Syncroton_Data_Factory::CLASS_EMAIL, $this->_device, $this->_syncTimeStamp);
     try {
         $dataController->sendEmail($this->_mime, $this->_saveInSent);
     } catch (Syncroton_Exception_Status $ses) {
         if ($this->_logger instanceof Syncroton_Log) {
             $this->_logger->warning(__METHOD__ . '::' . __LINE__ . " Sending email failed: " . $ses->getMessage());
         }
         $response = new Syncroton_Model_SendMail(array('status' => $ses->getCode()));
         $response->appendXML($this->_outputDom->documentElement, $this->_device);
         return $this->_outputDom;
     }
 }
 /**
  * parse MeetingResponse request
  */
 public function handle()
 {
     $dataController = Syncroton_Data_Factory::factory(Syncroton_Data_Factory::CLASS_CALENDAR, $this->_device, $this->_syncTimeStamp);
     $xml = simplexml_import_dom($this->_requestBody);
     foreach ($xml as $meetingResponse) {
         $request = new Syncroton_Model_MeetingResponse($meetingResponse);
         try {
             $calendarId = $dataController->setAttendeeStatus($request);
             $this->_results[] = array('calendarId' => $calendarId, 'request' => $request, 'status' => 1);
         } catch (Syncroton_Exception_Status_MeetingResponse $sesmr) {
             $this->_results[] = array('request' => $request, 'status' => $sesmr->getCode());
         }
     }
     if ($this->_logger instanceof Syncroton_Log) {
         $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " results: " . print_r($this->_results, true));
     }
 }
 /**
  * parse FolderCreate request
  *
  */
 public function handle()
 {
     $xml = simplexml_import_dom($this->_requestBody);
     $syncKey = (int) $xml->SyncKey;
     if ($this->_logger instanceof Syncroton_Log) {
         $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " synckey is {$syncKey}");
     }
     if (!($this->_syncState = $this->_syncStateBackend->validate($this->_device, 'FolderSync', $syncKey)) instanceof Syncroton_Model_SyncState) {
         return;
     }
     $folder = new Syncroton_Model_Folder($xml);
     if ($this->_logger instanceof Syncroton_Log) {
         $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " parentId: {$folder->parentId} displayName: {$folder->displayName}");
     }
     switch ($folder->type) {
         case Syncroton_Command_FolderSync::FOLDERTYPE_CALENDAR_USER_CREATED:
             $folder->class = Syncroton_Data_Factory::CLASS_CALENDAR;
             break;
         case Syncroton_Command_FolderSync::FOLDERTYPE_CONTACT_USER_CREATED:
             $folder->class = Syncroton_Data_Factory::CLASS_CONTACTS;
             break;
         case Syncroton_Command_FolderSync::FOLDERTYPE_MAIL_USER_CREATED:
             $folder->class = Syncroton_Data_Factory::CLASS_EMAIL;
             break;
         case Syncroton_Command_FolderSync::FOLDERTYPE_NOTE_USER_CREATED:
             $folder->class = Syncroton_Data_Factory::CLASS_NOTES;
             break;
         case Syncroton_Command_FolderSync::FOLDERTYPE_TASK_USER_CREATED:
             $folder->class = Syncroton_Data_Factory::CLASS_TASKS;
             break;
         default:
             throw new Syncroton_Exception_UnexpectedValue('invalid type defined');
             break;
     }
     $dataController = Syncroton_Data_Factory::factory($folder->class, $this->_device, $this->_syncTimeStamp);
     $this->_folder = $dataController->createFolder($folder);
     $this->_folder->class = $folder->class;
     $this->_folder->deviceId = $this->_device;
     $this->_folder->creationTime = $this->_syncTimeStamp;
     $this->_folderBackend->create($this->_folder);
 }
 /**
  * this function generates the response for the client
  *
  * @return void
  */
 public function getResponse()
 {
     $dataController = Syncroton_Data_Factory::factory(Syncroton_Data_Factory::CLASS_EMAIL, $this->_device, $this->_syncTimeStamp);
     $attachment = $dataController->getFileReference($this->_attachmentName);
     if (PHP_SAPI !== 'cli') {
         // cache for 3600 seconds
         $maxAge = 3600;
         $now = new DateTime(null, new DateTimeZone('UTC'));
         header('Cache-Control: private, max-age=' . $maxAge);
         header("Expires: " . gmdate('D, d M Y H:i:s', $now->modify("+{$maxAge} sec")->getTimestamp()) . " GMT");
         // overwrite Pragma header from session
         header("Pragma: cache");
         #header('Content-Disposition: attachment; filename="' . $filename . '"');
         header("Content-Type: " . $attachment->contentType);
     }
     if (is_resource($attachment->data)) {
         fpassthru($attachment->data);
     } else {
         echo $attachment->data;
     }
 }
 /**
  * test if only default addressbook is default AS folder 
  */
 public function testGetAllFoldersIPhone()
 {
     $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE), Tinebase_DateTime::now());
     $allSyncrotonFolders = $controller->getAllFolders();
     $defaultFolderId = Tinebase_Core::getPreference('Addressbook')->{Addressbook_Preference::DEFAULTADDRESSBOOK};
     foreach ($allSyncrotonFolders as $syncrotonFolder) {
         $this->assertTrue($syncrotonFolder->serverId == $defaultFolderId ? $syncrotonFolder->type === Syncroton_Command_FolderSync::FOLDERTYPE_CONTACT : $syncrotonFolder->type === Syncroton_Command_FolderSync::FOLDERTYPE_CONTACT_USER_CREATED);
     }
 }
Exemple #10
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->_requestBody instanceof DOMDocument) {
         $xml = simplexml_import_dom($this->_requestBody);
         $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->id] = $folder;
                 } catch (Syncroton_Exception_NotFound $senf) {
                     if ($this->_logger instanceof Syncroton_Log) {
                         $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " " . $senf->getMessage());
                     }
                     $status = self::STATUS_FOLDER_NOT_FOUND;
                     break;
                 }
             }
             $this->_device->pingfolder = serialize(array_keys($folders));
         }
     }
     $this->_device->lastping = new DateTime('now', new DateTimeZone('utc'));
     if ($status == self::STATUS_NO_CHANGES_FOUND) {
         $this->_device = $this->_deviceBackend->update($this->_device);
     }
     $lifeTime = $this->_device->pinglifetime;
     $maxLifeTime = Syncroton_Registry::getMaxPingInterval();
     if ($maxLifeTime > 0 && $lifeTime > $maxLifeTime) {
         $ping = $this->_outputDom->documentElement;
         $ping->appendChild($this->_outputDom->createElementNS('uri:Ping', 'Status', self::STATUS_INTERVAL_TO_GREAT_OR_SMALL));
         $ping->appendChild($this->_outputDom->createElementNS('uri:Ping', 'HeartbeatInterval', $maxLifeTime));
         return;
     }
     $intervalEnd = $intervalStart + $lifeTime;
     $secondsLeft = $intervalEnd;
     $folders = unserialize($this->_device->pingfolder);
     if ($status === self::STATUS_NO_CHANGES_FOUND && (!is_array($folders) || count($folders) == 0)) {
         $status = self::STATUS_MISSING_PARAMETERS;
     }
     if ($this->_logger instanceof Syncroton_Log) {
         $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " Folders to monitor({$lifeTime} / {$intervalStart} / {$intervalEnd} / {$status}): " . print_r($folders, true));
     }
     if ($status === self::STATUS_NO_CHANGES_FOUND) {
         do {
             // take a break to save battery lifetime
             sleep(Syncroton_Registry::getPingTimeout());
             try {
                 $device = $this->_deviceBackend->get($this->_device->id);
             } catch (Syncroton_Exception_NotFound $e) {
                 if ($this->_logger instanceof Syncroton_Log) {
                     $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " " . $e->getMessage());
                 }
                 $status = self::STATUS_FOLDER_NOT_FOUND;
                 break;
             } catch (Exception $e) {
                 if ($this->_logger instanceof Syncroton_Log) {
                     $this->_logger->err(__METHOD__ . '::' . __LINE__ . " " . $e->getMessage());
                 }
                 // do nothing, maybe temporal issue, should we stop?
                 continue;
             }
             // if another Ping command updated lastping property, we can stop processing this Ping command request
             if (isset($device->lastping) && $device->lastping instanceof DateTime && $device->pingfolder === $this->_device->pingfolder && $device->lastping->getTimestamp() > $this->_device->lastping->getTimestamp()) {
                 break;
             }
             $now = new DateTime('now', new DateTimeZone('utc'));
             foreach ($folders as $folderId) {
                 try {
                     $folder = $this->_folderBackend->get($folderId);
                     $dataController = Syncroton_Data_Factory::factory($folder->class, $this->_device, $this->_syncTimeStamp);
                 } catch (Syncroton_Exception_NotFound $e) {
                     if ($this->_logger instanceof Syncroton_Log) {
                         $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " " . $e->getMessage());
                     }
                     $status = self::STATUS_FOLDER_NOT_FOUND;
                     break;
                 } catch (Exception $e) {
                     if ($this->_logger instanceof Syncroton_Log) {
                         $this->_logger->error(__METHOD__ . '::' . __LINE__ . " " . $e->getMessage());
                     }
                     // do nothing, maybe temporal issue, should we stop?
                     continue;
                 }
                 try {
                     $syncState = $this->_syncStateBackend->getSyncState($this->_device, $folder);
                     // another process synchronized data of this folder already. let's skip it
                     if ($syncState->lastsync > $this->_syncTimeStamp) {
                         continue;
                     }
                     // safe battery time by skipping folders which got synchronied less than Syncroton_Registry::getQuietTime() seconds ago
                     if ($now->getTimestamp() - $syncState->lastsync->getTimestamp() < Syncroton_Registry::getQuietTime()) {
                         continue;
                     }
                     $foundChanges = $dataController->hasChanges($this->_contentStateBackend, $folder, $syncState);
                 } catch (Syncroton_Exception_NotFound $e) {
                     // folder got never synchronized to client
                     if ($this->_logger instanceof Syncroton_Log) {
                         $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " " . $e->getMessage());
                     }
                     if ($this->_logger instanceof Syncroton_Log) {
                         $this->_logger->info(__METHOD__ . '::' . __LINE__ . ' syncstate not found. enforce sync for folder: ' . $folder->serverId);
                     }
                     $foundChanges = true;
                 }
                 if ($foundChanges == true) {
                     $this->_foldersWithChanges[] = $folder;
                     $status = self::STATUS_CHANGES_FOUND;
                 }
             }
             if ($status != self::STATUS_NO_CHANGES_FOUND) {
                 break;
             }
             $secondsLeft = $intervalEnd - time();
             if ($this->_logger instanceof Syncroton_Log) {
                 $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " DeviceId: " . $this->_device->deviceid . " seconds left: " . $secondsLeft);
             }
             // See: http://www.tine20.org/forum/viewtopic.php?f=12&t=12146
             //
             // break if there are less than PingTimeout + 10 seconds left for the next loop
             // otherwise the response will be returned after the client has finished his Ping
             // request already maybe
         } while ($secondsLeft > Syncroton_Registry::getPingTimeout() + 10);
     }
     if ($this->_logger instanceof Syncroton_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->serverId));
             if ($this->_logger instanceof Syncroton_Log) {
                 $this->_logger->info(__METHOD__ . '::' . __LINE__ . " DeviceId: " . $this->_device->deviceid . " changes in folder: " . $changedFolder->serverId);
             }
         }
     }
 }
Exemple #11
0
 /**
  * (non-PHPdoc)
  * @see Syncroton_Command_Wbxml::getResponse()
  */
 public function getResponse()
 {
     $sync = $this->_outputDom->documentElement;
     $collections = $this->_outputDom->createElementNS('uri:AirSync', 'Collections');
     $totalChanges = 0;
     // Detect devices that do not support empty Sync reponse
     $emptySyncSupported = !preg_match('/(meego|nokian800)/i', $this->_device->useragent);
     // continue only if there are changes or no time is left
     if ($this->_heartbeatInterval > 0) {
         $intervalStart = time();
         do {
             // take a break to save battery lifetime
             sleep(Syncroton_Registry::getPingTimeout());
             $now = new DateTime(null, new DateTimeZone('utc'));
             foreach ($this->_collections as $collectionData) {
                 // continue immediately if folder does not exist
                 if (!$collectionData->folder instanceof Syncroton_Model_IFolder) {
                     break 2;
                     // countinue immediately if syncstate is invalid
                 } elseif (!$collectionData->syncState instanceof Syncroton_Model_ISyncState) {
                     break 2;
                 } else {
                     if ($collectionData->getChanges !== true) {
                         continue;
                     }
                     try {
                         // just check if the folder still exists
                         $this->_folderBackend->get($collectionData->folder);
                     } catch (Syncroton_Exception_NotFound $senf) {
                         if ($this->_logger instanceof Syncroton_Log) {
                             $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " collection does not exist anymore: " . $collectionData->collectionId);
                         }
                         $collectionData->getChanges = false;
                         // make sure this is the last while loop
                         // no break 2 here, as we like to check the other folders too
                         $intervalStart -= $this->_heartbeatInterval;
                     }
                     // check that the syncstate still exists and is still valid
                     try {
                         $syncState = $this->_syncStateBackend->getSyncState($this->_device, $collectionData->folder);
                         // another process synchronized data of this folder already. let's skip it
                         if ($syncState->id !== $collectionData->syncState->id) {
                             if ($this->_logger instanceof Syncroton_Log) {
                                 $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " syncstate changed during heartbeat interval for collection: " . $collectionData->folder->serverId);
                             }
                             $collectionData->getChanges = false;
                             // make sure this is the last while loop
                             // no break 2 here, as we like to check the other folders too
                             $intervalStart -= $this->_heartbeatInterval;
                         }
                     } catch (Syncroton_Exception_NotFound $senf) {
                         if ($this->_logger instanceof Syncroton_Log) {
                             $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " no syncstate found anymore for collection: " . $collectionData->folder->serverId);
                         }
                         $collectionData->syncState = null;
                         // make sure this is the last while loop
                         // no break 2 here, as we like to check the other folders too
                         $intervalStart -= $this->_heartbeatInterval;
                     }
                     // safe battery time by skipping folders which got synchronied less than Syncroton_Command_Ping::$quietTime seconds ago
                     if (!$collectionData->syncState instanceof Syncroton_Model_SyncState || $now->getTimestamp() - $collectionData->syncState->lastsync->getTimestamp() < Syncroton_Registry::getQuietTime()) {
                         continue;
                     }
                     $dataController = Syncroton_Data_Factory::factory($collectionData->folder->class, $this->_device, $this->_syncTimeStamp);
                     // countinue immediately if there are any changes available
                     if ($dataController->hasChanges($this->_contentStateBackend, $collectionData->folder, $collectionData->syncState)) {
                         break 2;
                     }
                 }
             }
             // See: http://www.tine20.org/forum/viewtopic.php?f=12&t=12146
             //
             // break if there are less than PingTimeout + 10 seconds left for the next loop
             // otherwise the response will be returned after the client has finished his Ping
             // request already maybe
         } while (time() - $intervalStart < $this->_heartbeatInterval - (Syncroton_Registry::getPingTimeout() + 10));
     }
     foreach ($this->_collections as $collectionData) {
         $collectionChanges = 0;
         /**
          * keep track of entries added on server side
          */
         $newContentStates = array();
         /**
          * keep track of entries deleted on server side
          */
         $deletedContentStates = array();
         // invalid collectionid provided
         if (!$collectionData->folder instanceof Syncroton_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 Syncroton_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 = Syncroton_Data_Factory::factory($collectionData->folder->class, $this->_device, $this->_syncTimeStamp);
             $clientModifications = $this->_modifications[$collectionData->collectionId];
             $serverModifications = array('added' => array(), 'changed' => array(), 'deleted' => array());
             if ($collectionData->getChanges === true) {
                 // continue sync session?
                 if (is_array($collectionData->syncState->pendingdata)) {
                     if ($this->_logger instanceof Syncroton_Log) {
                         $this->_logger->info(__METHOD__ . '::' . __LINE__ . " restored from sync state ");
                     }
                     $serverModifications = $collectionData->syncState->pendingdata;
                 } elseif ($dataController->hasChanges($this->_contentStateBackend, $collectionData->folder, $collectionData->syncState)) {
                     // update _syncTimeStamp as $dataController->hasChanges might have spent some time
                     $this->_syncTimeStamp = new DateTime(null, new DateTimeZone('utc'));
                     try {
                         // fetch entries added since last sync
                         $allClientEntries = $this->_contentStateBackend->getFolderState($this->_device, $collectionData->folder);
                         $allServerEntries = $dataController->getServerEntries($collectionData->collectionId, $collectionData->options['filterType']);
                         // add entries
                         $serverDiff = array_diff($allServerEntries, $allClientEntries);
                         // add entries which produced problems during delete from client
                         $serverModifications['added'] = $clientModifications['forceAdd'];
                         // add entries not yet sent to client
                         $serverModifications['added'] = array_unique(array_merge($serverModifications['added'], $serverDiff));
                         // @todo still needed?
                         foreach ($serverModifications['added'] as $id => $serverId) {
                             // skip entries added by client during this sync session
                             if (isset($clientModifications['added'][$serverId]) && !isset($clientModifications['forceAdd'][$serverId])) {
                                 if ($this->_logger instanceof Syncroton_Log) {
                                     $this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped added entry: " . $serverId);
                                 }
                                 unset($serverModifications['added'][$id]);
                             }
                         }
                         // entries to be deleted
                         $serverModifications['deleted'] = array_diff($allClientEntries, $allServerEntries);
                         // fetch entries changed since last sync
                         $serverModifications['changed'] = $dataController->getChangedEntries($collectionData->collectionId, $collectionData->syncState->lastsync, $this->_syncTimeStamp, $collectionData->options['filterType']);
                         $serverModifications['changed'] = array_merge($serverModifications['changed'], $clientModifications['forceChange']);
                         foreach ($serverModifications['changed'] as $id => $serverId) {
                             // skip entry, if it got changed by client during current sync
                             if (isset($clientModifications['changed'][$serverId]) && !isset($clientModifications['forceChange'][$serverId])) {
                                 if ($this->_logger instanceof Syncroton_Log) {
                                     $this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped changed entry: " . $serverId);
                                 }
                                 unset($serverModifications['changed'][$id]);
                             } else {
                                 if (isset($clientModifications['added'][$serverId]) && !isset($clientModifications['forceAdd'][$serverId])) {
                                     if ($this->_logger instanceof Syncroton_Log) {
                                         $this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped change for added entry: " . $serverId);
                                     }
                                     unset($serverModifications['changed'][$id]);
                                 }
                             }
                         }
                         // entries comeing in scope are already in $serverModifications['added'] and do not need to
                         // be send with $serverCanges
                         $serverModifications['changed'] = array_diff($serverModifications['changed'], $serverModifications['added']);
                     } catch (Exception $e) {
                         if ($this->_logger instanceof Syncroton_Log) {
                             $this->_logger->crit(__METHOD__ . '::' . __LINE__ . " Folder state checking failed: " . $e->getMessage());
                         }
                         if ($this->_logger instanceof Syncroton_Log) {
                             $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " Folder state checking failed: " . $e->getTraceAsString());
                         }
                         // Prevent from removing client entries when getServerEntries() fails
                         // @todo: should we set Status and break the loop here?
                         $serverModifications = array('added' => array(), 'changed' => array(), 'deleted' => array());
                     }
                 }
                 if ($this->_logger instanceof Syncroton_Log) {
                     $this->_logger->info(__METHOD__ . '::' . __LINE__ . " found (added/changed/deleted) " . count($serverModifications['added']) . '/' . count($serverModifications['changed']) . '/' . count($serverModifications['deleted']) . ' entries for sync from server to client');
                 }
             }
             // collection header
             $collection = $this->_outputDom->createElementNS('uri:AirSync', 'Collection');
             if (!empty($collectionData->folder->class)) {
                 $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Class', $collectionData->folder->class));
             }
             $syncKeyElement = $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'SyncKey'));
             $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($clientModifications['added'])) {
                 foreach ($clientModifications['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($clientModifications['changed'])) {
                 foreach ($clientModifications['changed'] as $serverId => $status) {
                     if ($status !== Syncroton_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)) {
                 // unset all truncation settings as entries are not allowed to be truncated during fetch
                 $fetchCollectionData = clone $collectionData;
                 // unset truncationSize
                 if (isset($fetchCollectionData->options['bodyPreferences']) && is_array($fetchCollectionData->options['bodyPreferences'])) {
                     foreach ($fetchCollectionData->options['bodyPreferences'] as $key => $bodyPreference) {
                         unset($fetchCollectionData->options['bodyPreferences'][$key]['truncationSize']);
                     }
                 }
                 $fetchCollectionData->options['mimeTruncation'] = Syncroton_Command_Sync::TRUNCATE_NOTHING;
                 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->getEntry($fetchCollectionData, $serverId)->appendXML($applicationData, $this->_device);
                         $fetch->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_SUCCESS));
                         $fetch->appendChild($applicationData);
                     } catch (Exception $e) {
                         if ($this->_logger instanceof Syncroton_Log) {
                             $this->_logger->warning(__METHOD__ . '::' . __LINE__ . " unable to convert entry to xml: " . $e->getMessage());
                         }
                         if ($this->_logger instanceof Syncroton_Log) {
                             $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " unable to convert entry to xml: " . $e->getTraceAsString());
                         }
                         $fetch->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_OBJECT_NOT_FOUND));
                     }
                 }
             }
             if ($responses->hasChildNodes() === true) {
                 $collection->appendChild($responses);
             }
             $commands = $this->_outputDom->createElementNS('uri:AirSync', 'Commands');
             foreach ($serverModifications['added'] as $id => $serverId) {
                 if ($collectionChanges == $collectionData->windowSize || $totalChanges + $collectionChanges >= $this->_globalWindowSize) {
                     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 Syncroton_Log) {
                         $this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped an entry({$serverId}) which is already on the client");
                     }
                     unset($serverModifications['added'][$id]);
                     continue;
                 } catch (Syncroton_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->getEntry($collectionData, $serverId)->appendXML($applicationData, $this->_device);
                     $commands->appendChild($add);
                     $collectionChanges++;
                 } catch (Syncroton_Exception_MemoryExhausted $seme) {
                     // continue to next entry, as there is not enough memory left for the current entry
                     // this will lead to MoreAvailable at the end and the entry will be synced during the next Sync command
                     if ($this->_logger instanceof Syncroton_Log) {
                         $this->_logger->warning(__METHOD__ . '::' . __LINE__ . " memory exhausted for entry: " . $serverId);
                     }
                     continue;
                 } catch (Exception $e) {
                     if ($this->_logger instanceof Syncroton_Log) {
                         $this->_logger->warning(__METHOD__ . '::' . __LINE__ . " unable to convert entry to xml: " . $e->getMessage());
                     }
                     if ($this->_logger instanceof Syncroton_Log) {
                         $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " unable to convert entry to xml: " . $e->getTraceAsString());
                     }
                 }
                 // mark as sent to the client, even the conversion to xml might have failed
                 $newContentStates[] = new Syncroton_Model_Content(array('device_id' => $this->_device, 'folder_id' => $collectionData->folder, 'contentid' => $serverId, 'creation_time' => $this->_syncTimeStamp, 'creation_synckey' => $collectionData->syncState->counter + 1));
                 unset($serverModifications['added'][$id]);
             }
             /**
              * process entries changed on server side
              */
             foreach ($serverModifications['changed'] as $id => $serverId) {
                 if ($collectionChanges == $collectionData->windowSize || $totalChanges + $collectionChanges >= $this->_globalWindowSize) {
                     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->getEntry($collectionData, $serverId)->appendXML($applicationData, $this->_device);
                     $commands->appendChild($change);
                     $collectionChanges++;
                 } catch (Syncroton_Exception_MemoryExhausted $seme) {
                     // continue to next entry, as there is not enough memory left for the current entry
                     // this will lead to MoreAvailable at the end and the entry will be synced during the next Sync command
                     if ($this->_logger instanceof Syncroton_Log) {
                         $this->_logger->warning(__METHOD__ . '::' . __LINE__ . " memory exhausted for entry: " . $serverId);
                     }
                     continue;
                 } catch (Exception $e) {
                     if ($this->_logger instanceof Syncroton_Log) {
                         $this->_logger->warning(__METHOD__ . '::' . __LINE__ . " unable to convert entry to xml: " . $e->getMessage());
                     }
                 }
                 unset($serverModifications['changed'][$id]);
             }
             foreach ($serverModifications['deleted'] as $id => $serverId) {
                 if ($collectionChanges == $collectionData->windowSize || $totalChanges + $collectionChanges >= $this->_globalWindowSize) {
                     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);
                     $collectionChanges++;
                 } catch (Exception $e) {
                     if ($this->_logger instanceof Syncroton_Log) {
                         $this->_logger->warning(__METHOD__ . '::' . __LINE__ . " unable to convert entry to xml: " . $e->getMessage());
                     }
                 }
                 unset($serverModifications['deleted'][$id]);
             }
             $countOfPendingChanges = count($serverModifications['added']) + count($serverModifications['changed']) + count($serverModifications['deleted']);
             if ($countOfPendingChanges > 0) {
                 $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'MoreAvailable'));
             } else {
                 $serverModifications = null;
             }
             if ($commands->hasChildNodes() === true) {
                 $collection->appendChild($commands);
             }
             $totalChanges += $collectionChanges;
             // increase SyncKey if needed
             if (!empty($clientModifications['added']) || !empty($clientModifications['changed']) || !empty($clientModifications['deleted']) || $commands->hasChildNodes() === true || $collectionData->syncState->pendingdata != $serverModifications) {
                 // ...then increase SyncKey
                 $collectionData->syncState->counter++;
             }
             $syncKeyElement->appendChild($this->_outputDom->createTextNode($collectionData->syncState->counter));
             if ($this->_logger instanceof Syncroton_Log) {
                 $this->_logger->info(__METHOD__ . '::' . __LINE__ . " current synckey is " . $collectionData->syncState->counter);
             }
             if (!$emptySyncSupported || $collection->childNodes->length > 4 || $collectionData->syncState->counter != $collectionData->syncKey) {
                 $collections->appendChild($collection);
             }
         }
         if (isset($collectionData->syncState) && $collectionData->syncState instanceof Syncroton_Model_ISyncState && $collectionData->syncState->counter != $collectionData->syncKey) {
             if ($this->_logger instanceof Syncroton_Log) {
                 $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " update syncState for collection: " . $collectionData->collectionId);
             }
             // store pending data in sync state when needed
             if (isset($countOfPendingChanges) && $countOfPendingChanges > 0) {
                 $collectionData->syncState->pendingdata = array('added' => (array) $serverModifications['added'], 'changed' => (array) $serverModifications['changed'], 'deleted' => (array) $serverModifications['deleted']);
             } else {
                 $collectionData->syncState->pendingdata = null;
             }
             if (!empty($clientModifications['added'])) {
                 if ($this->_logger instanceof Syncroton_Log) {
                     $this->_logger->info(__METHOD__ . '::' . __LINE__ . " remove previous synckey as client added new entries");
                 }
                 $keepPreviousSyncKey = false;
             } else {
                 $keepPreviousSyncKey = true;
             }
             $collectionData->syncState->lastsync = clone $this->_syncTimeStamp;
             // increment sync timestamp by 1 second
             $collectionData->syncState->lastsync->modify('+1 sec');
             try {
                 $transactionId = Syncroton_Registry::getTransactionManager()->startTransaction(Syncroton_Registry::getDatabase());
                 // store new synckey
                 $this->_syncStateBackend->create($collectionData->syncState, $keepPreviousSyncKey);
                 // store contentstates for new entries added to client
                 foreach ($newContentStates as $state) {
                     $this->_contentStateBackend->create($state);
                 }
                 // remove contentstates for entries to be deleted on client
                 foreach ($deletedContentStates as $state) {
                     $this->_contentStateBackend->delete($state);
                 }
                 Syncroton_Registry::getTransactionManager()->commitTransaction($transactionId);
                 //} catch (Zend_Db_Statement_Exception $zdse) {
             } catch (Exception $zdse) {
                 // something went wrong
                 // maybe another parallel request added a new synckey
                 // we must remove data added from client
                 if (!empty($clientModifications['added'])) {
                     foreach ($clientModifications['added'] as $added) {
                         $this->_contentStateBackend->delete($added['contentState']);
                         $dataController->deleteEntry($collectionData->collectionId, $added['serverId'], array());
                     }
                 }
                 Syncroton_Registry::getTransactionManager()->rollBack();
                 throw $zdse;
             }
         }
         // store current filter type
         try {
             $folderState = $this->_folderBackend->get($collectionData->folder);
             $folderState->lastfiltertype = $collectionData->options['filterType'];
             if ($folderState->isDirty()) {
                 $this->_folderBackend->update($folderState);
             }
         } catch (Syncroton_Exception_NotFound $senf) {
             // failed to get folderstate => should not happen but is also no problem in this state
             if ($this->_logger instanceof Syncroton_Log) {
                 $this->_logger->warning(__METHOD__ . '::' . __LINE__ . ' failed to get folder state for: ' . $collectionData->collectionId);
             }
         }
     }
     if ($collections->hasChildNodes() === true) {
         $sync->appendChild($collections);
     }
     if ($sync->hasChildNodes()) {
         return $this->_outputDom;
     }
     return null;
 }
 /**
  * testUpdateEntriesIPhoneNonDefaultFolder
  *
  * for iOS devices we need to suppress attendee during sync for non default folder (see foldertype in foldersync)
  * iOS can only have one default folder
  */
 public function testUpdateEntriesIPhoneNonDefaultFolder()
 {
     // create event in folder1
     $syncrotonFolder = $this->testCreateFolder();
     $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE), Tinebase_DateTime::now());
     list($serverId, $syncrotonEvent) = $this->testCreateEntry($syncrotonFolder);
     // create new default folder2
     $syncrotonFolder2 = $this->testCreateFolder();
     Tinebase_Core::getPreference('Calendar')->setValue(Calendar_Preference::DEFAULTCALENDAR, $syncrotonFolder2->serverId);
     // load event in non default folder2
     $syncrotonEventtoUpdate = $controller->getEntry(new Syncroton_Model_SyncCollection(array('collectionId' => $syncrotonFolder->serverId)), $serverId);
     $this->assertEmpty($syncrotonEventtoUpdate->attendees, 'attendee in non default folders must be removed for ios');
     // update event in non default folder
     $syncrotonEventtoUpdate->exceptions[0]->subject = 'update';
     $syncrotonEventtoUpdate->exceptions[0]->startTime->addHour(1);
     $syncrotonEventtoUpdate->exceptions[0]->endTime->addHour(1);
     $syncTimestamp = Calendar_Controller_Event::getInstance()->get($serverId)->last_modified_time;
     $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE), $syncTimestamp);
     $serverId = $controller->updateEntry($syncrotonFolder->serverId, $serverId, $syncrotonEventtoUpdate);
     // assert update from non default folder
     $updatedSyncrotonEvent = $controller->getEntry(new Syncroton_Model_SyncCollection(array('collectionId' => $syncrotonFolder2->serverId)), $serverId);
     $this->assertEquals('update', $updatedSyncrotonEvent->exceptions[0]->subject);
     $this->assertNotEquals($syncrotonEvent->exceptions[0]->startTime->toString(), $updatedSyncrotonEvent->exceptions[0]->startTime->toString());
     $this->assertEquals($syncrotonEventtoUpdate->exceptions[0]->startTime->toString(), $updatedSyncrotonEvent->exceptions[0]->startTime->toString());
     // assert attendee are preserved
     $this->assertNotEmpty($updatedSyncrotonEvent->attendees, 'attendee must be preserved during update');
 }
 /**
  * (non-PHPdoc)
  * @see Syncroton_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 Syncroton_Log) {
                 $this->_logger->warning(__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', 'CollectionId', $collectionData['collectionId']));
             $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Estimate', 0));
         } elseif (!$collectionData['syncState'] instanceof Syncroton_Model_ISyncState) {
             if ($this->_logger instanceof Syncroton_Log) {
                 $this->_logger->warning(__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', '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', 'CollectionId', $collectionData['collectionId']));
             $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Estimate', 1));
         } else {
             $dataController = Syncroton_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', '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']) && $collectionData['folder']->isDirty()) {
             $this->_folderBackend->update($collectionData['folder']);
         }
     }
     return $this->_outputDom;
 }
    /**
     * test creation of tasks folder
     */
    public function testCreateTasksFolder()
    {
        $doc = new DOMDocument();
        $doc->loadXML('<?xml version="1.0" encoding="utf-8"?>
			<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
			<FolderCreate xmlns="uri:FolderHierarchy"><SyncKey>1</SyncKey><ParentId/><DisplayName>Test Folder</DisplayName><Type>15</Type></FolderCreate>');
        $folderCreate = new Syncroton_Command_FolderCreate($doc, $this->_device, null);
        $folderCreate->handle();
        $responseDoc = $folderCreate->getResponse();
        #$responseDoc->formatOutput = true; echo $responseDoc->saveXML();
        $xpath = new DomXPath($responseDoc);
        $xpath->registerNamespace('FolderHierarchy', 'uri:FolderHierarchy');
        $allFolders = Syncroton_Data_Factory::factory(Syncroton_Data_Factory::CLASS_TASKS, $this->_device, new DateTime('now'))->getAllFolders();
        $nodes = $xpath->query('//FolderHierarchy:FolderCreate/FolderHierarchy:ServerId');
        $this->assertEquals(1, $nodes->length, $responseDoc->saveXML());
        $this->assertFalse(empty($nodes->item(0)->nodeValue), $responseDoc->saveXML());
        $this->assertArrayHasKey($nodes->item(0)->nodeValue, $allFolders);
    }
 /**
  * test conversion to Tine 2.0 model
  */
 public function testToTineModel()
 {
     $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE), new Tinebase_DateTime(null, null, 'de_DE'));
     $syncrotonTask = new Syncroton_Model_Task(array('body' => 'a finished task', 'complete' => 1, 'dateCompleted' => new DateTime('2009-02-20T09:30:00.000Z', new DateTimeZone('UTC')), 'subject' => 'lars'));
     $tine20Task = $controller->toTineModel($syncrotonTask);
     //var_dump($tine20Task->toArray());
     $this->assertEquals('lars', $tine20Task->summary);
     $this->assertEquals('2009-02-20 09:30:00', $tine20Task->completed->format(Tinebase_Record_Abstract::ISO8601LONG));
 }
 public function testCreateUpdateGroupEvents()
 {
     $syncrotonFolder = $this->testCreateFolder();
     Tinebase_Core::getPreference('Calendar')->setValue(Calendar_Preference::DEFAULTCALENDAR, $syncrotonFolder->serverId);
     $defaultUserGroup = Tinebase_Group::getInstance()->getDefaultGroup();
     $defaultUserGroupMembers = Tinebase_Group::getInstance()->getGroupMembers($defaultUserGroup->getId());
     // create event with default user group (without explicit groupmembers)
     $xml = new SimpleXMLElement(str_replace(array('usersgroupid'), $defaultUserGroup->getId(), file_get_contents(__DIR__ . '/files/event_with_group_attendee.xml')));
     $syncrotonEvent = new Syncroton_Model_Event($xml->Collections->Collection->Commands->Change[0]->ApplicationData);
     $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE), Tinebase_DateTime::now());
     $serverId = $controller->createEntry($syncrotonFolder->serverId, $syncrotonEvent);
     // assert created group & groupmembers
     $syncrotonEventtoUpdate = $controller->getEntry(new Syncroton_Model_SyncCollection(array('collectionId' => $syncrotonFolder->serverId)), $serverId);
     $this->assertCount(count($defaultUserGroupMembers) + 1, $syncrotonEventtoUpdate->attendees, 'groupmembers not resolved: ' . print_r($syncrotonEventtoUpdate->attendees, true));
     $this->assertCount(count($defaultUserGroupMembers) + 1, $syncrotonEventtoUpdate->exceptions[0]->attendees, 'groupmembers not resolved');
     // update event
     $syncrotonEventtoUpdate->exceptions[0]->subject = 'update';
     $syncTimestamp = Calendar_Controller_Event::getInstance()->get($serverId)->last_modified_time;
     $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE), $syncTimestamp);
     $serverId = $controller->updateEntry($syncrotonFolder->serverId, $serverId, $syncrotonEventtoUpdate);
     // assert updated group & groupmembers
     $updatedSyncrotonEvent = $controller->getEntry(new Syncroton_Model_SyncCollection(array('collectionId' => $syncrotonFolder->serverId)), $serverId);
     $this->assertCount(count($defaultUserGroupMembers) + 1, $updatedSyncrotonEvent->attendees, 'groupmembers not resolved');
     $this->assertCount(count($defaultUserGroupMembers) + 1, $updatedSyncrotonEvent->exceptions[0]->attendees, 'groupmembers not resolved');
 }
    /**
     * test creation of contacts folder
     */
    public function testUpdateContactsFolder()
    {
        $doc = new DOMDocument();
        $doc->loadXML('<?xml version="1.0" encoding="utf-8"?>
			<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
			<FolderUpdate xmlns="uri:FolderHierarchy"><SyncKey>1</SyncKey><ParentId/><ServerId>anotherAddressbookFolderId</ServerId><DisplayName>Test Folder Update</DisplayName><Type>14</Type></FolderUpdate>');
        $folderUpdate = new Syncroton_Command_FolderUpdate($doc, $this->_device, null);
        $folderUpdate->handle();
        $responseDoc = $folderUpdate->getResponse();
        #$responseDoc->formatOutput = true; echo $responseDoc->saveXML();
        $xpath = new DomXPath($responseDoc);
        $xpath->registerNamespace('FolderHierarchy', 'uri:FolderHierarchy');
        $nodes = $xpath->query('//FolderHierarchy:FolderUpdate/FolderHierarchy:Status');
        $this->assertEquals(1, $nodes->length, $responseDoc->saveXML());
        $this->assertEquals(Syncroton_Command_FolderSync::STATUS_SUCCESS, $nodes->item(0)->nodeValue, $responseDoc->saveXML());
        $nodes = $xpath->query('//FolderHierarchy:FolderUpdate/FolderHierarchy:SyncKey');
        $this->assertEquals(1, $nodes->length, $responseDoc->saveXML());
        $this->assertEquals(2, $nodes->item(0)->nodeValue, $responseDoc->saveXML());
        $allFolders = Syncroton_Data_Factory::factory(Syncroton_Data_Factory::CLASS_CONTACTS, $this->_device, new DateTime('now'))->getAllFolders();
        $this->assertArrayHasKey('anotherAddressbookFolderId', $allFolders);
        $this->assertEquals('Test Folder Update', $allFolders['anotherAddressbookFolderId']->displayName);
    }
 /**
  * generate FolderSync response
  *
  * @todo changes are missing in response (folder got renamed for example)
  */
 public function getResponse()
 {
     $folderSync = $this->_outputDom->documentElement;
     // invalid synckey provided
     if (!$this->_syncState instanceof Syncroton_Model_SyncState) {
         if ($this->_logger instanceof Syncroton_Log) {
             $this->_logger->info(__METHOD__ . '::' . __LINE__ . " invalid synckey provided. FolderSync 0 needed.");
         }
         $folderSync->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Status', self::STATUS_INVALID_SYNC_KEY));
         return $this->_outputDom;
     }
     // send headers from options command also when FolderSync SyncKey is 0
     if ($this->_syncState->counter == 0) {
         $optionsCommand = new Syncroton_Command_Options();
         $this->_headers = array_merge($this->_headers, $optionsCommand->getHeaders());
     }
     $adds = array();
     $updates = array();
     $deletes = array();
     foreach ($this->_classes as $class) {
         try {
             $dataController = Syncroton_Data_Factory::factory($class, $this->_device, $this->_syncTimeStamp);
         } catch (Exception $e) {
             // backend not defined
             if ($this->_logger instanceof Syncroton_Log) {
                 $this->_logger->info(__METHOD__ . '::' . __LINE__ . " no data backend defined for class: " . $class);
             }
             continue;
         }
         try {
             // retrieve all folders available in data backend
             $serverFolders = $dataController->getAllFolders();
             // retrieve all folders sent to client
             $clientFolders = $this->_folderBackend->getFolderState($this->_device, $class);
             if ($this->_syncState->counter > 0) {
                 // retrieve all folders changed since last sync
                 $changedFolders = $dataController->getChangedFolders($this->_syncState->lastsync, $this->_syncTimeStamp);
             } else {
                 $changedFolders = array();
             }
             if ($class == 'Contacts' && sizeof($serverFolders) > 2) {
                 //debug($this->_syncState->lastsync);
                 //die;
             }
             // only folders which were sent to the client already are allowed to be in $changedFolders
             $clientFolders = $this->_folderBackend->getFolderState($this->_device, $class);
             $changedFolders = array_intersect_key($changedFolders, $clientFolders);
         } catch (Exception $e) {
             if ($this->_logger instanceof Syncroton_Log) {
                 $this->_logger->critical(__METHOD__ . '::' . __LINE__ . " Syncing folder hierarchy failed: " . $e->getMessage());
             }
             if ($this->_logger instanceof Syncroton_Log) {
                 $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " Syncing folder hierarchy failed: " . $e->getTraceAsString());
             }
             // The Status element is global for all collections. If one collection fails,
             // a failure status MUST be returned for all collections.
             if ($e instanceof Syncroton_Exception_Status) {
                 $status = $e->getCode();
             } else {
                 $status = Syncroton_Exception_Status_FolderSync::UNKNOWN_ERROR;
             }
             $folderSync->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Status', $status));
             return $this->_outputDom;
         }
         $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) {
             // have we created a folderObject in syncroton_folder before?
             if (isset($clientFolders[$serverFolderId])) {
                 $add = $clientFolders[$serverFolderId];
             } else {
                 $add = $serverFolders[$serverFolderId];
                 $add->creationTime = $this->_syncTimeStamp;
                 $add->deviceId = $this->_device;
                 unset($add->id);
             }
             $add->class = $class;
             $adds[] = $add;
         }
         // calculate changed entries
         foreach ($changedFolders as $changedFolder) {
             $change = $clientFolders[$changedFolder->serverId];
             $change->displayName = $changedFolder->displayName;
             $change->parentId = $changedFolder->parentId;
             $change->type = $changedFolder->type;
             $updates[] = $change;
         }
         // calculate deleted entries
         $serverDiff = array_diff($clientFoldersIds, $serverFoldersIds);
         foreach ($serverDiff as $serverFolderId) {
             $deletes[] = $clientFolders[$serverFolderId];
         }
     }
     $folderSync->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Status', self::STATUS_SUCCESS));
     $count = count($adds) + count($updates) + count($deletes);
     if ($count > 0) {
         $this->_syncState->counter++;
         $this->_syncState->lastsync = $this->_syncTimeStamp;
     }
     // 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'));
         $folder->appendXML($add, $this->_device);
         // 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'));
         $folder->appendXML($update, $this->_device);
         $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->serverId));
         $this->_folderBackend->delete($folder);
     }
     if (empty($this->_syncState->id)) {
         $this->_syncStateBackend->create($this->_syncState);
     } else {
         $this->_syncStateBackend->update($this->_syncState);
     }
     return $this->_outputDom;
 }
    /**
     * test empty folder
     */
    public function testEmptyFolderContents()
    {
        // do initial sync first
        $doc = new DOMDocument();
        $doc->loadXML('<?xml version="1.0" encoding="utf-8"?>
			<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
			<FolderSync xmlns="uri:ItemOperations"><SyncKey>0</SyncKey></FolderSync>');
        $folderSync = new Syncroton_Command_FolderSync($doc, $this->_device, null);
        $folderSync->handle();
        $responseDoc = $folderSync->getResponse();
        $dataController = Syncroton_Data_Factory::factory(Syncroton_Data_Factory::CLASS_EMAIL, $this->_device, new DateTime(null, new DateTimeZone('UTC')));
        $entries = $dataController->getServerEntries('emailInboxFolderId', null);
        $doc = new DOMDocument();
        $doc->loadXML('<?xml version="1.0" encoding="utf-8"?>
			<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
			<ItemOperations xmlns="uri:ItemOperations" xmlns:AirSync="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase">
				<EmptyFolderContents>
					<AirSync:CollectionId>emailInboxFolderId</AirSync:CollectionId>
					<Options><DeleteSubFolders/></Options>
				</EmptyFolderContents>
			</ItemOperations>');
        $itemOperations = new Syncroton_Command_ItemOperations($doc, $this->_device, null);
        $itemOperations->handle();
        $responseDoc = $itemOperations->getResponse();
        #$responseDoc->formatOutput = true; echo $responseDoc->saveXML();
        $xpath = new DomXPath($responseDoc);
        $xpath->registerNamespace('ItemOperations', 'uri:ItemOperations');
        $xpath->registerNamespace('Email', 'uri:Email');
        $nodes = $xpath->query('//ItemOperations:ItemOperations/ItemOperations:Status');
        $this->assertEquals(1, $nodes->length, 'ItemOperations:Status missing');
        $this->assertEquals(Syncroton_Command_ItemOperations::STATUS_SUCCESS, $nodes->item(0)->nodeValue, $responseDoc->saveXML());
        $nodes = $xpath->query('//ItemOperations:ItemOperations/ItemOperations:Response/ItemOperations:EmptyFolderContents/ItemOperations:Status');
        $this->assertEquals(1, $nodes->length, 'ItemOperations:Status missing');
        $this->assertEquals(Syncroton_Command_ItemOperations::STATUS_SUCCESS, $nodes->item(0)->nodeValue, $responseDoc->saveXML());
        $nodes = $xpath->query('//ItemOperations:ItemOperations/ItemOperations:Response/ItemOperations:EmptyFolderContents/AirSync:CollectionId');
        $this->assertEquals(1, $nodes->length, 'ItemOperations:Part missing');
        $this->assertEquals('emailInboxFolderId', $nodes->item(0)->nodeValue, 'part mismatch');
    }
 /**
  * generate ItemOperations response
  *
  * @todo add multipart support to all types of fetches
  */
 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');
     $this->_outputDom->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:Search', 'uri:Search');
     $itemOperations = $this->_outputDom->documentElement;
     $itemOperations->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Status', Syncroton_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 = Syncroton_Data_Factory::factory($fetch['store'], $this->_device, $this->_syncTimeStamp);
             if (isset($fetch['collectionId'])) {
                 $fetchTag->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Status', Syncroton_Command_ItemOperations::STATUS_SUCCESS));
                 $fetchTag->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'CollectionId', $fetch['collectionId']));
                 $fetchTag->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ServerId', $fetch['serverId']));
                 $properties = $this->_outputDom->createElementNS('uri:ItemOperations', 'Properties');
                 $dataController->getEntry(new Syncroton_Model_SyncCollection(array('collectionId' => $fetch['collectionId'], 'options' => $fetch['options'])), $fetch['serverId'])->appendXML($properties, $this->_device);
                 $fetchTag->appendChild($properties);
             } elseif (isset($fetch['longId'])) {
                 $fetchTag->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Status', Syncroton_Command_ItemOperations::STATUS_SUCCESS));
                 $fetchTag->appendChild($this->_outputDom->createElementNS('uri:Search', 'LongId', $fetch['longId']));
                 $properties = $this->_outputDom->createElementNS('uri:ItemOperations', 'Properties');
                 $dataController->getEntry(new Syncroton_Model_SyncCollection(array('collectionId' => $fetch['longId'], 'options' => $fetch['options'])), $fetch['longId'])->appendXML($properties, $this->_device);
                 $fetchTag->appendChild($properties);
             } elseif (isset($fetch['fileReference'])) {
                 $fetchTag->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Status', Syncroton_Command_ItemOperations::STATUS_SUCCESS));
                 $fetchTag->appendChild($this->_outputDom->createElementNS('uri:AirSyncBase', 'FileReference', $fetch['fileReference']));
                 $properties = $this->_outputDom->createElementNS('uri:ItemOperations', 'Properties');
                 $fileReference = $dataController->getFileReference($fetch['fileReference']);
                 // unset data field and move content to stream
                 if ($this->_requestParameters['acceptMultipart'] == true) {
                     $this->_headers['Content-Type'] = 'application/vnd.ms-sync.multipart';
                     $partStream = fopen("php://temp", 'r+');
                     if (is_resource($fileReference->data)) {
                         stream_copy_to_stream($fileReference->data, $partStream);
                     } else {
                         fwrite($partStream, $fileReference->data);
                     }
                     unset($fileReference->data);
                     $this->_parts[] = $partStream;
                     $fileReference->part = count($this->_parts);
                 }
                 $fileReference->appendXML($properties, $this->_device);
                 $fetchTag->appendChild($properties);
             }
         } catch (Syncroton_Exception_NotFound $e) {
             $response->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Status', Syncroton_Command_ItemOperations::STATUS_ITEM_FAILED_CONVERSION));
         } catch (Exception $e) {
             //echo __LINE__; echo $e->getMessage(); echo $e->getTraceAsString();
             $response->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Status', Syncroton_Command_ItemOperations::STATUS_SERVER_ERROR));
         }
     }
     foreach ($this->_emptyFolderContents as $emptyFolderContents) {
         try {
             $folder = $this->_folderBackend->getFolder($this->_device, $emptyFolderContents['collectionId']);
             $dataController = Syncroton_Data_Factory::factory($folder->class, $this->_device, $this->_syncTimeStamp);
             $dataController->emptyFolderContents($emptyFolderContents['collectionId'], $emptyFolderContents['options']);
             $status = Syncroton_Command_ItemOperations::STATUS_SUCCESS;
         } catch (Syncroton_Exception_Status_ItemOperations $e) {
             $status = $e->getCode();
         } catch (Exception $e) {
             $status = Syncroton_Exception_Status_ItemOperations::ITEM_SERVER_ERROR;
         }
         $emptyFolderContentsTag = $this->_outputDom->createElementNS('uri:ItemOperations', 'EmptyFolderContents');
         $emptyFolderContentsTag->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Status', $status));
         $emptyFolderContentsTag->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'CollectionId', $emptyFolderContents['collectionId']));
         $response->appendChild($emptyFolderContentsTag);
     }
     return $this->_outputDom;
 }
 /**
  * addnote method
  *
  * @return void
  */
 public function addnote()
 {
     App::uses('ConnectionManager', 'Model');
     Syncroton_Registry::setDatabase(ConnectionManager::getDataSource('default'));
     Syncroton_Registry::setNotesDataClass('Syncroton_Data_Notes');
     Syncroton_Registry::set('loggerBackend', new Syncroton_Log('lil_active_sync2'));
     $device = Syncroton_Registry::getDeviceBackend()->getUserDevice('miha', 'ApplC33JKLWDDTWD');
     Syncroton_Data_Factory::factory(Syncroton_Data_Factory::CLASS_NOTES, $device, new DateTime(null, new DateTimeZone('utc')))->createEntry('', new Syncroton_Model_Note(array('subject' => 'Second note')));
     return new CakeResponse(array('body' => 'success'));
 }
 /**
  * test if changed folders got returned
  * 
  * @see 0007786: changed email folder names do not sync to device
  * 
  * @todo implement
  */
 public function testGetChangedFolders()
 {
     $this->markTestIncomplete('not yet implemented in controller/felamimail');
     $syncrotonFolder = $this->testUpdateFolder();
     $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE), new Tinebase_DateTime(null, null, 'de_DE'));
     $changedFolders = $controller->getChangedFolders(Tinebase_DateTime::now()->subMinute(1), Tinebase_DateTime::now());
     //var_dump($changedFolders);
     $this->assertEquals(1, count($changedFolders));
     $this->assertArrayHasKey($syncrotonFolder->serverId, $changedFolders);
 }
 public function testDeleteEntry()
 {
     try {
         $device = Syncroton_Registry::getDeviceBackend()->getUserDevice('1234', 'iphone-abcd');
         Syncroton_Registry::getDeviceBackend()->delete($device);
     } catch (Syncroton_Exception_NotFound $e) {
         // do nothing => it's ok
     }
     require_once dirname(dirname(__FILE__)) . DS . 'Backend' . DS . 'DeviceTest.php';
     $device = Syncroton_Registry::getDeviceBackend()->create(DeviceTest::getTestDevice(Syncroton_Model_Device::TYPE_IPHONE));
     $dataController = Syncroton_Data_Factory::factory(Syncroton_Data_Factory::CLASS_CONTACTS, $device, new DateTime(null, new DateTimeZone('UTC')));
     $entries = $dataController->getServerEntries('addressbookFolderId', null);
     $dataController->deleteEntry('addressbookFolderId', $entries[0], array());
     $newEntries = $dataController->getServerEntries('addressbookFolderId', null);
     $this->assertArrayNotHasKey($entries[0], $newEntries);
 }
    /**
     * test deleting existing folder
     */
    public function testDeleteExsistingFolder()
    {
        // delete folder created above
        $doc = new DOMDocument();
        $doc->loadXML('<?xml version="1.0" encoding="utf-8"?>
			<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
			<FolderDelete xmlns="uri:FolderHierarchy">
				<SyncKey>2</SyncKey>
				<ServerId>' . $this->_testFolderId . '</ServerId>
			</FolderDelete>');
        $folderDelete = new Syncroton_Command_FolderDelete($doc, $this->_device, null);
        $folderDelete->handle();
        $responseDoc = $folderDelete->getResponse();
        #$responseDoc->formatOutput = true; echo $responseDoc->saveXML();
        $xpath = new DomXPath($responseDoc);
        $xpath->registerNamespace('FolderHierarchy', 'uri:FolderHierarchy');
        $nodes = $xpath->query('//FolderHierarchy:FolderDelete/FolderHierarchy:Status');
        $this->assertEquals(1, $nodes->length, $responseDoc->saveXML());
        $this->assertEquals(Syncroton_Command_FolderSync::STATUS_SUCCESS, $nodes->item(0)->nodeValue, $responseDoc->saveXML());
        $nodes = $xpath->query('//FolderHierarchy:FolderDelete/FolderHierarchy:SyncKey');
        $this->assertEquals(1, $nodes->length, $responseDoc->saveXML());
        $this->assertEquals(3, $nodes->item(0)->nodeValue, $responseDoc->saveXML());
        $allFolders = Syncroton_Data_Factory::factory(Syncroton_Data_Factory::CLASS_CALENDAR, $this->_device, new DateTime('now'))->getAllFolders();
        $this->assertArrayNotHasKey($this->_testFolderId, $allFolders);
    }
 /**
  * get application activesync controller
  * 
  * @param ActiveSync_Model_Device $_device
  */
 protected function _getController(ActiveSync_Model_Device $_device)
 {
     if ($this->_controller === null) {
         $this->_controller = Syncroton_Data_Factory::factory($this->_class, $_device, new Tinebase_DateTime(null, null, 'de_DE'));
     }
     return $this->_controller;
 }
 public function testUpdateEntriesIPhone()
 {
     $syncrotonFolder = $this->testCreateFolder();
     $syncrotonFolder2 = $this->testCreateFolder();
     //make $syncrotonFolder2 the default
     Tinebase_Core::getPreference('Calendar')->setValue(Calendar_Preference::DEFAULTCALENDAR, $syncrotonFolder2->serverId);
     $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE), Tinebase_DateTime::now());
     list($serverId, $syncrotonEvent) = $this->testCreateEntry($syncrotonFolder);
     $event = Calendar_Controller_Event::getInstance()->get($serverId);
     unset($syncrotonEvent->recurrence);
     unset($syncrotonEvent->exceptions);
     unset($syncrotonEvent->attendees);
     // need to create new controller to set new sync timestamp for concurrency handling
     $syncTimestamp = Calendar_Controller_Event::getInstance()->get($serverId)->last_modified_time;
     $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE), $syncTimestamp);
     $serverId = $controller->updateEntry($syncrotonFolder->serverId, $serverId, $syncrotonEvent);
     $syncrotonEvent = $controller->getEntry(new Syncroton_Model_SyncCollection(array('collectionId' => $syncrotonFolder->serverId)), $serverId);
     $updatedEvent = Calendar_Controller_Event::getInstance()->get($serverId);
     $this->assertEmpty($syncrotonEvent->attendees, 'Events attendees found in folder which is not the default calendar');
     $this->assertNotEmpty($updatedEvent->attendee, 'attendee must be preserved');
     $this->assertEquals($event->attendee[0]->getId(), $updatedEvent->attendee[0]->getId(), 'attendee must be perserved');
     return;
     list($serverId, $syncrotonEvent) = $this->testCreateEntry($syncrotonFolder2);
     unset($syncrotonEvent->recurrence);
     unset($syncrotonEvent->exceptions);
     $syncrotonEvent->attendees[0]->name = Tinebase_Record_Abstract::generateUID();
     // need to create new controller to set new sync timestamp for concurrency handling
     $syncTimestamp = Calendar_Controller_Event::getInstance()->get($serverId)->last_modified_time;
     $controller = Syncroton_Data_Factory::factory($this->_class, $this->_getDevice(Syncroton_Model_Device::TYPE_IPHONE), $syncTimestamp);
     $serverId = $controller->updateEntry($syncrotonFolder2->serverId, $serverId, $syncrotonEvent);
     $syncrotonEvent = $controller->getEntry(new Syncroton_Model_SyncCollection(array('collectionId' => $syncrotonFolder2->serverId)), $serverId);
     $this->assertNotEmpty($syncrotonEvent->attendees, 'Events attendees not found in default calendar');
 }
Exemple #27
0
    /**
     * test that the last filterType got updated, even no changed entries were found
     */
    public function testUpdateOfLastFilterType()
    {
        $this->testSyncOfContacts();
        $dataController = Syncroton_Data_Factory::factory(Syncroton_Data_Factory::CLASS_CONTACTS, $this->_device, new DateTime(null, new DateTimeZone('UTC')));
        $entries = $dataController->getServerEntries('addressbookFolderId', null);
        // lets add one contact
        $doc = new DOMDocument();
        $doc->loadXML('<?xml version="1.0" encoding="utf-8"?>
			<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
			<Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase"><Collections>
				<Collection>
					<Class>Contacts</Class>
					<SyncKey>4</SyncKey>
					<CollectionId>addressbookFolderId</CollectionId>
					<DeletesAsMoves/>
					<GetChanges/>
					<WindowSize>100</WindowSize>
					<Options>
						<FilterType>2</FilterType>
						<AirSyncBase:BodyPreference>
							<AirSyncBase:Type>1</AirSyncBase:Type>
							<AirSyncBase:TruncationSize>5120</AirSyncBase:TruncationSize>
						</AirSyncBase:BodyPreference>
						<Conflict>1</Conflict>
					</Options>
				</Collection>
			</Collections></Sync>');
        $sync = new Syncroton_Command_Sync($doc, $this->_device, $this->_device->policykey);
        $sync->handle();
        $syncDoc = $sync->getResponse();
        $folder = Syncroton_Registry::getFolderBackend()->getFolder($this->_device, 'addressbookFolderId');
        $this->assertEquals(2, $folder->lastfiltertype);
    }
Exemple #28
0
    /**
     *
     */
    public function testPingContacts()
    {
        // first do a foldersync
        $doc = new DOMDocument();
        $doc->loadXML('<?xml version="1.0" encoding="utf-8"?>
			<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
			<FolderSync xmlns="uri:FolderHierarchy"><SyncKey>0</SyncKey></FolderSync>');
        $folderSync = new Syncroton_Command_FolderSync($doc, $this->_device, $this->_device->policykey);
        $folderSync->handle();
        $folderSync->getResponse();
        // request initial synckey
        $doc = new DOMDocument();
        $doc->loadXML('<?xml version="1.0" encoding="utf-8"?>
			<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
			<Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase"><Collections><Collection><Class>Contacts</Class><SyncKey>0</SyncKey><CollectionId>addressbookFolderId</CollectionId><DeletesAsMoves/><GetChanges/><WindowSize>100</WindowSize><Options><AirSyncBase:BodyPreference><AirSyncBase:Type>1</AirSyncBase:Type><AirSyncBase:TruncationSize>5120</AirSyncBase:TruncationSize></AirSyncBase:BodyPreference><Conflict>1</Conflict></Options></Collection></Collections></Sync>');
        $sync = new Syncroton_Command_Sync($doc, $this->_device, $this->_device->policykey);
        $sync->handle();
        $syncDoc = $sync->getResponse();
        #$syncDoc->formatOutput = true; echo $syncDoc->saveXML();
        // now do the first sync
        $doc = new DOMDocument();
        $doc->loadXML('<?xml version="1.0" encoding="utf-8"?>
			<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
			<Sync xmlns="uri:AirSync" xmlns:AirSyncBase="uri:AirSyncBase"><Collections><Collection><Class>Contacts</Class><SyncKey>1</SyncKey><CollectionId>addressbookFolderId</CollectionId><DeletesAsMoves/><GetChanges/><WindowSize>100</WindowSize><Options><AirSyncBase:BodyPreference><AirSyncBase:Type>1</AirSyncBase:Type><AirSyncBase:TruncationSize>5120</AirSyncBase:TruncationSize></AirSyncBase:BodyPreference><Conflict>1</Conflict></Options></Collection></Collections></Sync>');
        $sync = new Syncroton_Command_Sync($doc, $this->_device, $this->_device->policykey);
        $sync->handle();
        $syncDoc = $sync->getResponse();
        #$syncDoc->formatOutput = true; echo $syncDoc->saveXML();
        $folder = Syncroton_Registry::getFolderBackend()->getFolder($this->_device, 'addressbookFolderId');
        $oneSecondAgo = new DateTime(null, new DateTimeZone('utc'));
        $oneSecondAgo->modify('-1 second');
        $tenSecondsAgo = new DateTime(null, new DateTimeZone('utc'));
        $tenSecondsAgo->modify('-10 second');
        // update modify timeStamp of contact
        $dataController = Syncroton_Data_Factory::factory(Syncroton_Data_Factory::CLASS_CONTACTS, $this->_device, $oneSecondAgo);
        $contact = $dataController->getEntry(new Syncroton_Model_SyncCollection(array('folder' => 'addressbookFolderId')), 'contact1');
        $dataController->updateEntry('addressbookFolderId', 'contact1', $contact);
        // turn back last sync time
        $syncState = Syncroton_Registry::getSyncStateBackend()->getSyncState($this->_device, $folder);
        $syncState->lastsync = $tenSecondsAgo;
        $syncState = Syncroton_Registry::getSyncStateBackend()->update($syncState);
        // and now we can start the ping request
        $doc = new DOMDocument();
        $doc->loadXML('<?xml version="1.0" encoding="utf-8"?>
			<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
			<Ping xmlns="uri:Ping"><HeartBeatInterval>10</HeartBeatInterval><Folders><Folder><Id>addressbookFolderId</Id><Class>Contacts</Class></Folder></Folders></Ping>');
        $search = new Syncroton_Command_Ping($doc, $this->_device, null);
        $search->handle();
        $responseDoc = $search->getResponse();
        #$responseDoc->formatOutput = true; echo $responseDoc->saveXML();
        $xpath = new DomXPath($responseDoc);
        $xpath->registerNamespace('Ping', 'uri:Ping');
        $nodes = $xpath->query('//Ping:Ping/Ping:Status');
        $this->assertEquals(1, $nodes->length, $responseDoc->saveXML());
        $this->assertEquals(Syncroton_Command_Ping::STATUS_CHANGES_FOUND, $nodes->item(0)->nodeValue, $responseDoc->saveXML());
        $nodes = $xpath->query('//Ping:Ping/Ping:Folders/Ping:Folder');
        $this->assertEquals(1, $nodes->length, $responseDoc->saveXML());
        $this->assertEquals('addressbookFolderId', $nodes->item(0)->nodeValue, $responseDoc->saveXML());
    }
    /**
     * test reponse with synckey 1
     */
    public function testGetFoldersSyncKey1()
    {
        $this->testGetFoldersSyncKey0();
        // rewind back last sync timestamp
        $syncState = Syncroton_Registry::getSyncStateBackend()->getSyncState($this->_device, 'FolderSync');
        $syncState->lastsync = new DateTime('2013-02-09 10:40:36', new DateTimeZone('utc'));
        $syncState = Syncroton_Registry::getSyncStateBackend()->update($syncState);
        $createdFolder = Syncroton_Data_Factory::factory(Syncroton_Data_Factory::CLASS_CONTACTS, $this->_device, new DateTime(null, new DateTimeZone('utc')))->createFolder(new Syncroton_Model_Folder(array('serverId' => 'addressbookFolderId2', 'parentId' => null, 'displayName' => 'User created Contacts Folder', 'type' => Syncroton_Command_FolderSync::FOLDERTYPE_CONTACT_USER_CREATED)));
        // update created folder to validate that created folders are not returned as changes folders
        Syncroton_Data_Factory::factory(Syncroton_Data_Factory::CLASS_CONTACTS, $this->_device, new DateTime(null, new DateTimeZone('utc')))->updateFolder($createdFolder);
        Syncroton_Data_Factory::factory(Syncroton_Data_Factory::CLASS_CONTACTS, $this->_device, new DateTime(null, new DateTimeZone('utc')))->updateFolder(new Syncroton_Model_Folder(array('serverId' => 'anotherAddressbookFolderId', 'parentId' => null, 'displayName' => 'User updated Contacts Folder', 'type' => Syncroton_Command_FolderSync::FOLDERTYPE_CONTACT_USER_CREATED)));
        $doc = new DOMDocument();
        $doc->loadXML('<?xml version="1.0" encoding="utf-8"?' . '>
			<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
			<FolderSync xmlns="uri:FolderHierarchy"><SyncKey>1</SyncKey></FolderSync>');
        $folderSync = new Syncroton_Command_FolderSync($doc, $this->_device, $this->_device->policykey);
        $folderSync->handle();
        $responseDoc = $folderSync->getResponse();
        //$responseDoc->formatOutput = true; echo '<pre>'.htmlspecialchars($responseDoc->saveXML()).'</pre>'; die;
        $xpath = new DomXPath($responseDoc);
        $xpath->registerNamespace('FolderHierarchy', 'uri:FolderHierarchy');
        $nodes = $xpath->query('//FolderHierarchy:FolderSync/FolderHierarchy:Status');
        $this->assertEquals(1, $nodes->length, $responseDoc->saveXML());
        $this->assertEquals(Syncroton_Command_FolderSync::STATUS_SUCCESS, $nodes->item(0)->nodeValue, $responseDoc->saveXML());
        $nodes = $xpath->query('//FolderHierarchy:FolderSync/FolderHierarchy:SyncKey');
        $this->assertEquals(1, $nodes->length, $responseDoc->saveXML());
        $this->assertEquals(2, $nodes->item(0)->nodeValue, $responseDoc->saveXML());
        $nodes = $xpath->query('//FolderHierarchy:FolderSync/FolderHierarchy:Changes/FolderHierarchy:Add');
        $this->assertGreaterThanOrEqual(1, $nodes->length, $responseDoc->saveXML());
        $nodes = $xpath->query('//FolderHierarchy:FolderSync/FolderHierarchy:Changes/FolderHierarchy:Add/FolderHierarchy:DisplayName');
        $this->assertGreaterThanOrEqual('User created Contacts Folder', $nodes->item(0)->nodeValue, $responseDoc->saveXML());
        $nodes = $xpath->query('//FolderHierarchy:FolderSync/FolderHierarchy:Changes/FolderHierarchy:Update');
        $this->assertGreaterThanOrEqual(1, $nodes->length, $responseDoc->saveXML());
        $nodes = $xpath->query('//FolderHierarchy:FolderSync/FolderHierarchy:Changes/FolderHierarchy:Update/FolderHierarchy:DisplayName');
        $this->assertGreaterThanOrEqual('User updated Contacts Folder', $nodes->item(0)->nodeValue, $responseDoc->saveXML());
    }