/**
* Handle incoming SYNC nodes
*
* @param array $collection The current collection array.
*
* @return boolean
*/
protected function _parseSyncCommands(&$collection)
{
// Some broken clients send SYNC_COMMANDS with a synckey of 0.
// This is a violation of the spec, and could lead to all kinds
// of data integrity issues.
if (empty($collection['synckey'])) {
$this->_logger->warn(sprintf('[%s] Attempting a SYNC_COMMANDS, but device failed to send synckey. Ignoring.', $this->_procid));
}
if (empty($collection['class'])) {
$collection['class'] = $this->_collections->getCollectionClass($collection['id']);
}
if (empty($collection['serverid'])) {
try {
$collection['serverid'] = $this->_collections->getBackendIdForFolderUid($collection['id']);
} catch (Horde_ActiveSync_Exception $e) {
$this->_statusCode = self::STATUS_FOLDERSYNC_REQUIRED;
$this->_handleError($colleciton);
return false;
}
}
try {
$this->_collections->initCollectionState($collection);
} catch (Horde_ActiveSync_Exception_StateGone $e) {
$this->_logger->warn(sprintf('[%s] State not found sending STATUS_KEYMISM', $this->_procid));
$this->_statusCode = self::STATUS_KEYMISM;
$this->_handleError($collection);
return false;
} catch (Horde_ActiveSync_Exception_StaleState $e) {
$this->_logger->notice($e->getMessage());
$this->_statusCode = self::STATUS_SERVERERROR;
$this->_handleGlobalSyncError();
return false;
} catch (Horde_ActiveSync_Exception $e) {
$this->_logger->err($e->getMessage());
$this->_statusCode = self::STATUS_SERVERERROR;
$this->_handleGlobalSyncError();
return false;
}
// Configure importer with last state
if (!empty($collection['synckey'])) {
$importer = $this->_activeSync->getImporter();
$importer->init($this->_state, $collection['id'], $collection['conflict']);
}
$nchanges = 0;
while (1) {
// SYNC_MODIFY, SYNC_REMOVE, SYNC_ADD or SYNC_FETCH
$element = $this->_decoder->getElement();
if ($element[Horde_ActiveSync_Wbxml::EN_TYPE] != Horde_ActiveSync_Wbxml::EN_TYPE_STARTTAG) {
$this->_decoder->_ungetElement($element);
break;
}
$nchanges++;
// Only sent during SYNC_MODIFY/SYNC_REMOVE/SYNC_FETCH
if (($element[Horde_ActiveSync_Wbxml::EN_TAG] == Horde_ActiveSync::SYNC_MODIFY || $element[Horde_ActiveSync_Wbxml::EN_TAG] == Horde_ActiveSync::SYNC_REMOVE || $element[Horde_ActiveSync_Wbxml::EN_TAG] == Horde_ActiveSync::SYNC_FETCH) && $this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_SERVERENTRYID)) {
$serverid = $this->_decoder->getElementContent();
// Work around broken clients (Blackberry) that can send empty
// $serverid values as a single empty <SYNC_SERVERENTRYID /> tag.
if ($serverid !== false && !$this->_decoder->getElementEndTag()) {
$this->_statusCode = self::STATUS_PROTERROR;
$this->_handleGlobalSyncError();
$this->_logger->err('Parsing Error - expecting </SYNC_SERVERENTRYID>');
return false;
}
} else {
$serverid = false;
}
// This tag is only sent here during > 12.1 and SYNC_ADD requests...
// and it's not even sent by all clients. Parse it if it's there,
// ignore it if not.
if ($this->_activeSync->device->version > Horde_ActiveSync::VERSION_TWELVEONE && $element[Horde_ActiveSync_Wbxml::EN_TAG] == Horde_ActiveSync::SYNC_ADD && $this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_FOLDERTYPE)) {
$collection['class'] = $this->_decoder->getElementContent();
if (!$this->_decoder->getElementEndTag()) {
$this->_statusCode = self::STATUS_PROTERROR;
$this->_handleGlobalSyncError();
$this->_logger->err('Parsing Error - expecting </SYNC_FOLDERTYPE>');
return false;
}
}
// Only sent during SYNC_ADD
if ($element[Horde_ActiveSync_Wbxml::EN_TAG] == Horde_ActiveSync::SYNC_ADD && $this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_CLIENTENTRYID)) {
$clientid = $this->_decoder->getElementContent();
if (!$this->_decoder->getElementEndTag()) {
$this->_statusCode = self::STATUS_PROTERROR;
$this->_handleGlobalSyncError();
$this->_logger->err('Parsing Error - expecting </SYNC_CLIENTENTRYID>');
return false;
}
} else {
$clientid = false;
}
// Create Message object from messages passed from client.
// Only passed during SYNC_ADD or SYNC_MODIFY
if (($element[Horde_ActiveSync_Wbxml::EN_TAG] == Horde_ActiveSync::SYNC_ADD || $element[Horde_ActiveSync_Wbxml::EN_TAG] == Horde_ActiveSync::SYNC_MODIFY) && $this->_decoder->getElementStartTag(Horde_ActiveSync::SYNC_DATA)) {
switch ($collection['class']) {
case Horde_ActiveSync::CLASS_EMAIL:
$appdata = Horde_ActiveSync::messageFactory('Mail');
$appdata->decodeStream($this->_decoder);
break;
case Horde_ActiveSync::CLASS_CONTACTS:
$appdata = Horde_ActiveSync::messageFactory('Contact');
$appdata->decodeStream($this->_decoder);
break;
case Horde_ActiveSync::CLASS_CALENDAR:
$appdata = Horde_ActiveSync::messageFactory('Appointment');
$appdata->decodeStream($this->_decoder);
break;
case Horde_ActiveSync::CLASS_TASKS:
$appdata = Horde_ActiveSync::messageFactory('Task');
$appdata->decodeStream($this->_decoder);
break;
case Horde_ActiveSync::CLASS_NOTES:
$appdata = Horde_ActiveSync::messageFactory('Note');
$appdata->decodeStream($this->_decoder);
break;
case Horde_ActiveSync::CLASS_SMS:
$appdata = Horde_ActiveSync::messageFactory('Mail');
$appdata->decodeStream($this->_decoder);
break;
}
if (!$this->_decoder->getElementEndTag()) {
// End application data
$this->_statusCode = self::STATUS_PROTERROR;
$this->_handleGlobalSyncError();
return false;
}
}
if (!empty($collection['synckey'])) {
switch ($element[Horde_ActiveSync_Wbxml::EN_TAG]) {
case Horde_ActiveSync::SYNC_MODIFY:
if (isset($appdata)) {
$id = $importer->importMessageChange($serverid, $appdata, $this->_device, false);
if ($id && !is_array($id)) {
$collection['importedchanges'] = true;
} elseif (is_array($id)) {
$collection['importfailures'][$id[0]] = $id[1];
}
}
break;
case Horde_ActiveSync::SYNC_ADD:
if (isset($appdata)) {
$id = $importer->importMessageChange(false, $appdata, $this->_device, $clientid, $collection['class']);
if ($clientid && $id && !is_array($id)) {
$collection['clientids'][$clientid] = $id;
$collection['importedchanges'] = true;
} elseif (!$id || is_array($id)) {
$collection['clientids'][$clientid] = false;
}
}
break;
case Horde_ActiveSync::SYNC_REMOVE:
// Work around broken clients that send empty $serverid.
if ($serverid) {
$collection['removes'][] = $serverid;
}
break;
case Horde_ActiveSync::SYNC_FETCH:
$collection['fetchids'][] = $serverid;
break;
}
}
if (!$this->_decoder->getElementEndTag()) {
$this->_statusCode = self::STATUS_PROTERROR;
$this->_handleGlobalSyncError();
$this->_logger->err('Parsing error');
return false;
}
}
// Do all the SYNC_REMOVE requests at once
if (!empty($collection['removes']) && !empty($collection['synckey'])) {
if (!empty($collection['deletesasmoves']) && ($folderid = $this->_driver->getWasteBasket($collection['class']))) {
$results = $importer->importMessageMove($collection['removes'], $folderid);
} else {
$results = $importer->importMessageDeletion($collection['removes'], $collection['class']);
if (is_array($results)) {
$results['results'] = $results;
$results['missing'] = array_diff($collection['removes'], $results['results']);
}
}
if (!empty($results['missing'])) {
$collection['missing'] = $results['missing'];
}
unset($collection['removes']);
$collection['importedchanges'] = true;
}
$this->_logger->info(sprintf('[%s] Processed %d incoming changes', $this->_procid, $nchanges));
if (!$this->_decoder->getElementEndTag()) {
// end commands
$this->_statusCode = self::STATUS_PROTERROR;
$this->_handleGlobalSyncError();
$this->_logger->err('PARSING ERROR');
return false;
}
return true;
}