/**
  * generate FolderUpdate response
  *
  * @todo currently we support only the main folder which contains all contacts/tasks/events/notes per class
  */
 public function getResponse($_keepSession = FALSE)
 {
     $folderUpdate = $this->_outputDom->documentElement;
     if ($this->_syncKey > '0' && $this->_controller->validateSyncKey($this->_device, $this->_syncKey, 'FolderSync') === false) {
         Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " INVALID synckey");
         $folderUpdate->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Status', ActiveSync_Command_FolderSync::STATUS_INVALID_SYNC_KEY));
     } else {
         $newSyncKey = $this->_syncKey + 1;
         // create xml output
         $folderUpdate->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Status', ActiveSync_Command_FolderSync::STATUS_SUCCESS));
         $folderUpdate->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'SyncKey', $newSyncKey));
         $this->_controller->updateSyncKey($this->_device, $newSyncKey, $this->_syncTimeStamp, 'FolderSync');
     }
     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;
 }
Beispiel #3
0
 /**
  * process the XML file and add, change, delete or fetches data 
  */
 public function handle()
 {
     // input xml
     $xml = new SimpleXMLElement($this->_inputDom->saveXML());
     #$xml = simplexml_import_dom($this->_inputDom);
     foreach ($xml->Collections->Collection as $xmlCollection) {
         $collectionData = array('syncKey' => (int) $xmlCollection->SyncKey, 'syncKeyValid' => true, 'class' => isset($xmlCollection->Class) ? (string) $xmlCollection->Class : null, 'collectionId' => (string) $xmlCollection->CollectionId, 'windowSize' => isset($xmlCollection->WindowSize) ? (int) $xmlCollection->WindowSize : 100, 'deletesAsMoves' => isset($xmlCollection->DeletesAsMoves) ? true : false, 'getChanges' => isset($xmlCollection->GetChanges) ? true : false, 'added' => array(), 'changed' => array(), 'deleted' => array(), 'forceAdd' => array(), 'forceChange' => array(), 'toBeFetched' => array(), 'filterType' => 0, 'mimeSupport' => self::MIMESUPPORT_DONT_SEND_MIME, 'mimeTruncation' => ActiveSync_Command_Sync::TRUNCATE_NOTHING, 'bodyPreferences' => array());
         // process options
         if (isset($xmlCollection->Options)) {
             // optional parameters
             if (isset($xmlCollection->Options->FilterType)) {
                 $collectionData['filterType'] = (int) $xmlCollection->Options->FilterType;
             }
             if (isset($xmlCollection->Options->MIMESupport)) {
                 $collectionData['mimeSupport'] = (int) $xmlCollection->Options->MIMESupport;
             }
             if (isset($xmlCollection->Options->MIMETruncation)) {
                 $collectionData['mimeTruncation'] = (int) $xmlCollection->Options->MIMETruncation;
             }
             // try to fetch element from AirSyncBase:BodyPreference
             $airSyncBase = $xmlCollection->Options->children('uri:AirSyncBase');
             if (isset($airSyncBase->BodyPreference)) {
                 foreach ($airSyncBase->BodyPreference as $bodyPreference) {
                     $type = (int) $bodyPreference->Type;
                     $collectionData['bodyPreferences'][$type] = array('type' => $type);
                     // optional
                     if (isset($bodyPreference->TruncationSize)) {
                         $collectionData['bodyPreferences'][$type]['truncationSize'] = (int) $bodyPreference->TruncationSize;
                     }
                 }
             }
         }
         // does the folder exist for this device
         try {
             $folder = $this->getFolderState($this->_device, $collectionData['collectionId']);
             // newer clients don't send the class tag anymore
             $collectionData['class'] = $folder->class;
         } catch (Tinebase_Exception_NotFound $e) {
             if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) {
                 Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . " folder {$collectionData['collectionId']} not found");
             }
             $collectionData['syncState'] = new ActiveSync_Model_SyncState(array('device_id' => $this->_device->getId(), 'counter' => 0, 'type' => $collectionData['collectionId'], 'lastsync' => $this->_syncTimeStamp));
             $this->_collections['collectionNotFound'][$collectionData['collectionId']] = $collectionData;
             continue;
         }
         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " SyncKey is {$collectionData['syncKey']} Class: {$collectionData['class']} CollectionId: {$collectionData['collectionId']}");
         }
         // initial synckey
         if ($collectionData['syncKey'] === 0) {
             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
                 Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " initial client synckey 0 provided");
             }
             $collectionData['syncState'] = new ActiveSync_Model_SyncState(array('device_id' => $this->_device->getId(), 'counter' => $collectionData['syncKey'], 'type' => $collectionData['class'] . '-' . $collectionData['collectionId'], 'lastsync' => $this->_syncTimeStamp));
             $this->_collections[$collectionData['class']][$collectionData['collectionId']] = $collectionData;
             continue;
         }
         // check for invalid sycnkey
         if (($collectionData['syncState'] = $this->_controller->validateSyncKey($this->_device, $collectionData['syncKey'], $collectionData['class'], $collectionData['collectionId'])) === false) {
             if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) {
                 Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . " invalid synckey {$collectionData['syncKey']} provided");
             }
             $collectionData['syncKeyValid'] = false;
             $collectionData['syncState'] = new ActiveSync_Model_SyncState(array('device_id' => $this->_device->getId(), 'counter' => $collectionData['syncKey'], 'type' => $collectionData['class'] . '-' . $collectionData['collectionId'], 'lastsync' => $this->_syncTimeStamp));
             $this->_collections[$collectionData['class']][$collectionData['collectionId']] = $collectionData;
             continue;
         }
         $dataController = ActiveSync_Controller::dataFactory($collectionData['class'], $this->_device, $this->_syncTimeStamp);
         // handle incoming data
         if (isset($xmlCollection->Commands->Add)) {
             $adds = $xmlCollection->Commands->Add;
             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
                 Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " found " . count($adds) . " entries to be added to server");
             }
             foreach ($adds as $add) {
                 if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) {
                     Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ . " add entry with clientId " . (string) $add->ClientId);
                 }
                 // search for existing entries if first sync
                 // @todo: maybe this can be removed
                 // good phones don't send entries at synckey 1
                 // and if they sent, maybe they also send entries at synckey 2 too
                 if ($collectionData['syncKey'] == 1) {
                     $existing = $dataController->search($collectionData['collectionId'], $add->ApplicationData);
                 } else {
                     $existing = array();
                     // count() == 0
                 }
                 try {
                     if (count($existing) === 0) {
                         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
                             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " entry not found. adding as new");
                         }
                         $added = $dataController->add($collectionData['collectionId'], $add->ApplicationData);
                     } else {
                         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
                             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " found matching entry. reuse existing entry");
                         }
                         // use the first found entry
                         $added = $existing[0];
                     }
                     $collectionData['added'][(string) $add->ClientId]['serverId'] = $added->getId();
                     $collectionData['added'][(string) $add->ClientId]['status'] = self::STATUS_SUCCESS;
                     $this->_addContentState($collectionData['class'], $collectionData['collectionId'], $added->getId());
                 } catch (Exception $e) {
                     if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) {
                         Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . " failed to add entry " . $e->getMessage());
                     }
                     $collectionData['added'][(string) $add->ClientId]['status'] = self::STATUS_SERVER_ERROR;
                 }
             }
         }
         // handle changes, but only if not first sync
         if ($collectionData['syncKey'] > 1 && isset($xmlCollection->Commands->Change)) {
             $changes = $xmlCollection->Commands->Change;
             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
                 Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " found " . count($changes) . " entries to be updated on server");
             }
             foreach ($changes as $change) {
                 $serverId = (string) $change->ServerId;
                 try {
                     $changed = $dataController->change($collectionData['collectionId'], $serverId, $change->ApplicationData);
                     $collectionData['changed'][$serverId] = self::STATUS_SUCCESS;
                 } catch (Tinebase_Exception_AccessDenied $e) {
                     $collectionData['changed'][$serverId] = self::STATUS_CONFLICT_MATCHING_THE_CLIENT_AND_SERVER_OBJECT;
                     $collectionData['forceChange'][$serverId] = $serverId;
                 } catch (Tinebase_Exception_NotFound $e) {
                     // entry does not exist anymore, will get deleted automaticaly
                     $collectionData['changed'][$serverId] = self::STATUS_OBJECT_NOT_FOUND;
                 } catch (Exception $e) {
                     if (Tinebase_Core::isLogLevel(Zend_Log::WARN)) {
                         Tinebase_Core::getLogger()->warn(__METHOD__ . '::' . __LINE__ . " failed to update entry " . $e);
                     }
                     // something went wrong while trying to update the entry
                     $collectionData['changed'][$serverId] = self::STATUS_SERVER_ERROR;
                 }
             }
         }
         // handle deletes, but only if not first sync
         if (isset($xmlCollection->Commands->Delete)) {
             $deletes = $xmlCollection->Commands->Delete;
             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
                 Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " found " . count($deletes) . " entries to be deleted on server");
             }
             foreach ($deletes as $delete) {
                 $serverId = (string) $delete->ServerId;
                 try {
                     // check if we have send this entry to the phone
                     $this->_controller->getContentState($this->_device, $collectionData['class'], $collectionData['collectionId'], $serverId);
                     try {
                         $dataController->delete($collectionData['collectionId'], $serverId, $collectionData);
                     } catch (Tinebase_Exception_NotFound $e) {
                         if (Tinebase_Core::isLogLevel(Zend_Log::CRIT)) {
                             Tinebase_Core::getLogger()->crit(__METHOD__ . '::' . __LINE__ . ' tried to delete entry ' . $serverId . ' but entry was not found');
                         }
                     } catch (Tinebase_Exception $e) {
                         if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
                             Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' tried to delete entry ' . $serverId . ' but a error occured: ' . $e->getMessage());
                         }
                         $collectionData['forceAdd'][$serverId] = $serverId;
                     }
                 } catch (Tinebase_Exception_NotFound $tenf) {
                     if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
                         Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . ' ' . $serverId . ' should have been removed from client already');
                     }
                     // should we send a special status???
                     //$collectionData['deleted'][$serverId] = self::STATUS_SUCCESS;
                 }
                 $collectionData['deleted'][$serverId] = self::STATUS_SUCCESS;
                 $this->_deleteContentState($collectionData['class'], $collectionData['collectionId'], $serverId);
             }
         }
         // handle fetches, but only if not first sync
         if ($collectionData['syncKey'] > 1 && isset($xmlCollection->Commands->Fetch)) {
             // the default value for GetChanges is 1. If the phone don't want the changes it must set GetChanges to 0
             // unfortunately the iPhone dont set GetChanges to 0 when fetching email body, but is confused when we send
             // changes
             if (!isset($xmlCollection->GetChanges)) {
                 $collectionData['getChanges'] = false;
             }
             $fetches = $xmlCollection->Commands->Fetch;
             if (Tinebase_Core::isLogLevel(Zend_Log::INFO)) {
                 Tinebase_Core::getLogger()->info(__METHOD__ . '::' . __LINE__ . " found " . count($fetches) . " entries to be fetched from server");
             }
             foreach ($fetches as $fetch) {
                 $serverId = (string) $fetch->ServerId;
                 $collectionData['toBeFetched'][$serverId] = $serverId;
             }
         }
         $this->_collections[$collectionData['class']][$collectionData['collectionId']] = $collectionData;
     }
 }