/** * 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; }
/** * 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); } }
/** * 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); } } } }
/** * (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'); }
/** * 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); }
/** * */ 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()); }