diff --git a/phpgwapi/inc/horde/Horde/SyncML/Command/Sync/ContentSyncElement.php b/phpgwapi/inc/horde/Horde/SyncML/Command/Sync/ContentSyncElement.php new file mode 100644 index 0000000000..517bbedb0a --- /dev/null +++ b/phpgwapi/inc/horde/Horde/SyncML/Command/Sync/ContentSyncElement.php @@ -0,0 +1,158 @@ + + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @author Anthony Mills + * @version $Revision$ + * @since Horde 3.0 + * @package Horde_SyncML + */ +class Horde_SyncML_Command_Sync_ContentSyncElement extends Horde_SyncML_Command_Sync_SyncElement { + + /** + * The content: vcard data, etc. + */ + var $_content; + + /** + * Local to server: our Horde guid. + */ + var $_locURI; + + var $_targetURI; + var $_contentType; + + function setSourceURI($uri) + { + $this->_locURI = $uri; + } + + function getSourceURI() + { + return $this->_locURI; + } + + function setTargetURI($uri) + { + $this->_targetURI = $uri; + } + + function getTargetURI() + { + return $this->_targetURI; + } + + function setContentType($c) + { + $this->_contentType = $c; + } + + function setContentFormat($_format) + { + $this->_contentFormat = $_format; + } + + function getContentType() + { + return $this->_contentType; + } + + function getContent() + { + return $this->_content; + } + + function setContent($content) + { + $this->_content = $content; + } + + function endElement($uri, $element) + { + switch ($this->_xmlStack) { + case 2: + if ($element == 'Data') { + $this->_content = trim($this->_chars); + } + break; + } + + parent::endElement($uri, $element); + } + + function outputCommand($currentCmdID, &$output, $command) + { + $state = $_SESSION['SyncML.state']; + + $attrs = array(); + $output->startElement($state->getURI(), $command, $attrs); + + $output->startElement($state->getURI(), 'CmdID', $attrs); + $chars = $currentCmdID; + $output->characters($chars); + $output->endElement($state->getURI(), 'CmdID'); + + if (isset($this->_contentType)) { + $output->startElement($state->getURI(), 'Meta', $attrs); + $output->startElement($state->getURIMeta(), 'Type', $attrs); + $output->characters($this->_contentType); + $output->endElement($state->getURIMeta(), 'Type'); + $output->endElement($state->getURI(), 'Meta'); + } + + if (isset($this->_content) + || isset($this->_locURI) || isset($this->targetURI)) { + $output->startElement($state->getURI(), 'Item', $attrs); + // send only when sending adds + if ($this->_locURI != null && (strtolower($command) == 'add')) { + $output->startElement($state->getURI(), 'Source', $attrs); + $output->startElement($state->getURI(), 'LocURI', $attrs); + $chars = substr($this->_locURI,0,39); + $output->characters($chars); + $output->endElement($state->getURI(), 'LocURI'); + $output->endElement($state->getURI(), 'Source'); + } + + if(isset($this->_contentFormat)) { + $output->startElement($state->getURI(), 'Meta', $attrs); + $output->startElement($state->getURIMeta(), 'Format', $attrs); + $output->characters($this->_contentFormat); + $output->endElement($state->getURIMeta(), 'Format'); + $output->endElement($state->getURI(), 'Meta'); + } + + if ($this->_targetURI != null) { + $output->startElement($state->getURI(), 'Target', $attrs); + $output->startElement($state->getURI(), 'LocURI', $attrs); + $chars = $this->_targetURI; + $output->characters($chars); + $output->endElement($state->getURI(), 'LocURI'); + $output->endElement($state->getURI(), 'Target'); + } + if (isset($this->_content)) { + $output->startElement($state->getURI(), 'Data', $attrs); + #$chars = '_content.']]>'; + $chars = $this->_content; + $output->characters($chars); + $output->endElement($state->getURI(), 'Data'); + } + $output->endElement($state->getURI(), 'Item'); + } + + $output->endElement($state->getURI(), $command); + + $currentCmdID++; + + return $currentCmdID; + } + +} diff --git a/phpgwapi/inc/horde/Horde/SyncML/State_egw.php b/phpgwapi/inc/horde/Horde/SyncML/State_egw.php new file mode 100644 index 0000000000..3bfc501d68 --- /dev/null +++ b/phpgwapi/inc/horde/Horde/SyncML/State_egw.php @@ -0,0 +1,409 @@ +_locName . $this->_sourceURI . $type; + + $db = clone($GLOBALS['phpgw']->db); + + $cols = array('map_timestamp'); + + $where = array + ( + 'map_id' => $mapID, + 'map_guid' => $guid, + ); + + #Horde::logMessage('SyncML: getChangeTS for ' . $mapID .' / '. $guid, __FILE__, __LINE__, PEAR_LOG_DEBUG); + + $db->select('egw_contentmap', $cols, $where, __LINE__, __FILE__); + + if($db->next_record()) + { + #Horde::logMessage('SyncML: getChangeTS changets is ' . $db->from_timestamp($db->f('map_timestamp')), __FILE__, __LINE__, PEAR_LOG_DEBUG); + return $db->from_timestamp($db->f('map_timestamp')); + } + + return false; + } + + /** + * Retrieves information about the clients device info if any. Returns + * false if no info found or a DateTreeObject with at least the + * following attributes: + * + * a array containing all available infos about the device + */ + function getClientDeviceInfo() + { + $deviceID = $this->_locName . $this->_sourceURI; + + $db = clone($GLOBALS['egw']->db); + + $cols = array + ( + 'dev_dtdversion', + 'dev_numberofchanges', + 'dev_largeobjs', + 'dev_swversion', + 'dev_oem', + 'dev_model', + 'dev_manufacturer', + 'dev_devicetype', + 'dev_deviceid', + 'dev_datastore', + ); + + $where = array + ( + 'dev_id' => $deviceID, + ); + + $db->select('egw_syncmldevinfo', $cols, $where, __LINE__, __FILE__); + + if($db->next_record()) + { + $devInfo = array + ( + 'DTDVersion' => $db->f('dev_dtdversion'), + 'supportNumberOfChanges' => $db->f('dev_numberofchanges'), + 'supportLargeObjs' => $db->f('dev_largeobjs'), + 'softwareVersion' => $db->f('dev_swversion'), + 'oem' => $db->f('dev_oem'), + 'model' => $db->f('dev_model'), + 'manufacturer' => $db->f('dev_manufacturer'), + 'deviceType' => $db->f('dev_devicetype'), + 'deviceID' => $db->f('dev_deviceid'), + 'dataStore' => unserialize($db->f('dev_datastore')), + ); + + return $devInfo; + } + + return false; + } + + /** + * Retrieves the Horde server guid (like + * kronolith:0d1b415fc124d3427722e95f0e926b75) for a given client + * locid. Returns false if no such id is stored yet. + * + * Opposite of getLocId which returns the locid for a given guid. + */ + function getGlobalUID($type, $locid) + { + $mapID = $this->_locName . $this->_sourceURI . $type; + + #Horde::logMessage('SyncML: search GlobalUID for ' . $mapID .' / '.$locid, __FILE__, __LINE__, PEAR_LOG_DEBUG); + + $db = clone($GLOBALS['egw']->db); + + $cols = array('map_guid'); + + $where = array + ( + 'map_id' => $mapID, + 'map_locuid' => $locid, + 'map_expired' => 0, + ); + + $db->select('egw_contentmap', $cols, $where, __LINE__, __FILE__); + + if($db->next_record()) + { + return $db->f('map_guid'); + } + + return false; + } + + /** + * Converts a EGW GUID (like + * kronolith:0d1b415fc124d3427722e95f0e926b75) to a client ID as + * used by the sync client (like 12) returns false if no such id + * is stored yet. + */ + function getLocID($type, $guid) + { + $mapID = $this->_locName . $this->_sourceURI . $type; + + $db = clone($GLOBALS['egw']->db); + + $cols = array('map_locuid'); + + $where = array + ( + 'map_id' => $mapID, + 'map_guid' => $guid + ); + Horde::logMessage('SyncML: search LocID for ' . $mapID .' / '.$guid, __FILE__, __LINE__, PEAR_LOG_DEBUG); + $db->select('egw_contentmap', $cols, $where, __LINE__, __FILE__); + + if($db->next_record()) + { + Horde::logMessage('SyncML: found LocID: '.$db->f('map_locuid'), __FILE__, __LINE__, PEAR_LOG_DEBUG); + return $db->f('map_locuid'); + } + + return false; + } + + /** + * Retrieves information about the previous sync if any. Returns + * false if no info found or a DateTreeObject with at least the + * following attributes: + * + * ClientAnchor: the clients Next Anchor of the previous sync. + * ServerAnchor: the Server Next Anchor of the previous sync. + */ + function getSyncSummary($type) + { + $deviceID = $this->_locName . $this->_sourceURI; + + $db = clone($GLOBALS['egw']->db); + + $cols = array('sync_serverts','sync_clientts'); + + $where = array + ( + 'dev_id' => $deviceID, + 'sync_path' => $type + ); + + $db->select('egw_syncmlsummary', $cols, $where, __LINE__, __FILE__); + + #Horde::logMessage("SyncML: get SYNCSummary for $deviceID", __FILE__, __LINE__, PEAR_LOG_DEBUG); + if($db->next_record()) + { + #Horde::logMessage("SyncML: get SYNCSummary for $deviceID serverts: ".$db->f('sync_serverts')." clients: ".$db->f('sync_clientts'), __FILE__, __LINE__, PEAR_LOG_DEBUG); + $retData = array + ( + 'ClientAnchor' => $db->f('sync_clientts'), + 'ServerAnchor' => $db->f('sync_serverts'), + ); + return $retData; + } + + return false; + + } + + function isAuthorized() + { + if (!$this->_isAuthorized) { + + if(!isset($this->_locName) && !isset($this->_password)) + { + Horde::logMessage('SyncML: Authentication not yet possible currently. Username and password not available' , __FILE__, __LINE__, PEAR_LOG_DEBUG); + return FALSE; + } + + if(!isset($this->_password)) + { + Horde::logMessage('SyncML: Authentication not yet possible currently. Password not available' , __FILE__, __LINE__, PEAR_LOG_DEBUG); + return FALSE; + } + + if(strstr($this->_locName,'@') === False) + { + $this->_locName .= '@'.$GLOBALS['egw_info']['server']['default_domain']; + } + + #Horde::logMessage('SyncML: authenticate with username: ' . $this->_locName . ' and password: ' . $this->_password, __FILE__, __LINE__, PEAR_LOG_DEBUG); + + if($GLOBALS['sessionid'] = $GLOBALS['egw']->session->create($this->_locName,$this->_password,'text','u')) + { + $this->_isAuthorized = true; + Horde::logMessage('SyncML_EGW: Authentication of ' . $this->_locName . '/' . $GLOBALS['sessionid'] . ' succeded' , __FILE__, __LINE__, PEAR_LOG_DEBUG); + } + else + { + $this->_isAuthorized = false; + Horde::logMessage('SyncML: Authentication of ' . $this->_locName . ' failed' , __FILE__, __LINE__, PEAR_LOG_INFO); + } + } + else + { + // store sessionID in a variable, because ->verify maybe resets that value + $sessionID = session_id(); + if(!$GLOBALS['egw']->session->verify($sessionID, 'staticsyncmlkp3')) { + Horde::logMessage('SyncML_EGW: egw session(' .$sessionID. ') not verified ' , __FILE__, __LINE__, PEAR_LOG_DEBUG); + } + } + + return $this->_isAuthorized; + } + + /** + * Removes the locid<->guid mapping for the given locid. Returns + * the guid that was removed or false if no mapping entry was + * found. + */ + function removeUID($type, $locid) + { + $mapID = $this->_locName . $this->_sourceURI . $type; + + $db = clone($GLOBALS['egw']->db); + + $cols = array('map_guid'); + + $where = array + ( + 'map_id' => $mapID, + 'map_locuid' => $locid + ); + + $db->select('egw_contentmap', $cols, $where, __LINE__, __FILE__); + + if(!$db->next_record()) + { + Horde::logMessage("SyncML: state->removeUID(type=$type,locid=$locid) : nothing to remove", __FILE__, __LINE__, PEAR_LOG_DEBUG); + return false; + } + + $guid = $db->f('map_guid'); + + #Horde::logMessage("SyncML: state->removeUID(type=$type,locid=$locid) : removing guid:$guid", __FILE__, __LINE__, PEAR_LOG_DEBUG); + + $db->delete('egw_contentmap', $where, __LINE__, __FILE__); + + return $guid; + } + + /** + * Puts a given client $locid and Horde server $guid pair into the + * map table to allow mapping between the client's and server's + * IDs. Actually there are two maps: from the localid to the guid + * and vice versa. The localid is converted to a key as follows: + * this->_locName . $this->_sourceURI . $type . $locid so you can + * have different syncs with different devices. If an entry + * already exists, it is overwritten. + */ + function setUID($type, $locid, $guid, $ts=0) + { + // fix $guid, it maybe was to long for some devices + // format is appname-id-systemid + $guidParts = explode('-',$guid); + if(count($guidParts) == 3) + { + $guid = $GLOBALS['egw']->common->generate_uid($guidParts[0],$guidParts[1]); + } + + if($ts == 0) + { + $ts = time(); + } + + #Horde::logMessage("SyncML: setUID $type, $locid, $guid, $ts ".count($guidParts), __FILE__, __LINE__, PEAR_LOG_DEBUG); + + $db = clone($GLOBALS['egw']->db); + + $mapID = $this->_locName . $this->_sourceURI . $type; + + // delete all client id's + $where = array( + 'map_id' => $mapID, + 'map_locuid' => $locid, + ); + $db->delete('egw_contentmap', $where, __LINE__, __FILE__); + + // delete all egw id's + $where = array( + 'map_id' => $mapID, + 'map_guid' => $guid, + ); + $db->delete('egw_contentmap', $where, __LINE__, __FILE__); + + $data = $where + array( + 'map_locuid' => $locid, + 'map_timestamp' => $ts, + 'map_expired' => 0, + ); + $db->insert('egw_contentmap', $data, $where, __LINE__, __FILE__); + + #Horde::logMessage("SyncML: setUID $type, $locid, $guid, $ts $mapID", __FILE__, __LINE__, PEAR_LOG_DEBUG); + + } + + /** + * write clients device info to database + */ + function writeClientDeviceInfo() + { + if (!isset($this->_clientDeviceInfo) || !is_array($this->_clientDeviceInfo)) { + return false; + } + + $deviceID = $this->_locName . $this->_sourceURI; + + $data = array + ( + 'dev_id' => $deviceID, + 'dev_dtdversion' => $this->_clientDeviceInfo['DTDVersion'], + 'dev_numberofchanges' => $this->_clientDeviceInfo['supportNumberOfChanges'], + 'dev_largeobjs' => $this->_clientDeviceInfo['supportLargeObjs'], + 'dev_swversion' => $this->_clientDeviceInfo['softwareVersion'], + 'dev_oem' => $this->_clientDeviceInfo['oem'], + 'dev_model' => $this->_clientDeviceInfo['model'], + 'dev_manufacturer' => $this->_clientDeviceInfo['manufacturer'], + 'dev_devicetype' => $this->_clientDeviceInfo['deviceType'], + 'dev_deviceid' => $this->_clientDeviceInfo['deviceID'], + 'dev_datastore' => serialize($this->_clientDeviceInfo['dataStore']), + ); + + $where = array + ( + 'dev_id' => $deviceID, + ); + + $GLOBALS['egw']->db->insert('egw_syncmldevinfo', $data, $where, __LINE__, __FILE__); + } + + /** + * After a successful sync, the client and server's Next Anchors + * are written to the database so they can be used to negotiate + * upcoming syncs. + */ + function writeSyncSummary() + { + #parent::writeSyncSummary(); + + if (!isset($this->_serverAnchorNext) || !is_array($this->_serverAnchorNext)) { + return; + } + + $deviceID = $this->_locName . $this->_sourceURI; + + foreach((array)$this->_serverAnchorNext as $type => $a) + { + Horde::logMessage("SyncML: write SYNCSummary for $deviceID $type serverts: $a clients: ".$this->_clientAnchorNext[$type], __FILE__, __LINE__, PEAR_LOG_DEBUG); + + $where = array + ( + 'dev_id' => $deviceID, + 'sync_path' => $type, + ); + + $data = $where + array + ( + 'sync_serverts' => $a, + 'sync_clientts' => $this->_clientAnchorNext[$type] + ); + + $GLOBALS['egw']->db->insert('egw_syncmlsummary', $data, $where, __LINE__, __FILE__); + } + } + + +} + +?> \ No newline at end of file diff --git a/phpgwapi/inc/horde/Horde/SyncML/Sync/SlowSync.php b/phpgwapi/inc/horde/Horde/SyncML/Sync/SlowSync.php index 9798adc563..c048196a9d 100644 --- a/phpgwapi/inc/horde/Horde/SyncML/Sync/SlowSync.php +++ b/phpgwapi/inc/horde/Horde/SyncML/Sync/SlowSync.php @@ -73,6 +73,9 @@ class Horde_SyncML_Sync_SlowSync extends Horde_SyncML_Sync_TwoWaySync { // can happen. #LK $cmd->setContent($state->convertServer2Client($c, $contentType)); $cmd->setContent($c); + if($hordeType == 'sifcalendar' || $hordeType == 'sifcontacts' || $hordeType == 'siftasks') { + $cmd->setContentFormat('b64'); + } $cmd->setContentType($contentType['ContentType']); $cmd->setSourceURI($guid); $currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Add'); diff --git a/phpgwapi/inc/horde/Horde/SyncML/Sync/TwoWaySync.php b/phpgwapi/inc/horde/Horde/SyncML/Sync/TwoWaySync.php new file mode 100644 index 0000000000..901cca881e --- /dev/null +++ b/phpgwapi/inc/horde/Horde/SyncML/Sync/TwoWaySync.php @@ -0,0 +1,247 @@ + + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @author Anthony Mills + * @author Karsten Fourmont + * + * @version $Revision$ + * @since Horde 3.0 + * @package Horde_SyncML + */ +class Horde_SyncML_Sync_TwoWaySync extends Horde_SyncML_Sync { + + function endSync($currentCmdID, &$output) + { + global $registry; + + $state = &$_SESSION['SyncML.state']; + + $syncType = $this->_targetLocURI; + $hordeType = str_replace('./','',$syncType); + + $refts = $state->getServerAnchorLast($syncType); + $currentCmdID = $this->handleSync($currentCmdID, + $hordeType, + $syncType, + $output, + $refts); + if ($syncType == 'calendar' && $state->handleTasksInCalendar()) { + Horde::logMessage("SyncML: handling tasks in calendar sync", __FILE__, __LINE__, PEAR_LOG_DEBUG); + + $currentCmdID = $this->handleSync($currentCmdID, 'tasks', $syncType, + $output, $refts); + } + + return $currentCmdID; + } + + function handleSync($currentCmdID, $hordeType, $syncType,&$output, $refts) { + global $registry; + + // array of Items which got modified, but got never send to the client before + $missedAdds = array(); + + $history = $GLOBALS['phpgw']->contenthistory; + $state = &$_SESSION['SyncML.state']; + $counter = 0; + + $changes = &$state->getChangedItems($hordeType); + $deletes = &$state->getDeletedItems($hordeType); + $adds = &$state->getAddedItems($hordeType); + + Horde::logMessage("SyncML: ".count($changes).' changed items found for '.$hordeType, __FILE__, __LINE__, PEAR_LOG_DEBUG); + Horde::logMessage("SyncML: ".count($deletes).' deleted items found for '.$hordeType, __FILE__, __LINE__, PEAR_LOG_DEBUG); + Horde::logMessage("SyncML: ".count($adds). ' added items found for '.$hordeType , __FILE__, __LINE__, PEAR_LOG_DEBUG); + + while($guid = array_shift($changes)) { + $guid_ts = $history->getTSforAction($guid, 'modify'); + $sync_ts = $state->getChangeTS($syncType, $guid); + Horde::logMessage("SyncML: timestamp modify guid_ts: $guid_ts sync_ts: $sync_ts", __FILE__, __LINE__, PEAR_LOG_DEBUG); + if ($sync_ts && $sync_ts == $guid_ts) { + // Change was done by us upon request of client. + // Don't mirror that back to the client. + Horde::logMessage("SyncML: change: $guid ignored, came from client", __FILE__, __LINE__, PEAR_LOG_DEBUG); + continue; + } + Horde::logMessage("SyncML: change $guid hs_ts:$guid_ts dt_ts:" . $state->getChangeTS($syncType, $guid), __FILE__, __LINE__, PEAR_LOG_DEBUG); + $locid = $state->getLocID($syncType, $guid); + if (!$locid) { + // somehow we missed to add, lets store the uid, so we add this entry later + $missedAdds[] = $guid; + Horde::logMessage("SyncML: unable to create change for $guid: locid not found in map", __FILE__, __LINE__, PEAR_LOG_WARNING); + continue; + } + + // Create a replace request for client. + $contentType = $state->getPreferedContentTypeClient($this->_sourceLocURI); + if(is_a($contentType, 'PEAR_Error')) { + // Client did not sent devinfo + $contentType = array('ContentType' => $state->getPreferedContentType($this->_targetLocURI)); + } + $c = $registry->call($hordeType. '/export', + array('guid' => $guid, 'contentType' => $contentType)); + if (!is_a($c, 'PEAR_Error')) { + // Item in history but not in database. Strange, but can happen. + Horde::logMessage("SyncML: change: $guid export content: $c", __FILE__, __LINE__, PEAR_LOG_DEBUG); + $cmd = &new Horde_SyncML_Command_Sync_ContentSyncElement(); +# LK $cmd->setContent($state->convertServer2Client($c, $contentType)); + $cmd->setContent($c); + $cmd->setSourceURI($guid); + $cmd->setTargetURI($locid); + $cmd->setContentType($contentType['ContentType']); + if($hordeType == 'sifcalendar' || $hordeType == 'sifcontacts' || $hordeType == 'siftasks') { + $cmd->setContentFormat('b64'); + } + $currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Replace'); + $state->log('Server-Replace'); + + // return if we have to much data + if(++$counter >= MAX_ENTRIES && $hordeType != 'sifcalendar' && $hordeType != 'sifcontacts' && $hordeType != 'siftasks') { + $state->setSyncStatus(SERVER_SYNC_DATA_PENDING); + return $currentCmdID; + } + } + } + Horde::logMessage("SyncML: handling sync (changes done) ".$currentCmdID, __FILE__, __LINE__, PEAR_LOG_DEBUG); + + // deletes + while($guid = array_shift($deletes)) + { + $guid_ts = $history->getTSforAction($guid, 'delete'); + $sync_ts = $state->getChangeTS($syncType, $guid); + Horde::logMessage("SyncML: timestamp delete guid_ts: $guid_ts sync_ts: $sync_ts", __FILE__, __LINE__, PEAR_LOG_DEBUG); + if ($sync_ts && $sync_ts == $guid_ts) { + // Change was done by us upon request of client. + // Don't mirror that back to the client. + Horde::logMessage("SyncML: delete $guid ignored, came from client", __FILE__, __LINE__, PEAR_LOG_DEBUG); + continue; + } + $locid = $state->getLocID($syncType, $guid); + if (!$locid) { + Horde::logMessage("SyncML: unable to create delete for $guid: locid not found in map", __FILE__, __LINE__, PEAR_LOG_WARNING); + continue; + } + + Horde::logMessage("SyncML: delete: $guid", __FILE__, __LINE__, PEAR_LOG_DEBUG); + // Create a Delete request for client. + $cmd = &new Horde_SyncML_Command_Sync_ContentSyncElement(); + $cmd->setTargetURI($locid); + $cmd->setSourceURI($guid); + $currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Delete'); + $state->log('Server-Delete'); + $state->removeUID($syncType, $locid); + + // return if we have to much data + if(++$counter >= MAX_ENTRIES && $hordeType != 'sifcalender' && $hordeType != 'sifcontacts' &&$hordeType != 'siftasks') { + $state->setSyncStatus(SERVER_SYNC_DATA_PENDING); + return $currentCmdID; + } + } + #Horde::logMessage("SyncML: handling sync ".$currentCmdID, __FILE__, __LINE__, PEAR_LOG_DEBUG); + + // Get adds. + if(count($missedAdds) > 0) { + Horde::logMessage("SyncML: add missed changes as adds ".count($adds).' / '.$missedAdds[0], __FILE__, __LINE__, PEAR_LOG_DEBUG); + $state->setAddedItems($hordeType, array_merge($adds, $missedAdds)); + $adds = &$state->getAddedItems($hordeType); + Horde::logMessage("SyncML: merged adds counter ".count($adds).' / '.$adds[0], __FILE__, __LINE__, PEAR_LOG_DEBUG); + } + + while($guid = array_shift($adds)) { + $guid_ts = $history->getTSforAction($guid, 'add'); + $sync_ts = $state->getChangeTS($syncType, $guid); + Horde::logMessage("SyncML: timestamp add $guid guid_ts: $guid_ts sync_ts: $sync_ts", __FILE__, __LINE__, PEAR_LOG_DEBUG); + if ($sync_ts && $sync_ts == $guid_ts) { + // Change was done by us upon request of client. + // Don't mirror that back to the client. + Horde::logMessage("SyncML: add: $guid ignored, came from client", __FILE__, __LINE__, PEAR_LOG_DEBUG); + continue; + } + + $locid = $state->getLocID($syncType, $guid); + + if ($locid && $refts == 0) { + // For slow sync (ts=0): do not add data for which we + // have a locid again. This is a heuristic to avoid + // duplication of entries. + Horde::logMessage("SyncML: skipping add of guid $guid as there already is a locid $locid", __FILE__, __LINE__, PEAR_LOG_DEBUG); + continue; + } + Horde::logMessage("SyncML: add: $guid", __FILE__, __LINE__, PEAR_LOG_DEBUG); + + // Create an Add request for client. + $contentType = $state->getPreferedContentTypeClient($this->_sourceLocURI); + if(is_a($contentType, 'PEAR_Error')) { + // Client did not sent devinfo + $contentType = array('ContentType' => $state->getPreferedContentType($this->_targetLocURI)); + } + + $cmd = &new Horde_SyncML_Command_Sync_ContentSyncElement(); + $c = $registry->call($hordeType . '/export', + array( + 'guid' => $guid , + 'contentType' => $contentType , + ) + ); + + if (!is_a($c, 'PEAR_Error')) { + // Item in history but not in database. Strange, but can happen. + $cmd->setContent($c); + if($hordeType == 'sifcalendar' || $hordeType == 'sifcontacts' || $hordeType == 'siftasks') { + $cmd->setContentFormat('b64'); + } + $cmd->setContentType($contentType['ContentType']); + $cmd->setSourceURI($guid); + $currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Add'); + $state->log('Server-Add'); + + // return if we have to much data + if(++$counter >= MAX_ENTRIES && $hordeType != 'sifcalendar' && $hordeType != 'sifcontacts' &&$hordeType != 'siftasks') { + $state->setSyncStatus(SERVER_SYNC_DATA_PENDING); + return $currentCmdID; + } + } + } + Horde::logMessage("SyncML: handling sync ".$currentCmdID, __FILE__, __LINE__, PEAR_LOG_DEBUG); + + $state->clearSync($syncType); + + return $currentCmdID; + } + + function loadData() + { + global $registry; + + $state = &$_SESSION['SyncML.state']; + $syncType = $this->_targetLocURI; + $hordeType = str_replace('./','',$syncType); + $refts = $state->getServerAnchorLast($syncType); + + Horde::logMessage("SyncML: reading changed items from database for $hordeType", __FILE__, __LINE__, PEAR_LOG_DEBUG); + $state->setChangedItems($hordeType, $registry->call($hordeType. '/listBy', array('action' => 'modify', 'timestamp' => $refts))); + + Horde::logMessage("SyncML: reading deleted items from database for $hordeType", __FILE__, __LINE__, PEAR_LOG_DEBUG); + $state->setDeletedItems($hordeType, $registry->call($hordeType. '/listBy', array('action' => 'delete', 'timestamp' => $refts))); + + Horde::logMessage("SyncML: reading added items from database for $hordeType", __FILE__, __LINE__, PEAR_LOG_DEBUG); + $state->setAddedItems($hordeType, $registry->call($hordeType. '/listBy', array('action' => 'add', 'timestamp' => $refts))); + + $this->_syncDataLoaded = TRUE; + + return count($state->getChangedItems($hordeType)) + + count($state->getDeletedItems($hordeType)) + + count($state->getAddedItems($hordeType)); + } + +}