diff --git a/phpgwapi/inc/class.contenthistory.inc.php b/phpgwapi/inc/class.contenthistory.inc.php new file mode 100644 index 0000000000..85a1dcb9ea --- /dev/null +++ b/phpgwapi/inc/class.contenthistory.inc.php @@ -0,0 +1,199 @@ +db + */ + var $db; + + function contenthistory() + { + $this->db = clone($GLOBALS['egw']->db); + $this->db->set_app('phpgwapi'); + $this->table = 'egw_api_content_history'; + } + + /** + * mark mapping as expired + * + * mark a mapping from externel to internal id as expired, when + * a egw entry gets deleted + * + * @param $_appName string the appname example: infolog_notes + * @param $_id int the internal egwapp content id + * @return bool + */ + function expireMapping($_appName, $_id) + { + $where = array ( + 'map_guid' => $GLOBALS['egw']->common->generate_uid($_appName, $_id), + ); + + $newData = array ( + 'map_expired' => 1, + ); + + if(!$this->db->update('egw_contentmap',$newData,$where,__LINE__,__FILE__)) + return FALSE; + else + return TRUE; + } + + /** + * get the timestamp for action + * + * find which content changed since $_ts for application $_appName + * + * @param $_appName string the appname example: infolog_notes + * @param $_action string can be modify, add or delete + * @param $_ts string timestamp where to start searching from + * @return array containing the global UID's + */ + function getHistory($_appName, $_action, $_ts) + { + $query = "select sync_guid from egw_api_content_history where sync_appname = '".$this->db->db_addslashes($_appName)."' and "; + + switch($_action) + { + case 'modify': + $query .= "sync_modified > '".$this->db->to_timestamp($_ts)."' and sync_deleted is null"; + break; + case 'delete': + $query .= "sync_deleted > '".$this->db->to_timestamp($_ts)."'"; + break; + case 'add': + $query .= "sync_added > '".$this->db->to_timestamp($_ts)."' and sync_deleted is null and sync_modified is null"; + break; + default: + // no valid $_action set + return array(); + break; + } + #Horde::logMessage("SymcML: egwcontactssync $query", __FILE__, __LINE__, PEAR_LOG_DEBUG); + $this->db->query($query, __LINE__, __FILE__); + + $guidList = array(); + + while($this->db->next_record()) + { + $guidList[] = $this->db->f('sync_guid'); + } + + return $guidList; + } + + /** + * when got a entry last added/modified/deleted + * + * @param $_guid string the global uid of the entry + * @param $_action string can be add, delete or modify + * @return string the last timestamp + */ + function getTSforAction($_guid, $_action) + { + $where = array ( + 'sync_guid' => $_guid, + ); + + $this->db->select($this->table,array('sync_added','sync_modified','sync_deleted'),$where,__LINE__,__FILE__); + if($this->db->next_record()) + { + switch($_action) + { + case 'add': + return $this->db->from_timestamp($this->db->f('sync_added')); + break; + case 'delete': + return $this->db->from_timestamp($this->db->f('sync_deleted')); + break; + case 'modify': + return $this->db->from_timestamp($this->db->f('sync_modified')); + break; + default: + return false; + } + } + + return false; + } + + /** + * update a timestamp for action + * + * @param $_appName string the appname example: infolog_notes + * @param $_id int the app internal content id + * @param $_action string can be modify, add or delete + * @param $_ts string timestamp where to start searching from + * @return boolean returns allways true + */ + function updateTimeStamp($_appName, $_id, $_action, $_ts) + { + $_ts = $this->db->to_timestamp($_ts); + + $newData = array ( + 'sync_appname' => $_appName, + 'sync_contentid' => $_id, + 'sync_added' => $_ts, + 'sync_guid' => $GLOBALS['egw']->common->generate_uid($_appName, $_id), + 'sync_changedby' => $GLOBALS['egw_info']['user']['account_id'], + ); + + switch($_action) + { + case 'add': + $this->db->insert($this->table,$newData,array(),__LINE__,__FILE__); + break; + + case 'modify': + case 'delete': + // first check that this entry got ever added to database already + $where = array ( + 'sync_appname' => $_appName, + 'sync_contentid' => $_id, + ); + + $this->db->select($this->table,'sync_contentid',$where,__LINE__,__FILE__); + if(!$this->db->next_record()) + { + $this->db->insert($this->table,$newData,array(),__LINE__,__FILE__); + } + + #$this->db->select($this->table,'sync_added',$where,__LINE__,__FILE__); + #$this->db->next_record(); + #$syncAdded = $this->db->f('sync_added'); + + // now update the time stamp + $newData = array ( + 'sync_changedby' => $GLOBALS['egw_info']['user']['account_id'], + $_action == 'modify' ? 'sync_modified' : 'sync_deleted' => $_ts , + # 'sync_added' => $syncAdded, + ); + $this->db->update($this->table, $newData, $where,__LINE__,__FILE__); + break; + } + + return true; + } + } +?> diff --git a/phpgwapi/inc/horde/Horde/SyncML/Sync/SlowSync.php b/phpgwapi/inc/horde/Horde/SyncML/Sync/SlowSync.php new file mode 100644 index 0000000000..9798adc563 --- /dev/null +++ b/phpgwapi/inc/horde/Horde/SyncML/Sync/SlowSync.php @@ -0,0 +1,241 @@ + + * + * 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_Sync_SlowSync extends Horde_SyncML_Sync_TwoWaySync { + + function handleSync($currentCmdID, $hordeType, $syncType,&$output, $refts) + { + global $registry; + + $history = $GLOBALS['phpgw']->contenthistory; + $state = &$_SESSION['SyncML.state']; + + $adds = &$state->getAddedItems($hordeType); + + #if($adds === FALSE) + #{ + # Horde::logMessage("SyncML: reading added items from database", __FILE__, __LINE__, PEAR_LOG_DEBUG); + # $state->setAddedItems($hordeType, $registry->call($hordeType. '/list', array())); + # $adds = &$state->getAddedItems($hordeType); + #} + + Horde::logMessage("SyncML: ".count($adds). ' added items found for '.$hordeType , __FILE__, __LINE__, PEAR_LOG_DEBUG); + $serverAnchorNext = $state->getServerAnchorNext($syncType); + $counter = 0; + + while($guid = array_shift($adds)) + { + #$guid_ts = max($history->getTSforAction($guid, 'add'),$history->getTSforAction($guid, 'modify')); + $sync_ts = $state->getChangeTS($syncType, $guid); + #Horde::logMessage("SyncML: slowsync timestamp add: $guid sync_ts: $sync_ts anchorNext: ". $serverAnchorNext.' / '.time(), __FILE__, __LINE__, PEAR_LOG_DEBUG); + // $sync_ts it got synced from client to server someone + // $sync_ts >= $serverAnchorNext it got synced from client to server in this sync package already + if ($sync_ts && $sync_ts >= $serverAnchorNext) { + // Change was done by us upon request of client. + // Don't mirror that back to the client. + //Horde::logMessage("SyncML: slowsync add: $guid ignored, came from client", __FILE__, __LINE__, PEAR_LOG_DEBUG); + continue; + } + + #$locid = $state->getLocID($syncType, $guid); + + // Create an Add request for client. +# LK $contentType = $state->getPreferedContentTypeClient($syncType); + + $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)); + #Horde::logMessage("SyncML: slowsync add to server $c", __FILE__, __LINE__, PEAR_LOG_DEBUG); + if (!is_a($c, 'PEAR_Error')) { + // Item in history but not in database. Strange, but + // can happen. + #LK $cmd->setContent($state->convertServer2Client($c, $contentType)); + $cmd->setContent($c); + $cmd->setContentType($contentType['ContentType']); + $cmd->setSourceURI($guid); + $currentCmdID = $cmd->outputCommand($currentCmdID, $output, 'Add'); + $state->log('Server-Add'); + + // return if we have to much data + #Horde::logMessage("SyncML: ".' checking hordetype '.$hordeType , __FILE__, __LINE__, PEAR_LOG_DEBUG); + 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; + } + + /** + * Here's where the actual processing of a client-sent Sync + * Command takes place. Entries are added or replaced + * from the server database by using Horde API (Registry) calls. + */ + function runSyncCommand(&$command) { + #Horde::logMessage('SyncML: content type is ' . $command->getContentType() .' moreData '. $command->_moreData, __FILE__, __LINE__, PEAR_LOG_DEBUG); + global $registry; + + $history = $GLOBALS['egw']->contenthistory; + + $state = &$_SESSION['SyncML.state']; + + if(isset($state->_moreData['luid'])) { + if(($command->_luid == $state->_moreData['luid'])) { + Horde::logMessage('SyncML: got next moreData chunk '.$command->getContent(), __FILE__, __LINE__, PEAR_LOG_DEBUG); + $lastChunks = implode('',$state->_moreData['chunks']); + $command->_content = $lastChunks.$command->_content; + $stringlen1 = strlen($lastChunks); + $stringlen2 = strlen($command->_content); + + if(!$command->_moreData && strlen($command->_content) != $state->_moreData['contentSize']) { + $command->_status = RESPONSE_SIZE_MISMATCH; + $state->_moreData = array(); + + return; + } elseif(!$command->_moreData && strlen($command->_content) == $state->_moreData['contentSize']) { + $state->_moreData = array(); + Horde::logMessage('SyncML: chunk ended successful type is ' . $command->getContentType() .' content is '. $command->getContent(), __FILE__, __LINE__, PEAR_LOG_DEBUG); + } + } else { + // alert 223 needed too + #$command->_status = ALERT_NO_END_OF_DATA; + + $state->_moreData = array(); + + return; + } + } + + // don't add/replace the data currently, they are not yet complete + if($command->_moreData == TRUE) { + $state->_moreData['chunks'][] = $command->_content; + $state->_moreData['luid'] = $command->_luid; + + // gets only set with the first chunk of data + if(isset($command->_contentSize)) + $state->_moreData['contentSize'] = $command->_contentSize; + + $command->_status = RESPONSE_CHUNKED_ITEM_ACCEPTED_AND_BUFFERED; + Horde::logMessage('SyncML: added moreData chunk '.$command->getContent(), __FILE__, __LINE__, PEAR_LOG_DEBUG); + + return; + } + + $hordeType = $type = $this->_targetLocURI; + // remove the './' from the beginning + $hordeType = str_replace('./','',$hordeType); + + $syncElementItems = $command->getSyncElementItems(); + + foreach($syncElementItems as $syncItem) { + if(!$contentType = $syncItem->getContentType()) { + $contentType = $state->getPreferedContentType($type); + } + + if ($this->_targetLocURI == 'calendar' && strpos($syncItem->getContent(), 'BEGIN:VTODO') !== false) { + $hordeType = 'tasks'; + } + + $guid = false; + # if (is_a($command, 'Horde_SyncML_Command_Sync_Add')) { + # $guid = $registry->call($hordeType . '/import', + # array($state->convertClient2Server($syncItem->getContent(), $contentType), $contentType)); + # if (!is_a($guid, 'PEAR_Error')) { + # $ts = $history->getTSforAction($guid, 'add'); + # $state->setUID($type, $syncItem->getLocURI(), $guid, $ts); + # $state->log("Client-Add"); + # #Horde::logMessage('SyncML: added client entry as ' . $guid, __FILE__, __LINE__, PEAR_LOG_DEBUG); + # } else { + # $state->log("Client-AddFailure"); + # Horde::logMessage('SyncML: Error in adding client entry:' . $guid->message, __FILE__, __LINE__, PEAR_LOG_ERR); + # } + # } elseif (is_a($command, 'Horde_SyncML_Command_Sync_Replace')) { + #$guid = $state->getGlobalUID($type, $syncItem->getLocURI()); + $guid = $registry->call($hordeType . '/search', + array($state->convertClient2Server($syncItem->getContent(), $contentType), $contentType)); + Horde::logMessage('SyncML: found guid ' . $guid , __FILE__, __LINE__, PEAR_LOG_DEBUG); + $ok = false; + if ($guid) { + #Horde::logMessage('SyncML: locuri'. $syncItem->getLocURI() . ' guid ' . $guid , __FILE__, __LINE__, PEAR_LOG_ERR); + // Entry exists: replace current one. + $ok = $registry->call($hordeType . '/replace', + array($guid, $state->convertClient2Server($syncItem->getContent(), $contentType), $contentType)); + if (!is_a($ok, 'PEAR_Error')) { + $ts = $history->getTSforAction($guid, 'modify'); + $state->setUID($type, $syncItem->getLocURI(), $guid, $ts); + #Horde::logMessage('SyncML: replaced entry due to client request guid: '. $guid .' LocURI: '. $syncItem->getLocURI() .' ts: '. $ts, __FILE__, __LINE__, PEAR_LOG_DEBUG); + $state->log("Client-Replace"); + $ok = true; + } else { + // Entry may have been deleted; try adding it. + $ok = false; + } + } + + if (!$ok) { + // Entry does not exist in map or database: add a new + // one. + Horde::logMessage('SyncML: try to add contentype ' . $contentType .' to '. $hordeType, __FILE__, __LINE__, PEAR_LOG_DEBUG); + $guid = $registry->call($hordeType . '/import', + array($state->convertClient2Server($syncItem->getContent(), $contentType), $contentType)); + if (!is_a($guid, 'PEAR_Error')) { + $ts = $history->getTSforAction($guid, 'add'); + $state->setUID($type, $syncItem->getLocURI(), $guid, $ts); + $state->log("Client-AddReplace"); + Horde::logMessage('SyncML: r/ added client entry as ' . $guid, __FILE__, __LINE__, PEAR_LOG_DEBUG); + } else { + Horde::logMessage('SyncML: Error in replacing/add client entry:' . $guid->message, __FILE__, __LINE__, PEAR_LOG_ERR); + $state->log("Client-AddFailure"); + } + } + # } + } + + return true; + } + + + function loadData() + { + global $registry; + + $state = &$_SESSION['SyncML.state']; + $syncType = $this->_targetLocURI; + $hordeType = str_replace('./','',$syncType); + + Horde::logMessage("SyncML: reading added items from database for $hordeType", __FILE__, __LINE__, PEAR_LOG_DEBUG); + $state->setAddedItems($hordeType, $registry->call($hordeType. '/list', array())); + $adds = &$state->getAddedItems($hordeType); + $this->_syncDataLoaded = TRUE; + + return count($state->getAddedItems($hordeType)); + } + +} diff --git a/phpgwapi/inc/horde/config/conf.php b/phpgwapi/inc/horde/config/conf.php new file mode 100644 index 0000000000..188b15ce28 --- /dev/null +++ b/phpgwapi/inc/horde/config/conf.php @@ -0,0 +1,44 @@ +