forked from extern/egroupware
507 lines
18 KiB
PHP
507 lines
18 KiB
PHP
<?php
|
|
/**
|
|
* eGroupWare - SyncML based on Horde 3
|
|
*
|
|
* The Horde_SyncML_Alert class provides a SyncML implementation of
|
|
* the Alert command as defined in SyncML Representation Protocol,
|
|
* version 1.1 5.5.2.
|
|
*
|
|
*
|
|
* Using the PEAR Log class (which need to be installed!)
|
|
*
|
|
* @link http://www.egroupware.org
|
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
|
* @package api
|
|
* @subpackage horde
|
|
* @author Anthony Mills <amills@pyramid6.com>
|
|
* @author Joerg Lehrke <jlehrke@noc.de>
|
|
* @copyright (c) The Horde Project (http://www.horde.org/)
|
|
* @version $Id$
|
|
*/
|
|
include_once 'Horde/SyncML/State_egw.php';
|
|
include_once 'Horde/SyncML/Command.php';
|
|
|
|
class Horde_SyncML_Command_Alert extends Horde_SyncML_Command {
|
|
|
|
/**
|
|
* Name of the command.
|
|
*
|
|
* @var string
|
|
*/
|
|
var $_cmdName = 'Alert';
|
|
|
|
/**
|
|
* The alert type. Should be one of the ALERT_* constants.
|
|
*
|
|
* @var integer
|
|
*/
|
|
var $_alert;
|
|
|
|
/**
|
|
* Source database of the Alert command.
|
|
*
|
|
* @var string
|
|
*/
|
|
var $_sourceLocURI;
|
|
|
|
/**
|
|
* Target database of the Alert command.
|
|
*
|
|
* @var string
|
|
*/
|
|
var $_targetLocURI;
|
|
|
|
/**
|
|
* Optional parameter for the Target database.
|
|
*
|
|
* @var string
|
|
*/
|
|
var $_targetLocURIParameters;
|
|
|
|
/**
|
|
* The current time this synchronization happens, from the <Meta><Next>
|
|
* element.
|
|
*
|
|
* @var string
|
|
*/
|
|
var $_metaAnchorNext;
|
|
|
|
/**
|
|
* The last time when synchronization happened, from the <Meta><Last>
|
|
* element.
|
|
*
|
|
* @var integer
|
|
*/
|
|
var $_metaAnchorLast;
|
|
|
|
/**
|
|
* The filter expression the client provided
|
|
* (e.g. the time range for calendar synchronization)
|
|
*
|
|
* @var string
|
|
*/
|
|
var $_filterExpression = '';
|
|
|
|
|
|
/**
|
|
* Creates a new instance of Alert.
|
|
*/
|
|
function Horde_SyncML_Command_Alert($alert = null)
|
|
{
|
|
if ($alert != null) {
|
|
$this->_alert = $alert;
|
|
}
|
|
}
|
|
|
|
function output($currentCmdID, &$output)
|
|
{
|
|
global $registry;
|
|
|
|
$attrs = array();
|
|
|
|
$state = &$_SESSION['SyncML.state'];
|
|
|
|
// Handle unauthorized first.
|
|
if (!$state->isAuthorized()) {
|
|
$status = new Horde_SyncML_Command_Status(RESPONSE_INVALID_CREDENTIALS, 'Alert');
|
|
$status->setCmdRef($this->_cmdID);
|
|
$currentCmdID = $status->output($currentCmdID, $output);
|
|
return $currentCmdID;
|
|
}
|
|
|
|
$type = $this->_targetLocURI;
|
|
|
|
$clientAnchorNext = $this->_metaAnchorNext;
|
|
|
|
if ($this->_alert == ALERT_TWO_WAY ||
|
|
$this->_alert == ALERT_ONE_WAY_FROM_CLIENT ||
|
|
$this->_alert == ALERT_ONE_WAY_FROM_SERVER) {
|
|
// Check if we have information about previous sync.
|
|
$info = $state->getSyncSummary($this->_targetLocURI);
|
|
if (is_a($info, 'DataTreeObject')) {
|
|
$x = $info->get('ClientAnchor');
|
|
$clientlast = $x[$type];
|
|
$x = $info->get('ServerAnchor');
|
|
$serverAnchorLast = $x[$type];
|
|
} elseif (is_array($info)) {
|
|
$clientlast = $info['ClientAnchor'];
|
|
$serverAnchorLast = $info['ServerAnchor'];
|
|
} else {
|
|
$clientlast = false;
|
|
$serverAnchorLast = 0;
|
|
}
|
|
$state->setServerAnchorLast($type, $serverAnchorLast);
|
|
|
|
if ($clientlast !== false){
|
|
// Info about previous successful sync sessions found.
|
|
Horde::logMessage('SyncML: Previous sync found for target ' . $type
|
|
. '; client timestamp: ' . $clientlast,
|
|
__FILE__, __LINE__, PEAR_LOG_DEBUG);
|
|
|
|
// Check if anchor sent from client matches our own stored
|
|
// data.
|
|
if ($clientlast == $this->_metaAnchorLast) {
|
|
// Last sync anchors matche, TwoWaySync will do.
|
|
$anchormatch = true;
|
|
Horde::logMessage('SyncML: Anchor timestamps match, TwoWaySync possible. Syncing data since '
|
|
. date('Y-m-d H:i:s', $serverAnchorLast),
|
|
__FILE__, __LINE__, PEAR_LOG_DEBUG);
|
|
} else {
|
|
// Server and client have different anchors, enforce
|
|
// SlowSync/RefreshSync
|
|
Horde::logMessage('SyncML: Client requested sync with anchor timestamp '
|
|
. $this->_metaAnchorLast
|
|
. ' but server has recorded timestamp '
|
|
. $clientlast . '. Enforcing SlowSync',
|
|
__FILE__, __LINE__, PEAR_LOG_INFO);
|
|
$anchormatch = false;
|
|
$clientlast = 0;
|
|
}
|
|
} else {
|
|
// No info about previous sync, use SlowSync or RefreshSync.
|
|
Horde::logMessage('SyncML: No info about previous syncs found for device ' .
|
|
$state->getSourceURI() . ' and target ' . $type,
|
|
__FILE__, __LINE__, PEAR_LOG_DEBUG);
|
|
$clientlast = 0;
|
|
$serverAnchorLast = 0;
|
|
$anchormatch = false;
|
|
}
|
|
} else {
|
|
// SlowSync requested, no anchor check required.
|
|
$anchormatch = true;
|
|
}
|
|
|
|
// Determine sync type and status response code.
|
|
Horde::logMessage("SyncML: Alert " . $this->_alert, __FILE__, __LINE__, PEAR_LOG_DEBUG);
|
|
switch ($this->_alert) {
|
|
case ALERT_NEXT_MESSAGE:
|
|
$state->setAlert222Received(true);
|
|
case ALERT_RESULT_ALERT:
|
|
case ALERT_NO_END_OF_DATA:
|
|
// Nothing to do on our side
|
|
$status = new Horde_SyncML_Command_Status(RESPONSE_OK, 'Alert');
|
|
$status->setCmdRef($this->_cmdID);
|
|
if ($this->_sourceLocURI != null) {
|
|
$status->setSourceRef($this->_sourceLocURI);
|
|
}
|
|
if ($this->_targetLocURI != null) {
|
|
$status->setTargetRef((isset($this->_targetLocURIParameters) ? $this->_targetLocURI.'?/'.$this->_targetLocURIParameters : $this->_targetLocURI));
|
|
}
|
|
if ($this->_alert == ALERT_NEXT_MESSAGE) {
|
|
if ($this->_sourceLocURI != null) {
|
|
$status->setItemSourceLocURI($this->_sourceLocURI);
|
|
}
|
|
if ($this->_targetLocURI != null) {
|
|
$status->setItemTargetLocURI(isset($this->_targetLocURIParameters) ? $this->_targetLocURI.'?/'.$this->_targetLocURIParameters : $this->_targetLocURI);
|
|
}
|
|
}
|
|
$currentCmdID = $status->output($currentCmdID, $output);
|
|
return $currentCmdID;
|
|
case ALERT_TWO_WAY:
|
|
if ($anchormatch) {
|
|
$synctype = ALERT_TWO_WAY;
|
|
$response = RESPONSE_OK;
|
|
} else {
|
|
$synctype = ALERT_SLOW_SYNC;
|
|
$response = RESPONSE_REFRESH_REQUIRED;
|
|
}
|
|
break;
|
|
|
|
case ALERT_SLOW_SYNC:
|
|
$synctype = ALERT_SLOW_SYNC;
|
|
$response = $anchormatch ? RESPONSE_OK : RESPONSE_REFRESH_REQUIRED;
|
|
break;
|
|
|
|
case ALERT_ONE_WAY_FROM_CLIENT:
|
|
if ($anchormatch) {
|
|
$synctype = ALERT_ONE_WAY_FROM_CLIENT;
|
|
$response = RESPONSE_OK;
|
|
} else {
|
|
$synctype = ALERT_REFRESH_FROM_CLIENT;
|
|
$response = RESPONSE_REFRESH_REQUIRED;
|
|
}
|
|
break;
|
|
|
|
case ALERT_REFRESH_FROM_CLIENT:
|
|
$synctype = ALERT_REFRESH_FROM_CLIENT;
|
|
$response = $anchormatch ? RESPONSE_OK : RESPONSE_REFRESH_REQUIRED;
|
|
|
|
// We will erase the current server content,
|
|
// then we can add the client's contents.
|
|
|
|
$hordeType = $state->getHordeType($this->_targetLocURI);
|
|
|
|
$state->setTargetURI($this->_targetLocURI);
|
|
$deletes = $state->getClientItems();
|
|
if (is_array($deletes)) {
|
|
foreach ($deletes as $delete) {
|
|
$registry->call($hordeType . '/delete', array($delete));
|
|
}
|
|
Horde::logMessage("SyncML: RefreshFromClient " . count($deletes) . " entries deleted for $hordeType", __FILE__, __LINE__, PEAR_LOG_DEBUG);
|
|
}
|
|
$anchormatch = false;
|
|
break;
|
|
|
|
case ALERT_ONE_WAY_FROM_SERVER:
|
|
if ($anchormatch) {
|
|
$synctype = ALERT_ONE_WAY_FROM_SERVER;
|
|
$response = RESPONSE_OK;
|
|
} else {
|
|
$synctype = ALERT_REFRESH_FROM_SERVER;
|
|
$response = RESPONSE_REFRESH_REQUIRED;
|
|
}
|
|
break;
|
|
|
|
case ALERT_REFRESH_FROM_SERVER:
|
|
$synctype = ALERT_REFRESH_FROM_SERVER;
|
|
$response = $anchormatch ? RESPONSE_OK : RESPONSE_REFRESH_REQUIRED;
|
|
break;
|
|
|
|
case ALERT_RESUME:
|
|
// @TODO: Suspend and Resume is not supported yet
|
|
$synctype = ALERT_SLOW_SYNC;
|
|
$response = RESPONSE_REFRESH_REQUIRED;
|
|
break;
|
|
|
|
default:
|
|
// We can't handle this one
|
|
Horde::logMessage('SyncML: Unknown sync type ' . $this->_alert,
|
|
__FILE__, __LINE__, PEAR_LOG_ERR);
|
|
$status = new Horde_SyncML_Command_Status(RESPONSE_BAD_REQUEST, 'Alert');
|
|
$status->setCmdRef($this->_cmdID);
|
|
if ($this->_sourceLocURI != null) {
|
|
$status->setSourceRef($this->_sourceLocURI);
|
|
}
|
|
if ($this->_targetLocURI != null) {
|
|
$status->setTargetRef((isset($this->_targetLocURIParameters) ? $this->_targetLocURI.'?/'.$this->_targetLocURIParameters : $this->_targetLocURI));
|
|
}
|
|
$currentCmdID = $status->output($currentCmdID, $output);
|
|
return $currentCmdID;
|
|
}
|
|
|
|
// Now set interval to retrieve server changes from, defined by
|
|
// ServerAnchor [Last,Next]
|
|
if ($synctype != ALERT_TWO_WAY &&
|
|
$synctype != ALERT_ONE_WAY_FROM_CLIENT &&
|
|
$synctype != ALERT_ONE_WAY_FROM_SERVER) {
|
|
$serverAnchorLast = 0;
|
|
if (isset($GLOBALS['egw_info']['user']['preferences']['syncml']['slowsync_ignore_map']) &&
|
|
$GLOBALS['egw_info']['user']['preferences']['syncml']['slowsync_ignore_map']
|
|
|| !$anchormatch) {
|
|
// Erase existing map
|
|
$state->removeAllUID($this->_targetLocURI);
|
|
}
|
|
}
|
|
// Now create the actual SyncML_Sync object, if it doesn't exist yet.
|
|
$sync = &$state->getSync($this->_targetLocURI);
|
|
if (!$sync) {
|
|
Horde::logMessage('SyncML: Creating SyncML_Sync object for target '
|
|
. $this->_targetLocURI . '; sync type ' . $synctype,
|
|
__FILE__, __LINE__, PEAR_LOG_DEBUG);
|
|
$sync = &Horde_SyncML_Sync::factory($synctype);
|
|
$state->clearConflictItems($this->_targetLocURI);
|
|
}
|
|
$sync->setTargetLocURI($this->_targetLocURI);
|
|
$sync->setSourceLocURI($this->_sourceLocURI);
|
|
$sync->setLocName($state->getLocName()); // We need it for conflict handling
|
|
$sync->setsyncType($synctype);
|
|
$sync->setFilterExpression($this->_filterExpression);
|
|
$state->setSync($this->_targetLocURI, $sync);
|
|
$hordeType = $state->getHordeType($this->_targetLocURI);
|
|
$changes =& $registry->call($hordeType. '/listBy',
|
|
array('action' => 'modify',
|
|
'timestamp' => $serverAnchorLast,
|
|
'type' => $this->_targetLocURI,
|
|
'filter' => $this->_filterExpression));
|
|
$state->setChangedItems($this->_targetLocURI, $changes);
|
|
|
|
// Store client's Next Anchor in State and
|
|
// set server's Next Anchor. After successful sync
|
|
// this is then written to persistence for negotiation of
|
|
// further syncs.
|
|
$state->setClientAnchorNext($type, $this->_metaAnchorNext);
|
|
$serverAnchorNext = time();
|
|
$state->setServerAnchorNext($type, $serverAnchorNext);
|
|
|
|
$status = new Horde_SyncML_Command_Status($response, 'Alert');
|
|
$status->setCmdRef($this->_cmdID);
|
|
if ($this->_sourceLocURI != null) {
|
|
$status->setSourceRef($this->_sourceLocURI);
|
|
}
|
|
if ($this->_targetLocURI != null) {
|
|
$status->setTargetRef((isset($this->_targetLocURIParameters) ? $this->_targetLocURI.'?/'.$this->_targetLocURIParameters : $this->_targetLocURI));
|
|
}
|
|
|
|
// Mirror Next Anchor from client back to client.
|
|
if (isset($this->_metaAnchorNext)) {
|
|
$status->setItemDataAnchorNext($this->_metaAnchorNext);
|
|
}
|
|
|
|
// Mirror Last Anchor from client back to client.
|
|
if (isset($this->_metaAnchorLast)) {
|
|
$status->setItemDataAnchorLast($this->_metaAnchorLast);
|
|
}
|
|
|
|
$currentCmdID = $status->output($currentCmdID, $output);
|
|
|
|
$output->startElement($state->getURI(), 'Alert', $attrs);
|
|
|
|
$output->startElement($state->getURI(), 'CmdID', $attrs);
|
|
$chars = $currentCmdID;
|
|
$output->characters($chars);
|
|
$output->endElement($state->getURI(), 'CmdID');
|
|
|
|
$output->startElement($state->getURI(), 'Data', $attrs);
|
|
$chars = $synctype;
|
|
$output->characters($chars);
|
|
$output->endElement($state->getURI(), 'Data');
|
|
|
|
$output->startElement($state->getURI(), 'Item', $attrs);
|
|
|
|
if ($this->_sourceLocURI != null) {
|
|
$output->startElement($state->getURI(), 'Target', $attrs);
|
|
$output->startElement($state->getURI(), 'LocURI', $attrs);
|
|
$chars = $this->_sourceLocURI;
|
|
$output->characters($chars);
|
|
$output->endElement($state->getURI(), 'LocURI');
|
|
$output->endElement($state->getURI(), 'Target');
|
|
}
|
|
|
|
if ($this->_targetLocURI != null) {
|
|
$output->startElement($state->getURI(), 'Source', $attrs);
|
|
$output->startElement($state->getURI(), 'LocURI', $attrs);
|
|
$chars = (isset($this->_targetLocURIParameters) ? $this->_targetLocURI.'?/'.$this->_targetLocURIParameters : $this->_targetLocURI);
|
|
$output->characters($chars);
|
|
$output->endElement($state->getURI(), 'LocURI');
|
|
$output->endElement($state->getURI(), 'Source');
|
|
}
|
|
|
|
$output->startElement($state->getURI(), 'Meta', $attrs);
|
|
|
|
$output->startElement($state->getURIMeta(), 'Anchor', $attrs);
|
|
|
|
$output->startElement($state->getURIMeta(), 'Last', $attrs);
|
|
$chars = $state->getServerAnchorLast($type);
|
|
$output->characters($chars);
|
|
$output->endElement($state->getURIMeta(), 'Last');
|
|
|
|
$output->startElement($state->getURIMeta(), 'Next', $attrs);
|
|
$chars = $state->getServerAnchorNext($type);
|
|
$output->characters($chars);
|
|
$output->endElement($state->getURIMeta(), 'Next');
|
|
|
|
$output->endElement($state->getURIMeta(), 'Anchor');
|
|
$output->endElement($state->getURI(), 'Meta');
|
|
$output->endElement($state->getURI(), 'Item');
|
|
$output->endElement($state->getURI(), 'Alert');
|
|
|
|
// Final packet of this message
|
|
$state->_sendFinal = true;
|
|
|
|
$currentCmdID++;
|
|
|
|
if ($state->_devinfoRequested == false &&
|
|
$this->_sourceLocURI != null &&
|
|
is_a($state->getPreferedContentTypeClient($this->_sourceLocURI), 'PEAR_Error')) {
|
|
|
|
Horde::logMessage("SyncML: PreferedContentTypeClient missing, sending <Get>", __FILE__, __LINE__, PEAR_LOG_DEBUG);
|
|
|
|
$output->startElement($state->getURI(), 'Get', $attrs);
|
|
|
|
$output->startElement($state->getURI(), 'CmdID', $attrs);
|
|
$output->characters($currentCmdID);
|
|
$currentCmdID++;
|
|
$output->endElement($state->getURI(), 'CmdID');
|
|
|
|
$output->startElement($state->getURI(), 'Meta', $attrs);
|
|
$output->startElement($state->getURIMeta(), 'Type', $attrs);
|
|
if (is_a($output, 'XML_WBXML_Encoder')) {
|
|
$output->characters('application/vnd.syncml-devinf+wbxml');
|
|
} else {
|
|
$output->characters('application/vnd.syncml-devinf+xml');
|
|
}
|
|
$output->endElement($state->getURIMeta(), 'Type');
|
|
$output->endElement($state->getURI(), 'Meta');
|
|
|
|
$output->startElement($state->getURI(), 'Item', $attrs);
|
|
$output->startElement($state->getURI(), 'Target', $attrs);
|
|
$output->startElement($state->getURI(), 'LocURI', $attrs);
|
|
if ($state->getVersion() == 2) {
|
|
$output->characters('./devinf12');
|
|
} elseif ($state->getVersion() == 1) {
|
|
$output->characters('./devinf11');
|
|
} else {
|
|
$output->characters('./devinf10');
|
|
}
|
|
$output->endElement($state->getURI(), 'LocURI');
|
|
$output->endElement($state->getURI(), 'Target');
|
|
$output->endElement($state->getURI(), 'Item');
|
|
|
|
$output->endElement($state->getURI(), 'Get');
|
|
|
|
$state->_devinfoRequested = true;
|
|
}
|
|
return $currentCmdID;
|
|
}
|
|
|
|
/**
|
|
* End element handler for the XML parser, delegated from
|
|
* SyncML_ContentHandler::endElement().
|
|
*
|
|
* @param string $uri The namespace URI of the element.
|
|
* @param string $element The element tag name.
|
|
*/
|
|
function endElement($uri, $element)
|
|
{
|
|
switch (count($this->_stack)) {
|
|
case 2:
|
|
if ($element == 'Data') {
|
|
$this->_alert = intval(trim($this->_chars));
|
|
}
|
|
break;
|
|
|
|
case 4:
|
|
if ($element == 'LocURI') {
|
|
switch ($this->_stack[2]) {
|
|
case 'Source':
|
|
$this->_sourceLocURI = trim($this->_chars);
|
|
break;
|
|
case 'Target':
|
|
$targetLocURIData = explode('?/',trim($this->_chars));
|
|
|
|
$this->_targetLocURI = $targetLocURIData[0];
|
|
|
|
if (isset($targetLocURIData[1])) {
|
|
$this->_targetLocURIParameters = $targetLocURIData[1];
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 5:
|
|
switch ($element) {
|
|
case 'Next':
|
|
$this->_metaAnchorNext = trim($this->_chars);
|
|
break;
|
|
case 'Last':
|
|
$this->_metaAnchorLast = trim($this->_chars);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 7:
|
|
if ($element == 'Data'
|
|
&& $this->_stack[2] == 'Target'
|
|
&& $this->_stack[3] == 'Filter'
|
|
&& $this->_stack[4] == 'Record'
|
|
&& $this->_stack[5] == 'Item') {
|
|
$this->_filterExpression = trim($this->_chars);
|
|
}
|
|
break;
|
|
}
|
|
parent::endElement($uri, $element);
|
|
}
|
|
|
|
}
|