/** * @param unknown_type $_class * @param Syncope_Model_IDevice $_device * @param DateTime $_timeStamp * @throws InvalidArgumentException * @return Syncope_Data_IData */ public static function factory($_classFactory, Syncope_Model_IDevice $_device, DateTime $_timeStamp) { switch ($_classFactory) { case self::CLASS_CALENDAR: $className = Syncope_Registry::get(Syncope_Registry::CALENDAR_DATA_CLASS); break; case self::CLASS_CONTACTS: $className = Syncope_Registry::get(Syncope_Registry::CONTACTS_DATA_CLASS); break; case self::STORE_EMAIL: case self::CLASS_EMAIL: $className = Syncope_Registry::get(Syncope_Registry::EMAIL_DATA_CLASS); break; case self::CLASS_TASKS: $className = Syncope_Registry::get(Syncope_Registry::TASKS_DATA_CLASS); break; default: throw new InvalidArgumentException('invalid class type provided'); breeak; } $class = new $className($_device, $_timeStamp); if (!$class instanceof Syncope_Data_IData) { throw new RuntimeException('class must be instanceof Syncope_Data_IData'); } return $class; }
/** * Returns TRUE if the $index is a named value in the registry, * or FALSE if $index was not found in the registry. * * @param string $index * @return boolean */ public static function isRegistered($index) { if (self::$_registry === null) { return false; } return self::$_registry->offsetExists($index); }
/** * (non-PHPdoc) * @see ActiveSync/ActiveSync_TestCase::setUp() */ protected function setUp() { Tinebase_TransactionManager::getInstance()->startTransaction(Tinebase_Core::getDb()); Syncope_Registry::setDatabase(Tinebase_Core::getDb()); Syncope_Registry::setTransactionManager(Tinebase_TransactionManager::getInstance()); $testDevice = ActiveSync_Backend_DeviceTests::getTestDevice(); $this->objects['device'] = ActiveSync_Controller_Device::getInstance()->create($testDevice); }
public function __construct($userId, Zend_Controller_Request_Http $request = null, $body = null) { if (Syncope_Registry::isRegistered('loggerBackend')) { $this->_logger = Syncope_Registry::get('loggerBackend'); } $this->_userId = $userId; $this->_request = $request !== null ? $request : new Zend_Controller_Request_Http(); $this->_body = $body !== null ? $body : fopen('php://input', 'r'); $this->_deviceBackend = Syncope_Registry::get('deviceBackend'); }
/** * (non-PHPdoc) * @see ActiveSync/ActiveSync_TestCase::setUp() */ protected function setUp() { Tinebase_TransactionManager::getInstance()->startTransaction(Tinebase_Core::getDb()); Syncope_Registry::setDatabase(Tinebase_Core::getDb()); Syncope_Registry::setTransactionManager(Tinebase_TransactionManager::getInstance()); $this->_deviceBackend = new Syncope_Backend_Device(Tinebase_Core::getDb(), SQL_TABLE_PREFIX . 'acsync_'); $this->_folderBackend = new Syncope_Backend_Folder(Tinebase_Core::getDb(), SQL_TABLE_PREFIX . 'acsync_'); $this->_syncStateBackend = new Syncope_Backend_SyncState(Tinebase_Core::getDb(), SQL_TABLE_PREFIX . 'acsync_'); $this->_contentStateBackend = new Syncope_Backend_Content(Tinebase_Core::getDb(), SQL_TABLE_PREFIX . 'acsync_'); $this->_device = $this->_deviceBackend->create(ActiveSync_Backend_DeviceTests::getTestDevice()); Syncope_Registry::set('deviceBackend', $this->_deviceBackend); Syncope_Registry::set('folderStateBackend', $this->_folderBackend); Syncope_Registry::set('syncStateBackend', $this->_syncStateBackend); Syncope_Registry::set('contentStateBackend', $this->_contentStateBackend); Syncope_Registry::set('loggerBackend', Tinebase_Core::getLogger()); Syncope_Registry::setContactsDataClass('ActiveSync_Controller_Contacts'); Syncope_Registry::setCalendarDataClass('ActiveSync_Controller_Calendar'); Syncope_Registry::setEmailDataClass('ActiveSync_Controller_Email'); Syncope_Registry::setTasksDataClass('ActiveSync_Controller_Tasks'); }
/** * constructor */ private function __construct() { if (Syncope_Registry::isRegistered('loggerBackend')) { $this->_logger = Syncope_Registry::get('loggerBackend'); } }
/** * Tears down the fixture * This method is called after a test is executed. * * @access protected */ protected function tearDown() { Syncope_Registry::getTransactionManager()->rollBack(); }
/** * the constructor * * @param mixed $_requestBody * @param Syncope_Model_Device $_device * @param string $_policyKey */ public function __construct($_requestBody, Syncope_Model_IDevice $_device, $_policyKey) { $this->_policyKey = $_policyKey; $this->_device = $_device; $this->_deviceBackend = Syncope_Registry::get('deviceBackend'); $this->_folderBackend = Syncope_Registry::get('folderStateBackend'); $this->_syncStateBackend = Syncope_Registry::get('syncStateBackend'); $this->_contentStateBackend = Syncope_Registry::get('contentStateBackend'); if (Syncope_Registry::isRegistered('loggerBackend')) { $this->_logger = Syncope_Registry::get('loggerBackend'); } if ($this->_skipValidatePolicyKey !== true && $this->_policyKey === null) { #throw new Syncope_Exception_PolicyKeyMissing(); } if ($this->_skipValidatePolicyKey !== true && ($this->_policyKey === 0 || $this->_device->policykey != $this->_policyKey)) { #throw new Syncope_Exception_ProvisioningNeeded(); } // should we wipe the mobile phone? if ($this->_skipValidatePolicyKey !== true && !empty($this->_policyKey) && $this->_device->remotewipe >= Syncope_Command_Provision::REMOTEWIPE_REQUESTED) { throw new Syncope_Exception_ProvisioningNeeded(); } $this->_inputDom = $_requestBody; $this->_syncTimeStamp = new DateTime(null, new DateTimeZone('UTC')); if ($this->_logger instanceof Zend_Log) { $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " sync timestamp: " . $this->_syncTimeStamp->format('Y-m-d H:i:s')); } // Creates an instance of the DOMImplementation class $imp = new DOMImplementation(); // Creates a DOMDocumentType instance $dtd = $imp->createDocumentType('AirSync', "-//AIRSYNC//DTD AirSync//EN", "http://www.microsoft.com/"); // Creates a DOMDocument instance $this->_outputDom = $imp->createDocument($this->_defaultNameSpace, $this->_documentElement, $dtd); $this->_outputDom->formatOutput = false; $this->_outputDom->encoding = 'utf-8'; }
/** * (non-PHPdoc) * @see Syncope_Command_Wbxml::getResponse() */ public function getResponse() { $this->_outputDom->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:AirSyncBase', 'uri:AirSyncBase'); $sync = $this->_outputDom->documentElement; $collections = $sync->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Collections')); foreach ($this->_collections as $collectionData) { // invalid collectionid provided if (!$collectionData['folder'] instanceof Syncope_Model_IFolder) { $collection = $collections->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Collection')); $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'SyncKey', 0)); $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'CollectionId', $collectionData['collectionId'])); $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_FOLDER_HIERARCHY_HAS_CHANGED)); // invalid synckey provided } elseif (!$collectionData['syncState'] instanceof Syncope_Model_ISyncState) { // set synckey to 0 $collection = $collections->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Collection')); $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'SyncKey', 0)); $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'CollectionId', $collectionData['collectionId'])); $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_INVALID_SYNC_KEY)); // initial sync } elseif ($collectionData['syncState']->counter === 0) { $collectionData['syncState']->counter++; // initial sync // send back a new SyncKey only $collection = $collections->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Collection')); if (!empty($collectionData['folder']->class)) { $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Class', $collectionData['folder']->class)); } $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'SyncKey', $collectionData['syncState']->counter)); $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'CollectionId', $collectionData['collectionId'])); $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_SUCCESS)); } else { $dataController = Syncope_Data_Factory::factory($collectionData['folder']->class, $this->_device, $this->_syncTimeStamp); $serverAdds = array(); $serverChanges = array(); $serverDeletes = array(); $moreAvailable = false; if ($collectionData['getChanges'] === true) { // continue sync session? if (is_array($collectionData['syncState']->pendingdata)) { if ($this->_logger instanceof Zend_Log) { $this->_logger->info(__METHOD__ . '::' . __LINE__ . " restored from sync state "); } $serverAdds = $collectionData['syncState']->pendingdata['serverAdds']; $serverChanges = $collectionData['syncState']->pendingdata['serverChanges']; $serverDeletes = $collectionData['syncState']->pendingdata['serverDeletes']; } else { // fetch entries added since last sync $allClientEntries = $this->_contentStateBackend->getFolderState($this->_device, $collectionData['folder']); $allServerEntries = $dataController->getServerEntries($collectionData['collectionId'], $collectionData['filterType']); // add entries $serverDiff = array_diff($allServerEntries, $allClientEntries); // add entries which produced problems during delete from client $serverAdds = $collectionData['forceAdd']; // add entries not yet sent to client $serverAdds = array_unique(array_merge($serverAdds, $serverDiff)); # @todo still needed? foreach ($serverAdds as $id => $serverId) { // skip entries added by client during this sync session if (isset($collectionData['added'][$serverId]) && !isset($collectionData['forceAdd'][$serverId])) { if ($this->_logger instanceof Zend_Log) { $this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped added entry: " . $serverId); } unset($serverAdds[$id]); } } // entries to be deleted $serverDeletes = array_diff($allClientEntries, $allServerEntries); // fetch entries changed since last sync $serverChanges = $dataController->getChangedEntries($collectionData['collectionId'], $collectionData['syncState']->lastsync, $this->_syncTimeStamp); $serverChanges = array_merge($serverChanges, $collectionData['forceChange']); foreach ($serverChanges as $id => $serverId) { // skip entry, if it got changed by client during current sync if (isset($collectionData['changed'][$serverId]) && !isset($collectionData['forceChange'][$serverId])) { if ($this->_logger instanceof Zend_Log) { $this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped changed entry: " . $serverId); } unset($serverChanges[$id]); } } // entries comeing in scope are already in $serverAdds and do not need to // be send with $serverCanges $serverChanges = array_diff($serverChanges, $serverAdds); } if ($this->_logger instanceof Zend_Log) { $this->_logger->info(__METHOD__ . '::' . __LINE__ . " found (added/changed/deleted) " . count($serverAdds) . '/' . count($serverChanges) . '/' . count($serverDeletes) . ' entries for sync from server to client'); } } if (!empty($collectionData['added']) || !empty($collectionData['changed']) || !empty($collectionData['deleted']) || !empty($serverAdds) || !empty($serverChanges) || !empty($serverDeletes)) { $collectionData['syncState']->counter++; } // collection header $collection = $collections->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Collection')); if (!empty($collectionData['folder']->class)) { $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Class', $collectionData['folder']->class)); } $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'SyncKey', $collectionData['syncState']->counter)); $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'CollectionId', $collectionData['collectionId'])); $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_SUCCESS)); $responses = $this->_outputDom->createElementNS('uri:AirSync', 'Responses'); // send reponse for newly added entries if (!empty($collectionData['added'])) { foreach ($collectionData['added'] as $entryData) { $add = $responses->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Add')); $add->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ClientId', $entryData['clientId'])); // we have no serverId is the add failed if (isset($entryData['serverId'])) { $add->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ServerId', $entryData['serverId'])); } $add->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', $entryData['status'])); } } // send reponse for changed entries if (!empty($collectionData['changed'])) { foreach ($collectionData['changed'] as $serverId => $status) { if ($status !== Syncope_Command_Sync::STATUS_SUCCESS) { $change = $responses->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Change')); $change->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ServerId', $serverId)); $change->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', $status)); } } } // send response for to be fetched entries if (!empty($collectionData['toBeFetched'])) { foreach ($collectionData['toBeFetched'] as $serverId) { $fetch = $responses->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Fetch')); $fetch->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ServerId', $serverId)); try { $applicationData = $this->_outputDom->createElementNS('uri:AirSync', 'ApplicationData'); $dataController->appendXML($applicationData, $collectionData, $serverId); $fetch->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_SUCCESS)); $fetch->appendChild($applicationData); } catch (Exception $e) { if ($this->_logger instanceof Zend_Log) { $this->_logger->warn(__METHOD__ . '::' . __LINE__ . " unable to convert entry to xml: " . $e->getMessage()); } $fetch->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_OBJECT_NOT_FOUND)); } } } if ($responses->hasChildNodes() === true) { $collection->appendChild($responses); } if (count($serverAdds) + count($serverChanges) + count($serverDeletes) > $collectionData['windowSize']) { $moreAvailable = true; $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'MoreAvailable')); } $commands = $this->_outputDom->createElementNS('uri:AirSync', 'Commands'); /** * process entries added on server side */ $newContentStates = array(); foreach ($serverAdds as $id => $serverId) { if ($this->_totalCount === $collectionData['windowSize']) { break; } #/** # * somewhere is a problem in the logic for handling moreAvailable # * # * it can happen, that we have a contentstate (which means we sent the entry to the client # * and that this entry is yet in $collectionData['syncState']->pendingdata['serverAdds'] # * I have no idea how this can happen, but the next lines of code work around this problem # */ #try { # $this->_contentStateBackend->getContentState($this->_device, $collectionData['folder'], $serverId); # # if ($this->_logger instanceof Zend_Log) # $this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped an entry($serverId) which is already on the client"); # # unset($serverAdds[$id]); # continue; # #} catch (Syncope_Exception_NotFound $senf) { # // do nothing => content state should not exist yet #} try { $add = $this->_outputDom->createElementNS('uri:AirSync', 'Add'); $add->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ServerId', $serverId)); $applicationData = $add->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ApplicationData')); $dataController->appendXML($applicationData, $collectionData, $serverId); $commands->appendChild($add); $this->_totalCount++; } catch (Exception $e) { if ($this->_logger instanceof Zend_Log) { $this->_logger->warn(__METHOD__ . '::' . __LINE__ . " unable to convert entry to xml: " . $e->getMessage()); } } // mark as send to the client, even the conversion to xml might have failed $newContentStates[] = new Syncope_Model_Content(array('device_id' => $this->_device, 'folder_id' => $collectionData['folder'], 'contentid' => $serverId, 'creation_time' => $this->_syncTimeStamp, 'creation_synckey' => $collectionData['syncState']->counter)); unset($serverAdds[$id]); } /** * process entries changed on server side */ foreach ($serverChanges as $id => $serverId) { if ($this->_totalCount === $collectionData['windowSize']) { break; } try { $change = $this->_outputDom->createElementNS('uri:AirSync', 'Change'); $change->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ServerId', $serverId)); $applicationData = $change->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ApplicationData')); $dataController->appendXML($applicationData, $collectionData, $serverId); $commands->appendChild($change); $this->_totalCount++; } catch (Exception $e) { if ($this->_logger instanceof Zend_Log) { $this->_logger->warn(__METHOD__ . '::' . __LINE__ . " unable to convert entry to xml: " . $e->getMessage()); } } unset($serverChanges[$id]); } /** * process entries deleted on server side */ $deletedContentStates = array(); foreach ($serverDeletes as $id => $serverId) { if ($this->_totalCount === $collectionData['windowSize']) { break; } try { // check if we have sent this entry to the phone $state = $this->_contentStateBackend->getContentState($this->_device, $collectionData['folder'], $serverId); $delete = $this->_outputDom->createElementNS('uri:AirSync', 'Delete'); $delete->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ServerId', $serverId)); $deletedContentStates[] = $state; $commands->appendChild($delete); $this->_totalCount++; } catch (Exception $e) { if ($this->_logger instanceof Zend_Log) { $this->_logger->warn(__METHOD__ . '::' . __LINE__ . " unable to convert entry to xml: " . $e->getMessage()); } } unset($serverDeletes[$id]); } if ($commands->hasChildNodes() === true) { $collection->appendChild($commands); } if ($this->_logger instanceof Zend_Log) { $this->_logger->info(__METHOD__ . '::' . __LINE__ . " new synckey is " . $collectionData['syncState']->counter); } } if (isset($collectionData['syncState']) && $collectionData['syncState'] instanceof Syncope_Model_ISyncState && $collectionData['syncState']->counter != $collectionData['syncKey']) { // increment sync timestamp by 1 second $this->_syncTimeStamp->modify('+1 sec'); // store pending data in sync state when needed if (isset($moreAvailable) && $moreAvailable === true) { $collectionData['syncState']->pendingdata = array('serverAdds' => (array) $serverAdds, 'serverChanges' => (array) $serverChanges, 'serverDeletes' => (array) $serverDeletes); } else { $collectionData['syncState']->pendingdata = null; } if (!empty($collectionData['added'])) { if ($this->_logger instanceof Zend_Log) { $this->_logger->info(__METHOD__ . '::' . __LINE__ . " remove previous synckey as client added new entries"); } $keepPreviousSyncKey = false; } else { $keepPreviousSyncKey = true; } $collectionData['syncState']->lastsync = $this->_syncTimeStamp; try { $transactionId = Syncope_Registry::getTransactionManager()->startTransaction(Syncope_Registry::getDatabase()); // store new synckey $this->_syncStateBackend->create($collectionData['syncState'], $keepPreviousSyncKey); // store contentstates for new entries added to client if (isset($newContentStates)) { foreach ($newContentStates as $state) { $this->_contentStateBackend->create($state); } } // remove contentstates for entries to be deleted on client if (isset($deletedContentStates)) { foreach ($deletedContentStates as $state) { $this->_contentStateBackend->delete($state); } } Syncope_Registry::getTransactionManager()->commitTransaction($transactionId); } catch (Zend_Db_Statement_Exception $zdse) { // something went wrong // maybe another parallel request added a new synckey // we must remove data added from client if (!empty($collectionData['added'])) { foreach ($collectionData['added'] as $added) { $this->_contentStateBackend->delete($added['contentState']); $dataController->deleteEntry($collectionData['collectionId'], $added['serverId'], array()); } } Syncope_Registry::getTransactionManager()->rollBack(); throw $zdse; } // store current filter type try { $folderState = $this->_folderBackend->getFolder($this->_device, $collectionData['collectionId']); $folderState->lastfiltertype = $collectionData['filterType']; $this->_folderBackend->update($folderState); } catch (Syncope_Exception_NotFound $senf) { // failed to get folderstate => should not happen but is also no problem in this state if ($this->_logger instanceof Zend_Log) { $this->_logger->crit(__METHOD__ . '::' . __LINE__ . ' failed to get content state for: ' . $collectionData['collectionId']); } } } } return $this->_outputDom; }