/** * generate MoveItems response */ public function getResponse() { $folderStateBackend = new ActiveSync_Backend_FolderState(); $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 { $folder = $folderStateBackend->getByProperty($move['srcFldId'], 'folderid'); $dataController = ActiveSync_Controller::dataFactory($folder->class, $this->_device, $this->_syncTimeStamp); $newId = $dataController->moveItem($move['srcFldId'], $move['srcMsgId'], $move['dstFldId']); $response->appendChild($this->_outputDom->createElementNS('uri:Move', 'Status', ActiveSync_Command_MoveItems::STATUS_SUCCESS)); $response->appendChild($this->_outputDom->createElementNS('uri:Move', 'DstMsgId', $newId)); } catch (Tinebase_Exception_NotFound $e) { $response->appendChild($this->_outputDom->createElementNS('uri:Move', 'Status', ActiveSync_Command_MoveItems::STATUS_INVALID_SOURCE)); } catch (Exception $e) { $response->appendChild($this->_outputDom->createElementNS('uri:Move', 'Status', ActiveSync_Command_MoveItems::STATUS_INVALID_DESTINATION)); } } return $this->_outputDom; }
/** * generate FolderSync response * * @todo currently we support only the main folder which contains all contacts/tasks/events/notes per class * * @param boolean $_keepSession keep session active(don't logout user) when true */ public function getResponse($_keepSession = FALSE) { $folderSync = $this->_outputDom->documentElement; if ($this->_syncKey > '0' && $this->_controller->validateSyncKey($this->_device, $this->_syncKey, 'FolderSync') === false) { Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " INVALID synckey provided"); $folderSync->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Status', self::STATUS_INVALID_SYNC_KEY)); } else { $adds = array(); $deletes = array(); $count = 0; if ($this->_syncKey == 0) { $this->_folderStateBackend->resetState($this->_device); } foreach ($this->_classes as $class) { $dataController = ActiveSync_Controller::dataFactory($class, $this->_device, $this->_syncTimeStamp); try { $folders = $dataController->getSupportedFolders(); } catch (Exception $e) { if (Tinebase_Core::isLogLevel(Zend_Log::CRIT)) { Tinebase_Core::getLogger()->crit(__METHOD__ . '::' . __LINE__ . " failed to get folders for class {$class}. " . $e->getMessage()); } if (Tinebase_Core::isLogLevel(Zend_Log::CRIT)) { Tinebase_Core::getLogger()->crit(__METHOD__ . '::' . __LINE__ . " failed to get folders for class {$class}. " . $e->getTraceAsString()); } continue; } if ($this->_syncKey == 0) { foreach ($folders as $folderId => $folder) { $adds[$class][$folderId] = $folder; $count++; } } else { $allServerEntries = array_keys($folders); $allClientEntries = $this->_folderStateBackend->getClientState($this->_device, $class); // added entries $serverDiff = array_diff($allServerEntries, $allClientEntries); foreach ($serverDiff as $folderId) { if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) { Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " add {$class} {$folderId}"); } $adds[$class][$folderId] = $folders[$folderId]; $count++; } // deleted entries $serverDiff = array_diff($allClientEntries, $allServerEntries); foreach ($serverDiff as $folderId) { if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) { Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " delete {$class} {$folderId}"); } $deletes[$class][$folderId] = $folderId; $count++; } } } if ($count > 0) { $newSyncKey = $this->_syncKey + 1; } else { $newSyncKey = $this->_syncKey; } // create xml output $folderSync->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Status', self::STATUS_SUCCESS)); $folderSync->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'SyncKey', $newSyncKey)); $changes = $folderSync->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Changes')); $changes->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Count', $count)); foreach ($adds as $class => $folders) { foreach ((array) $folders as $folder) { $add = $changes->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Add')); $add->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'ServerId', $folder['folderId'])); $add->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'ParentId', $folder['parentId'])); $add->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'DisplayName', $folder['displayName'])); $add->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Type', $folder['type'])); $this->_addFolderState($class, $folder['folderId']); if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) { Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " {$class} => " . $folder['folderId']); } } } foreach ($deletes as $class => $folders) { if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) { Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " {$class}"); } foreach ((array) $folders as $folderId) { $delete = $changes->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Delete')); $delete->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'ServerId', $folderId)); $this->_deleteFolderState($class, $folderId); } } $this->_controller->updateSyncKey($this->_device, $newSyncKey, $this->_syncTimeStamp, 'FolderSync'); } return $this->_outputDom; }
/** * generate ItemOperations response */ public function getResponse() { // add aditional namespaces for contacts, tasks and email $this->_outputDom->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:Contacts', 'uri:Contacts'); $this->_outputDom->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:Tasks', 'uri:Tasks'); $this->_outputDom->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:Email', 'uri:Email'); $this->_outputDom->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:Calendar', 'uri:Calendar'); $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'); $folderStateBackend = new ActiveSync_Backend_FolderState(); $itemOperations = $this->_outputDom->documentElement; $itemOperations->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Status', ActiveSync_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 = ActiveSync_Controller::dataFactory($fetch['store'], $this->_device, $this->_syncTimeStamp); if (isset($fetch['collectionId'])) { $properties = $this->_outputDom->createElementNS('uri:ItemOperations', 'Properties'); $dataController->appendXML($properties, $fetch['collectionId'], $fetch['serverId'], $fetch, true); $fetchTag->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Status', ActiveSync_Command_ItemOperations::STATUS_SUCCESS)); $fetchTag->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'CollectionId', $fetch['collectionId'])); $fetchTag->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ServerId', $fetch['serverId'])); $fetchTag->appendChild($properties); } elseif (isset($fetch['fileReference'])) { $properties = $this->_outputDom->createElementNS('uri:ItemOperations', 'Properties'); $dataController->appendFileReference($properties, $fetch['fileReference']); $fetchTag->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Status', ActiveSync_Command_ItemOperations::STATUS_SUCCESS)); $fetchTag->appendChild($this->_outputDom->createElementNS('uri:AirSyncBase', 'FileReference', $fetch['fileReference'])); $fetchTag->appendChild($properties); } } catch (Tinebase_Exception_NotFound $e) { $response->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Status', ActiveSync_Command_ItemOperations::STATUS_ITEM_FAILED_CONVERSION)); } catch (Exception $e) { $response->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Status', ActiveSync_Command_ItemOperations::STATUS_SERVER_ERROR)); } } return $this->_outputDom; }
/** * 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() { $controller = ActiveSync_Controller::getInstance(); $intervalStart = time(); $status = self::STATUS_NO_CHANGES_FOUND; // the client does not send a wbxml document, if the Ping parameters did not change compared with the last request if ($this->_inputDom instanceof DOMDocument) { #$xml = simplexml_load_string($this->_inputDom->saveXML()); $xml = new SimpleXMLElement($this->_inputDom->saveXML(), LIBXML_NOWARNING); $xml->registerXPathNamespace('Ping', 'Ping'); if (isset($xml->HeartBeatInterval)) { $this->_device->pinglifetime = $xml->HeartBeatInterval; } if (isset($xml->Folders->Folder)) { foreach ($xml->Folders->Folder as $folderXml) { #Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " folderType: " . print_r($folderXml, true)); #$folderBackend = $this->_backend->factory((string)$folderXml->Class); try { // does the folder exist? # $folderBackend->getFolder($folderXml->Id); $folder = array('serverEntryId' => (string) $folderXml->Id, 'folderType' => (string) $folderXml->Class); $folders[] = $folder; } catch (Exception $e) { if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) { Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " " . $e->getMessage()); } $status = self::STATUS_FOLDER_NOT_FOUND; break; } } $this->_device->pingfolder = serialize($folders); } $this->_device = $controller->updateDevice($this->_device); } $lifeTime = $this->_device->pinglifetime; Tinebase_Core::setExecutionLifeTime($lifeTime); $intervalEnd = $intervalStart + $lifeTime; $secondsLeft = $intervalEnd; $folders = unserialize($this->_device->pingfolder); if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) { Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " Folders to monitor({$lifeTime} / {$intervalStart} / {$intervalEnd} / {$status}): " . print_r($folders, true)); } if ($status === self::STATUS_NO_CHANGES_FOUND) { $folderWithChanges = array(); do { foreach ((array) $folders as $folder) { $dataController = ActiveSync_Controller::dataFactory($folder['folderType'], $this->_device, $this->_syncTimeStamp); #if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " " . print_r($folder, true)); try { $syncState = $controller->getSyncState($this->_device, $folder['folderType'], $folder['serverEntryId']); $count = $this->_getItemEstimate($dataController, $folder, $syncState->lastsync); } catch (ActiveSync_Exception_SyncStateNotFound $e) { // folder got never synchronized to client if (Tinebase_Core::isLogLevel(Zend_Log::TRACE)) { Tinebase_Core::getLogger()->trace(__METHOD__ . '::' . __LINE__ . " " . $e->getMessage()); } if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) { Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' syncstate not found. enforce sync for folder: ' . $folder['serverEntryId']); } $count = 1; } if ($count > 0) { $folderWithChanges[] = array('serverEntryId' => $folder['serverEntryId'], 'folderType' => $folder['folderType']); $status = self::STATUS_CHANGES_FOUND; } } if ($status === self::STATUS_CHANGES_FOUND) { break; } // another process synchronized data already if (isset($syncState) && $syncState->lastsync > $this->_syncTimeStamp) { Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " terminate ping process. Some other process updated data already."); break; } sleep(self::PING_TIMEOUT); $secondsLeft = $intervalEnd - time(); //if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " DeviceId: " . $this->_device->deviceid . " seconds left: " . $secondsLeft); } while ($secondsLeft > 0); } Tinebase_Core::getLogger()->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 ($folderWithChanges as $changedFolder) { $folder = $folders->appendChild($this->_outputDom->createElementNS('uri:Ping', 'Folder', $changedFolder['serverEntryId'])); #$folder->appendChild($this->_outputDom->createElementNS('uri:Ping', 'Id', $changedFolder['serverEntryId'])); Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " DeviceId: " . $this->_device->deviceid . " changes in folder: " . $changedFolder['serverEntryId']); } } }
/** * (non-PHPdoc) * @see ActiveSync_Command_Wbxml::getResponse() */ public function getResponse() { // add aditional namespaces for tasks and email $this->_outputDom->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:Tasks', 'uri:Tasks'); $this->_outputDom->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:Email', 'uri:Email'); $this->_outputDom->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:AirSyncBase', 'uri:AirSyncBase'); $sync = $this->_outputDom->documentElement; $collections = $sync->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Collections')); foreach ($this->_collections as $class => $classCollections) { foreach ($classCollections as $collectionId => $collectionData) { if ($class == 'collectionNotFound') { $collection = $collections->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Collection')); $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Class', $collectionData['class'])); $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)); } elseif ($collectionData['syncKeyValid'] !== true) { $collectionData['syncState']->counter = 0; // set synckey to 0 $collection = $collections->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Collection')); $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Class', $collectionData['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_INVALID_SYNC_KEY)); $this->_contentStateBackend->resetState($this->_device, $collectionData['class'], $collectionData['collectionId']); $this->_controller->resetSyncState($collectionData['syncState']); } 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')); $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Class', $collectionData['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)); $this->_contentStateBackend->resetState($this->_device, $collectionData['class'], $collectionData['collectionId']); $this->_controller->resetSyncState($collectionData['syncState']); } else { if (empty($collectionData['added']) && empty($collectionData['changed']) && empty($collectionData['deleted']) && $collectionData['getChanges'] === false) { // keep synckey during fetch requests } else { $collectionData['syncState']->counter++; } // collection header $collection = $collections->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Collection')); $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Class', $collectionData['class'])); $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'SyncKey', $collectionData['syncState']->counter)); $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'CollectionId', $collectionData['collectionId'])); $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_SUCCESS)); $responses = NULL; // sent reponse for newly added entries if (!empty($collectionData['added'])) { if ($responses === NULL) { $responses = $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Responses')); } foreach ($collectionData['added'] as $clientId => $entryData) { $add = $responses->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Add')); $add->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ClientId', $clientId)); if (isset($entryData['serverId'])) { $add->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ServerId', $entryData['serverId'])); } $add->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', $entryData['status'])); } } // sent reponse for changed entries // not really needed if (!empty($collectionData['changed'])) { if ($responses === NULL) { $responses = $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Responses')); } foreach ($collectionData['changed'] as $serverId => $status) { $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)); } } $dataController = ActiveSync_Controller::dataFactory($collectionData['class'], $this->_device, $this->_syncTimeStamp); // sent response for to be fetched entries if (!empty($collectionData['toBeFetched'])) { if ($responses === NULL) { $responses = $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Responses')); } 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'); // don't limit size during fetch $collectionFetchData = $collectionData; unset($collectionFetchData['bodyPreferences'][1]['truncationSize']); unset($collectionFetchData['bodyPreferences'][2]['truncationSize']); unset($collectionFetchData['bodyPreferences'][4]['truncationSize']); unset($collectionFetchData['mimeTruncation']); $dataController->appendXML($applicationData, $collectionData['collectionId'], $serverId, $collectionData); $fetch->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_SUCCESS)); $fetch->appendChild($applicationData); } catch (Exception $e) { if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) { Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " unable to convert entry to xml: " . $e->getMessage()); } $fetch->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_OBJECT_NOT_FOUND)); } } } if ($collectionData['getChanges'] === true) { if ($collectionData['syncState']->counter === 1) { // all entries available $serverAdds = $dataController->getServerEntries($collectionData['collectionId'], $collectionData['filterType']); $serverChanges = array(); $serverDeletes = array(); } else { // continue sync session if (is_array($collectionData['syncState']->pendingdata)) { if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) { Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " restored from sync state "); } $serverAdds = $collectionData['syncState']->pendingdata['serverAdds']; $serverChanges = $collectionData['syncState']->pendingdata['serverChanges']; $serverDeletes = $collectionData['syncState']->pendingdata['serverDeletes']; } else { // fetch entries added since last sync $allClientEntries = $this->_contentStateBackend->getClientState($this->_device, $collectionData['class'], $collectionData['collectionId']); $allServerEntries = $dataController->getServerEntries($collectionData['collectionId'], $collectionData['filterType']); // add entries $serverDiff = array_diff($allServerEntries, $allClientEntries); // add entries which produced problems during delete from client $serverAdds = $this->_collections[$class][$collectionId]['forceAdd']; // add entries not yet sent to client $serverAdds = array_unique(array_merge($serverAdds, $serverDiff)); foreach ($serverAdds as $id => $serverId) { // skip entries added by client during this sync session // @todo $this->_collections[$class][$collectionId] should be equal to $collectionData ??? if (isset($collectionData['added'][$serverId]) && !isset($this->_collections[$class][$collectionId]['forceAdd'][$serverId])) { if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) { Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " skipped added entry: " . $serverId); } unset($serverAdds[$id]); } } // entries to be deleted $serverDeletes = array_diff($allClientEntries, $allServerEntries); // fetch entries changed since last sync $serverChanges = $dataController->getChanged($collectionData['collectionId'], $collectionData['syncState']->lastsync, $this->_syncTimeStamp); $serverChanges = array_merge($serverChanges, $this->_collections[$class][$collectionId]['forceChange']); foreach ($serverChanges as $id => $serverId) { // skip entry, if it got changed by client during current sync // @todo $this->_collections[$class][$collectionId] should be equal to $collectionData ??? if (isset($collectionData['changed'][$serverId]) && !isset($this->_collections[$class][$collectionId]['forceChange'][$serverId])) { if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) { Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " skipped changed entry: " . $serverId); } unset($serverChanges[$id]); } } // entries comeing in scope are already in $serverAdds and do not need to // be send with $serverCanges $serverChanges = array_diff($serverChanges, $serverAdds); } } if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) { Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " found (added/changed/deleted) " . count($serverAdds) . '/' . count($serverChanges) . '/' . count($serverDeletes) . ' entries for sync from server to client'); } if (count($serverAdds) + count($serverChanges) + count($serverDeletes) > $collectionData['windowSize']) { $this->_moreAvailable = true; $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'MoreAvailable')); } if (count($serverAdds) > 0 || count($serverChanges) > 0 || count($serverDeletes) > 0) { $commands = $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Commands')); } /** * process added entries */ // fetch estimated entries in one batch $ids = array_slice($serverAdds, 0, abs($collectionData['windowSize'] - $this->_totalCount), TRUE); $serverEntries = $dataController->getMultiple($ids); foreach ($serverAdds as $id => $serverId) { if ($this->_totalCount === $collectionData['windowSize']) { break; } try { #$add = $commands->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Add')); $add = $this->_outputDom->createElementNS('uri:AirSync', 'Add'); $entriesIdx = $serverEntries->getIndexById($serverId); $serverEntriy = $entriesIdx !== FALSE ? $serverEntries[$entriesIdx] : $serverId; $add->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ServerId', $serverId)); $applicationData = $add->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ApplicationData')); $dataController->appendXML($applicationData, $collectionData['collectionId'], $serverEntriy, $collectionData); $commands->appendChild($add); #$this->_addContentState($collectionData['class'], $collectionData['collectionId'], $serverId); $this->_totalCount++; } catch (Exception $e) { if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) { Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " unable to convert entry to xml: " . $e->getMessage()); } } // mark as send to the client, even the conversion to xml might have failed $this->_addContentState($collectionData['class'], $collectionData['collectionId'], $serverId); if ($serverEntriy instanceof Tinebase_Record_Abstract) { $serverEntries->removeRecord($serverEntriy); } unset($serverAdds[$id]); } /** * process changed entries */ foreach ($serverChanges as $id => $serverId) { if ($this->_totalCount === $collectionData['windowSize']) { break; } try { $change = $this->_outputDom->createElementNS('uri:AirSync', 'Change'); $entriesIdx = $serverEntries->getIndexById($serverId); $serverEntriy = $entriesIdx !== FALSE ? $serverEntries[$entriesIdx] : $serverId; $change->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ServerId', $serverId)); $applicationData = $change->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ApplicationData')); $dataController->appendXML($applicationData, $collectionData['collectionId'], $serverEntriy, $collectionData); $commands->appendChild($change); $this->_totalCount++; } catch (Exception $e) { if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) { Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " unable to convert entry to xml: " . $e->getMessage()); } } if ($serverEntriy instanceof Tinebase_Record_Abstract) { $serverEntries->removeRecord($serverEntriy); } unset($serverChanges[$id]); } /** * process deleted entries */ foreach ($serverDeletes as $id => $serverId) { if ($this->_totalCount === $collectionData['windowSize']) { break; } try { #$delete = $commands->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Delete')); $delete = $this->_outputDom->createElementNS('uri:AirSync', 'Delete'); $delete->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ServerId', $serverId)); $this->_markContentStateAsDeleted($collectionData['class'], $collectionData['collectionId'], $serverId); $commands->appendChild($delete); $this->_totalCount++; } catch (Exception $e) { if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) { Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " unable to convert entry to xml: " . $e->getMessage()); } } unset($serverDeletes[$id]); } } if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) { Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " new synckey is " . $collectionData['syncState']->counter); } } if ($class != 'collectionNotFound') { // save data to sync state if more data available if ($this->_moreAvailable === true) { $collectionData['syncState']->pendingdata = array('serverAdds' => (array) $serverAdds, 'serverChanges' => (array) $serverChanges, 'serverDeletes' => (array) $serverDeletes); } else { $collectionData['syncState']->pendingdata = null; } $keepPreviousSyncKey = true; // increment sync timestamp by 1 second $this->_syncTimeStamp->add('1', Tinebase_DateTime::MODIFIER_SECOND); if (!empty($collectionData['added'])) { if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) { Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " remove previous synckey as client added new entries"); } $keepPreviousSyncKey = false; } $collectionData['syncState']->lastsync = $this->_syncTimeStamp; $this->_controller->updateSyncState($collectionData['syncState'], $keepPreviousSyncKey); // store current filter type try { $folderState = $this->getFolderState($this->_device, $collectionData['collectionId']); $folderState->lastfiltertype = $collectionData['filterType']; $this->_folderStateBackend->update($folderState); } catch (Tinebase_Exception_NotFound $tenf) { // failed to get folderstate => should not happen but is also no problem in this state if (Tinebase_Core::isLogLevel(Zend_Log::CRIT)) { Tinebase_Core::getLogger()->crit(__METHOD__ . '::' . __LINE__ . ' failed to get content state for: ' . $collectionData['collectionId']); } } } } } return $this->_outputDom; }
/** * get application activesync controller * * @param ActiveSync_Model_Device $_device */ protected function _getController(ActiveSync_Model_Device $_device) { if ($this->_controller === null) { $this->_controller = ActiveSync_Controller::dataFactory($this->_class, $_device, new Tinebase_DateTime(null, null, 'de_DE')); } return $this->_controller; }
/** * (non-PHPdoc) * @see ActiveSync_Command_Wbxml::getResponse() */ public function getResponse() { $controller = ActiveSync_Controller::getInstance(); $itemEstimate = $this->_outputDom->documentElement; foreach ($this->_collections as $collections) { foreach ($collections as $collectionData) { $response = $itemEstimate->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Response')); $dataController = ActiveSync_Controller::dataFactory($collectionData['class'], $this->_device, $this->_syncTimeStamp); try { // does the folder exist? $dataController->getFolder($collectionData['collectionId']); $folderExists = true; } catch (ActiveSync_Exception_FolderNotFound $asefnf) { $folderExists = false; } if ($folderExists !== true) { Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . " folder does not exist"); $response->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Status', self::STATUS_INVALID_COLLECTION)); $collection = $response->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Collection')); $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Class', $collectionData['class'])); $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'CollectionId', $collectionData['collectionId'])); $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Estimate', 0)); } elseif ($collectionData['syncKeyValid'] !== true) { Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . " invalid synckey {$collectionData['syncKey']} provided"); /* * Android phones (and maybe others) don't take care about status 4(INVALID_SYNC_KEY) * To solve the problem we always return status 1(SUCCESS) even the sync key is invalid with Estimate set to 1. * This way the phone gets forced to sync. Handling invalid synckeys during sync command works without any problems. * $response->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Status', self::STATUS_INVALID_SYNC_KEY)); $collection = $response->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Collection')); $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Class', $collectionData['class'])); $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'CollectionId', $collectionData['collectionId'])); $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Estimate', 0)); */ $response->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Status', self::STATUS_SUCCESS)); $collection = $response->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Collection')); $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Class', $collectionData['class'])); $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'CollectionId', $collectionData['collectionId'])); $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Estimate', 1)); } else { $response->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Status', self::STATUS_SUCCESS)); $collection = $response->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Collection')); $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Class', $collectionData['class'])); $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'CollectionId', $collectionData['collectionId'])); if ($collectionData['syncKey'] <= 1) { // this is the first sync. in most cases there are data on the server. $count = count($dataController->getServerEntries($collectionData['collectionId'], $collectionData['filterType'])); } else { $count = $this->_getItemEstimate($dataController, $collectionData); } $collection->appendChild($this->_outputDom->createElementNS('uri:ItemEstimate', 'Estimate', $count)); } // store current filter type $filter = new ActiveSync_Model_FolderStateFilter(array(array('field' => 'device_id', 'operator' => 'equals', 'value' => $this->_device->getId()), array('field' => 'class', 'operator' => 'equals', 'value' => $collectionData['class']), array('field' => 'folderid', 'operator' => 'equals', 'value' => $collectionData['collectionId']))); $folderState = $this->_folderStateBackend->search($filter)->getFirstRecord(); // folderState can be NULL in case of not existing folder if ($folderState instanceof ActiveSync_Model_FolderState) { $folderState->lastfiltertype = $collectionData['filterType']; $this->_folderStateBackend->update($folderState); } } } return $this->_outputDom; }