Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2529965
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
163 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/lib/ext/Syncroton/Backend/ABackend.php b/lib/ext/Syncroton/Backend/ABackend.php
index 00869e4..eb5e605 100644
--- a/lib/ext/Syncroton/Backend/ABackend.php
+++ b/lib/ext/Syncroton/Backend/ABackend.php
@@ -1,195 +1,211 @@
<?php
/**
* Syncroton
*
* @package Syncroton
* @subpackage Backend
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2012-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync Sync command
*
* @package Syncroton
* @subpackage Backend
*/
abstract class Syncroton_Backend_ABackend implements Syncroton_Backend_IBackend
{
/**
* the database adapter
*
* @var Zend_Db_Adapter_Abstract
*/
protected $_db;
protected $_tablePrefix;
- protected $_tableName;
-
+ protected $_tableName;
+
protected $_modelClassName;
protected $_modelInterfaceName;
/**
* the constructor
*
* @param Zend_Db_Adapter_Abstract $_db
* @param string $_tablePrefix
*/
public function __construct(Zend_Db_Adapter_Abstract $_db, $_tablePrefix = 'Syncroton_')
{
$this->_db = $_db;
$this->_tablePrefix = $_tablePrefix;
}
/**
* create new device
*
- * @param Syncroton_Model_IDevice $_device
- * @return Syncroton_Model_IDevice
+ * @param Syncroton_Model_AEntry $model
+ * @return Syncroton_Model_AEntry
*/
public function create($model)
{
if (! $model instanceof $this->_modelInterfaceName) {
- throw new InvalidArgumentException('$model must be instanace of ' . $this->_modelInterfaceName);
+ throw new InvalidArgumentException('$model must be instance of ' . $this->_modelInterfaceName);
}
$data = $this->_convertModelToArray($model);
$data['id'] = sha1(mt_rand(). microtime());
-
+
$this->_db->insert($this->_tablePrefix . $this->_tableName, $data);
return $this->get($data['id']);
}
/**
* convert iteratable object to array
*
- * @param unknown $model
+ * @param Syncroton_Model_AEntry $model
* @return array
*/
protected function _convertModelToArray($model)
{
$data = array();
- foreach ($model as $key => $value) {
- if ($value instanceof DateTime) {
- $value = $value->format('Y-m-d H:i:s');
- } elseif (is_object($value) && isset($value->id)) {
- $value = $value->id;
- }
-
- $data[$this->_fromCamelCase($key)] = $value;
+ foreach ($model as $key => $value) {
+ if ($value instanceof DateTime) {
+ $value = $value->format('Y-m-d H:i:s');
+ } elseif (is_object($value) && isset($value->id)) {
+ $value = $value->id;
+ }
+
+ $data[$this->_fromCamelCase($key)] = $value;
}
- return $data;
+ return $data;
}
/**
* @param string $_id
* @throws Syncroton_Exception_NotFound
* @return Syncroton_Model_IDevice
*/
public function get($id)
{
$id = $id instanceof $this->_modelInterfaceName ? $id->id : $id;
+ if (empty($id)) {
+ throw new Syncroton_Exception_NotFound('id can not be empty');
+ }
+
$select = $this->_db->select()
->from($this->_tablePrefix . $this->_tableName)
->where('id = ?', $id);
$stmt = $this->_db->query($select);
$data = $stmt->fetch();
$stmt = null; # see https://bugs.php.net/bug.php?id=44081
if ($data === false) {
throw new Syncroton_Exception_NotFound('id not found');
}
return $this->_getObject($data);
}
/**
* convert array to object
*
* @param array $data
* @return object
*/
protected function _getObject($data)
{
- foreach ($data as $key => $value) {
+ foreach ($data as $key => $value) {
unset($data[$key]);
-
- if (!empty($value) && preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/', $value)) { # 2012-08-12 07:43:26
- $value = new DateTime($value, new DateTimeZone('utc'));
+
+ if (!empty($value) && preg_match('/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/', $value)) { # 2012-08-12 07:43:26
+ $value = new DateTime($value, new DateTimeZone('utc'));
}
-
- $data[$this->_toCamelCase($key, false)] = $value;
- }
-
- return new $this->_modelClassName($data);
+
+ $data[$this->_toCamelCase($key, false)] = $value;
+ }
+
+ return new $this->_modelClassName($data);
}
/**
* (non-PHPdoc)
* @see Syncroton_Backend_IBackend::delete()
*/
public function delete($id)
{
$id = $id instanceof $this->_modelInterfaceName ? $id->id : $id;
$result = $this->_db->delete($this->_tablePrefix . $this->_tableName, array('id = ?' => $id));
return (bool) $result;
}
/**
* (non-PHPdoc)
* @see Syncroton_Backend_IBackend::update()
*/
public function update($model)
{
- if (! $model instanceof $this->_modelInterfaceName) {
- throw new InvalidArgumentException('$model must be instanace of ' . $this->_modelInterfaceName);
- }
+ if (! $model instanceof $this->_modelInterfaceName) {
+ throw new InvalidArgumentException('$model must be instanace of ' . $this->_modelInterfaceName);
+ }
$data = $this->_convertModelToArray($model);
$this->_db->update($this->_tablePrefix . $this->_tableName, $data, array(
'id = ?' => $model->id
));
return $this->get($model->id);
}
+ /**
+ * Returns list of user accounts
+ *
+ * @param Syncroton_Model_Device $device The device
+ *
+ * @return array List of Syncroton_Model_Account objects
+ */
+ public function userAccounts($device)
+ {
+ return array();
+ }
+
/**
* convert from camelCase to camel_case
* @param string $string
* @return string
*/
protected function _fromCamelCase($string)
{
$string = lcfirst($string);
return preg_replace_callback('/([A-Z])/', function ($string) {return '_' . strtolower($string[0]);}, $string);
}
/**
* convert from camel_case to camelCase
*
* @param string $string
* @param bool $ucFirst
* @return string
*/
protected function _toCamelCase($string, $ucFirst = true)
{
if ($ucFirst === true) {
$string = ucfirst($string);
}
return preg_replace_callback('/_([a-z])/', function ($string) {return strtoupper($string[1]);}, $string);
}
}
diff --git a/lib/ext/Syncroton/Command/FolderCreate.php b/lib/ext/Syncroton/Command/FolderCreate.php
index f1764d5..1654b34 100644
--- a/lib/ext/Syncroton/Command/FolderCreate.php
+++ b/lib/ext/Syncroton/Command/FolderCreate.php
@@ -1,115 +1,117 @@
<?php
/**
* Syncroton
*
* @package Syncroton
* @subpackage Command
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2008-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync FolderSync command
*
* @package Syncroton
* @subpackage Command
*/
class Syncroton_Command_FolderCreate extends Syncroton_Command_Wbxml
{
protected $_defaultNameSpace = 'uri:FolderHierarchy';
protected $_documentElement = 'FolderCreate';
/**
*
* @var Syncroton_Model_Folder
*/
protected $_folder;
/**
* parse FolderCreate request
*
*/
public function handle()
{
$xml = simplexml_import_dom($this->_requestBody);
$syncKey = (int)$xml->SyncKey;
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " synckey is $syncKey");
if (!($this->_syncState = $this->_syncStateBackend->validate($this->_device, 'FolderSync', $syncKey)) instanceof Syncroton_Model_SyncState) {
return;
}
$folder = new Syncroton_Model_Folder($xml);
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " parentId: {$folder->parentId} displayName: {$folder->displayName}");
switch($folder->type) {
case Syncroton_Command_FolderSync::FOLDERTYPE_CALENDAR_USER_CREATED:
$folder->class = Syncroton_Data_Factory::CLASS_CALENDAR;
break;
case Syncroton_Command_FolderSync::FOLDERTYPE_CONTACT_USER_CREATED:
$folder->class = Syncroton_Data_Factory::CLASS_CONTACTS;
break;
case Syncroton_Command_FolderSync::FOLDERTYPE_MAIL_USER_CREATED:
$folder->class = Syncroton_Data_Factory::CLASS_EMAIL;
break;
case Syncroton_Command_FolderSync::FOLDERTYPE_NOTE_USER_CREATED:
$folder->class = Syncroton_Data_Factory::CLASS_NOTES;
break;
case Syncroton_Command_FolderSync::FOLDERTYPE_TASK_USER_CREATED:
$folder->class = Syncroton_Data_Factory::CLASS_TASKS;
break;
default:
// unsupported type
return;
}
$dataController = Syncroton_Data_Factory::factory($folder->class, $this->_device, $this->_syncTimeStamp);
$this->_folder = $dataController->createFolder($folder);
$this->_folder->class = $folder->class;
$this->_folder->deviceId = $this->_device;
$this->_folder->creationTime = $this->_syncTimeStamp;
$this->_folderBackend->create($this->_folder);
}
/**
* generate FolderCreate response
*/
public function getResponse()
{
$folderCreate = $this->_outputDom->documentElement;
if (!$this->_syncState instanceof Syncroton_Model_SyncState) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " invalid synckey provided. FolderSync 0 needed.");
$folderCreate->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Status', Syncroton_Command_FolderSync::STATUS_INVALID_SYNC_KEY));
- } else if (!$this->_folder) {
+
+ } else if (!$this->_folder instanceof Syncroton_Model_Folder) {
$folderCreate->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Status', Syncroton_Command_FolderSync::STATUS_UNKNOWN_ERROR));
+
} else {
$this->_syncState->counter++;
$this->_syncState->lastsync = $this->_syncTimeStamp;
// store folder in state backend
$this->_syncStateBackend->update($this->_syncState);
// create xml output
$folderCreate->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Status', Syncroton_Command_FolderSync::STATUS_SUCCESS));
$folderCreate->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'SyncKey', $this->_syncState->counter));
$folderCreate->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'ServerId', $this->_folder->serverId));
}
return $this->_outputDom;
}
}
diff --git a/lib/ext/Syncroton/Command/FolderSync.php b/lib/ext/Syncroton/Command/FolderSync.php
index ed3a350..9c563bf 100644
--- a/lib/ext/Syncroton/Command/FolderSync.php
+++ b/lib/ext/Syncroton/Command/FolderSync.php
@@ -1,264 +1,267 @@
<?php
/**
* Syncroton
*
* @package Syncroton
* @subpackage Command
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2008-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync FolderSync command
*
* @package Syncroton
* @subpackage Command
*/
class Syncroton_Command_FolderSync extends Syncroton_Command_Wbxml
{
const STATUS_SUCCESS = 1;
const STATUS_FOLDER_EXISTS = 2;
const STATUS_IS_SPECIAL_FOLDER = 3;
const STATUS_FOLDER_NOT_FOUND = 4;
const STATUS_PARENT_FOLDER_NOT_FOUND = 5;
const STATUS_SERVER_ERROR = 6;
const STATUS_ACCESS_DENIED = 7;
const STATUS_REQUEST_TIMED_OUT = 8;
const STATUS_INVALID_SYNC_KEY = 9;
const STATUS_MISFORMATTED = 10;
const STATUS_UNKNOWN_ERROR = 11;
/**
* some usefull constants for working with the xml files
*/
const FOLDERTYPE_GENERIC_USER_CREATED = 1;
const FOLDERTYPE_INBOX = 2;
const FOLDERTYPE_DRAFTS = 3;
const FOLDERTYPE_DELETEDITEMS = 4;
const FOLDERTYPE_SENTMAIL = 5;
const FOLDERTYPE_OUTBOX = 6;
const FOLDERTYPE_TASK = 7;
const FOLDERTYPE_CALENDAR = 8;
const FOLDERTYPE_CONTACT = 9;
const FOLDERTYPE_NOTE = 10;
const FOLDERTYPE_JOURNAL = 11;
const FOLDERTYPE_MAIL_USER_CREATED = 12;
const FOLDERTYPE_CALENDAR_USER_CREATED = 13;
const FOLDERTYPE_CONTACT_USER_CREATED = 14;
const FOLDERTYPE_TASK_USER_CREATED = 15;
const FOLDERTYPE_JOURNAL_USER_CREATED = 16;
const FOLDERTYPE_NOTE_USER_CREATED = 17;
const FOLDERTYPE_UNKOWN = 18;
protected $_defaultNameSpace = 'uri:FolderHierarchy';
protected $_documentElement = 'FolderSync';
protected $_classes = array(
Syncroton_Data_Factory::CLASS_CALENDAR,
Syncroton_Data_Factory::CLASS_CONTACTS,
Syncroton_Data_Factory::CLASS_EMAIL,
Syncroton_Data_Factory::CLASS_NOTES,
Syncroton_Data_Factory::CLASS_TASKS
);
/**
* @var string
*/
protected $_syncKey;
/**
* parse FolderSync request
*
*/
public function handle()
{
$xml = simplexml_import_dom($this->_requestBody);
$syncKey = (int)$xml->SyncKey;
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " synckey is $syncKey");
if ($syncKey === 0) {
$this->_syncState = new Syncroton_Model_SyncState(array(
'device_id' => $this->_device,
'counter' => 0,
'type' => 'FolderSync',
'lastsync' => $this->_syncTimeStamp
));
// reset state of foldersync
$this->_syncStateBackend->resetState($this->_device, 'FolderSync');
return;
}
if (!($this->_syncState = $this->_syncStateBackend->validate($this->_device, 'FolderSync', $syncKey)) instanceof Syncroton_Model_SyncState) {
$this->_syncStateBackend->resetState($this->_device, 'FolderSync');
}
}
/**
* generate FolderSync response
*
* @todo changes are missing in response (folder got renamed for example)
*/
public function getResponse()
{
$folderSync = $this->_outputDom->documentElement;
// invalid synckey provided
if (!$this->_syncState instanceof Syncroton_Model_SyncState) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " invalid synckey provided. FolderSync 0 needed.");
$folderSync->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Status', self::STATUS_INVALID_SYNC_KEY));
return $this->_outputDom;
}
// send headers from options command also when FolderSync SyncKey is 0
if ($this->_syncState->counter == 0) {
$optionsCommand = new Syncroton_Command_Options();
$this->_headers = array_merge($this->_headers, $optionsCommand->getHeaders());
}
$adds = array();
$updates = array();
$deletes = array();
foreach($this->_classes as $class) {
try {
$dataController = Syncroton_Data_Factory::factory($class, $this->_device, $this->_syncTimeStamp);
} catch (Exception $e) {
// backend not defined
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " no data backend defined for class: " . $class);
continue;
}
try {
// retrieve all folders available in data backend
$serverFolders = $dataController->getAllFolders();
-
+
+ // retrieve all folders sent to client
+ $clientFolders = $this->_folderBackend->getFolderState($this->_device, $class);
+
if ($this->_syncState->counter > 0) {
// retrieve all folders changed since last sync
$changedFolders = $dataController->getChangedFolders($this->_syncState->lastsync, $this->_syncTimeStamp);
} else {
$changedFolders = array();
}
- // retrieve all folders sent to client
- $clientFolders = $this->_folderBackend->getFolderState($this->_device, $class);
+ // only folders which were sent to the client already are allowed to be in $changedFolders
+ $changedFolders = array_intersect_key($changedFolders, $clientFolders);
} catch (Exception $e) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->crit(__METHOD__ . '::' . __LINE__ . " Syncing folder hierarchy failed: " . $e->getMessage());
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " Syncing folder hierarchy failed: " . $e->getTraceAsString());
// The Status element is global for all collections. If one collection fails,
// a failure status MUST be returned for all collections.
if ($e instanceof Syncroton_Exception_Status) {
$status = $e->getCode();
} else {
$status = Syncroton_Exception_Status_FolderSync::UNKNOWN_ERROR;
}
$folderSync->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Status', $status));
return $this->_outputDom;
}
$serverFoldersIds = array_keys($serverFolders);
// is this the first sync?
if ($this->_syncState->counter == 0) {
$clientFoldersIds = array();
} else {
$clientFoldersIds = array_keys($clientFolders);
}
// calculate added entries
$serverDiff = array_diff($serverFoldersIds, $clientFoldersIds);
foreach ($serverDiff as $serverFolderId) {
// have we created a folderObject in syncroton_folder before?
if (isset($clientFolders[$serverFolderId])) {
$add = $clientFolders[$serverFolderId];
} else {
$add = $serverFolders[$serverFolderId];
$add->creationTime = $this->_syncTimeStamp;
$add->deviceId = $this->_device;
unset($add->id);
}
$add->class = $class;
$adds[] = $add;
}
// calculate changed entries
foreach ($changedFolders as $changedFolder) {
$change = $clientFolders[$changedFolder->serverId];
$change->displayName = $changedFolder->displayName;
$change->parentId = $changedFolder->parentId;
$change->type = $changedFolder->type;
$updates[] = $change;
}
// calculate deleted entries
$serverDiff = array_diff($clientFoldersIds, $serverFoldersIds);
foreach ($serverDiff as $serverFolderId) {
$deletes[] = $clientFolders[$serverFolderId];
}
}
$folderSync->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Status', self::STATUS_SUCCESS));
$count = count($adds) + count($updates) + count($deletes);
if($count > 0) {
$this->_syncState->counter++;
$this->_syncState->lastsync = $this->_syncTimeStamp;
}
// create xml output
$folderSync->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'SyncKey', $this->_syncState->counter));
$changes = $folderSync->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Changes'));
$changes->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Count', $count));
foreach($adds as $folder) {
$add = $changes->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Add'));
$folder->appendXML($add, $this->_device);
// store folder in backend
if (empty($folder->id)) {
$this->_folderBackend->create($folder);
}
}
foreach($updates as $folder) {
$update = $changes->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Update'));
$folder->appendXML($update, $this->_device);
$this->_folderBackend->update($folder);
}
foreach($deletes as $folder) {
$delete = $changes->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Delete'));
$delete->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'ServerId', $folder->serverId));
$this->_folderBackend->delete($folder);
}
if (empty($this->_syncState->id)) {
$this->_syncStateBackend->create($this->_syncState);
} else {
$this->_syncStateBackend->update($this->_syncState);
}
return $this->_outputDom;
}
}
diff --git a/lib/ext/Syncroton/Command/ItemOperations.php b/lib/ext/Syncroton/Command/ItemOperations.php
index 93aa722..20401ba 100644
--- a/lib/ext/Syncroton/Command/ItemOperations.php
+++ b/lib/ext/Syncroton/Command/ItemOperations.php
@@ -1,253 +1,305 @@
<?php
/**
* Syncroton
*
* @package Syncroton
* @subpackage Command
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync ItemOperations command
*
* @package Syncroton
* @subpackage Command
*/
class Syncroton_Command_ItemOperations extends Syncroton_Command_Wbxml
{
const STATUS_SUCCESS = 1;
const STATUS_PROTOCOL_ERROR = 2;
const STATUS_SERVER_ERROR = 3;
const STATUS_ITEM_FAILED_CONVERSION = 14;
protected $_defaultNameSpace = 'uri:ItemOperations';
protected $_documentElement = 'ItemOperations';
/**
* list of items to move
*
* @var array
*/
protected $_fetches = array();
/**
* list of folder to empty
*
* @var array
*/
protected $_emptyFolderContents = array();
/**
* parse MoveItems request
*
*/
public function handle()
{
$xml = simplexml_import_dom($this->_requestBody);
if (isset($xml->Fetch)) {
foreach ($xml->Fetch as $fetch) {
$this->_fetches[] = $this->_handleFetch($fetch);
}
}
if (isset($xml->EmptyFolderContents)) {
foreach ($xml->EmptyFolderContents as $emptyFolderContents) {
$this->_emptyFolderContents[] = $this->_handleEmptyFolderContents($emptyFolderContents);
}
}
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " fetches: " . print_r($this->_fetches, true));
}
/**
* generate ItemOperations response
*
* @todo add multipart support to all types of fetches
*/
public function getResponse()
{
// add aditional namespaces
$this->_outputDom->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:AirSyncBase' , 'uri:AirSyncBase');
$this->_outputDom->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:AirSync' , 'uri:AirSync');
$this->_outputDom->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:Search' , 'uri:Search');
$itemOperations = $this->_outputDom->documentElement;
$itemOperations->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Status', Syncroton_Command_ItemOperations::STATUS_SUCCESS));
$response = $itemOperations->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Response'));
foreach ($this->_fetches as $fetch) {
$fetchTag = $response->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Fetch'));
try {
$dataController = Syncroton_Data_Factory::factory($fetch['store'], $this->_device, $this->_syncTimeStamp);
if (isset($fetch['collectionId'])) {
$fetchTag->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Status', Syncroton_Command_ItemOperations::STATUS_SUCCESS));
$fetchTag->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'CollectionId', $fetch['collectionId']));
$fetchTag->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ServerId', $fetch['serverId']));
$properties = $this->_outputDom->createElementNS('uri:ItemOperations', 'Properties');
- $dataController
- ->getEntry(new Syncroton_Model_SyncCollection(array('collectionId' => $fetch['collectionId'], 'options' => $fetch['options'])), $fetch['serverId'])
+ $dataController
+ ->getEntry(new Syncroton_Model_SyncCollection(array('collectionId' => $fetch['collectionId'], 'options' => $fetch['options'])), $fetch['serverId'])
->appendXML($properties, $this->_device);
$fetchTag->appendChild($properties);
} elseif (isset($fetch['longId'])) {
$fetchTag->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Status', Syncroton_Command_ItemOperations::STATUS_SUCCESS));
$fetchTag->appendChild($this->_outputDom->createElementNS('uri:Search', 'LongId', $fetch['longId']));
$properties = $this->_outputDom->createElementNS('uri:ItemOperations', 'Properties');
- $dataController
- ->getEntry(new Syncroton_Model_SyncCollection(array('collectionId' => $fetch['longId'], 'options' => $fetch['options'])), $fetch['longId'])
+ $dataController
+ ->getEntry(new Syncroton_Model_SyncCollection(array('collectionId' => $fetch['longId'], 'options' => $fetch['options'])), $fetch['longId'])
->appendXML($properties, $this->_device);
$fetchTag->appendChild($properties);
} elseif (isset($fetch['fileReference'])) {
$fetchTag->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Status', Syncroton_Command_ItemOperations::STATUS_SUCCESS));
$fetchTag->appendChild($this->_outputDom->createElementNS('uri:AirSyncBase', 'FileReference', $fetch['fileReference']));
$properties = $this->_outputDom->createElementNS('uri:ItemOperations', 'Properties');
$fileReference = $dataController->getFileReference($fetch['fileReference']);
// unset data field and move content to stream
if ($this->_requestParameters['acceptMultipart'] == true) {
$this->_headers['Content-Type'] = 'application/vnd.ms-sync.multipart';
$partStream = fopen("php://temp", 'r+');
if (is_resource($fileReference->data)) {
stream_copy_to_stream($fileReference->data, $partStream);
} else {
fwrite($partStream, $fileReference->data);
}
unset($fileReference->data);
$this->_parts[] = $partStream;
$fileReference->part = count($this->_parts);
+ }
+
+ /**
+ * the client requested a range. But we return the whole file.
+ *
+ * That's not correct, but allowed. The server is allowed to overwrite the range.
+ *
+ * @todo implement cutting $fileReference->data into pieces
+ */
+ if (isset($fetch['options']['range'])) {
+ $dataSize = $this->_getDataSize($fileReference->data);
+
+ $total = $this->_outputDom->createElementNS('uri:ItemOperations', 'Total', $dataSize);
+ $properties->appendChild($total);
+
+ $rangeEnd = $dataSize > 0 ? $dataSize - 1 : 0;
+ $range = $this->_outputDom->createElementNS('uri:ItemOperations', 'Range', '0-' . $rangeEnd);
+ $properties->appendChild($range);
}
$fileReference->appendXML($properties, $this->_device);
$fetchTag->appendChild($properties);
}
} catch (Syncroton_Exception_NotFound $e) {
$response->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Status', Syncroton_Command_ItemOperations::STATUS_ITEM_FAILED_CONVERSION));
} catch (Exception $e) {
//echo __LINE__; echo $e->getMessage(); echo $e->getTraceAsString();
$response->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Status', Syncroton_Command_ItemOperations::STATUS_SERVER_ERROR));
}
}
foreach ($this->_emptyFolderContents as $emptyFolderContents) {
try {
$folder = $this->_folderBackend->getFolder($this->_device, $emptyFolderContents['collectionId']);
$dataController = Syncroton_Data_Factory::factory($folder->class, $this->_device, $this->_syncTimeStamp);
$dataController->emptyFolderContents($emptyFolderContents['collectionId'], $emptyFolderContents['options']);
$status = Syncroton_Command_ItemOperations::STATUS_SUCCESS;
}
catch (Syncroton_Exception_Status_ItemOperations $e) {
$status = $e->getCode();
}
catch (Exception $e) {
$status = Syncroton_Exception_Status_ItemOperations::ITEM_SERVER_ERROR;
}
$emptyFolderContentsTag = $this->_outputDom->createElementNS('uri:ItemOperations', 'EmptyFolderContents');
$emptyFolderContentsTag->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Status', $status));
$emptyFolderContentsTag->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'CollectionId', $emptyFolderContents['collectionId']));
$response->appendChild($emptyFolderContentsTag);
}
return $this->_outputDom;
}
+ /**
+ * parse fetch request
+ *
+ * @param SimpleXMLElement $fetch
+ * @return array
+ */
protected function _handleFetch(SimpleXMLElement $fetch)
{
$fetchArray = array(
- 'store' => (string)$fetch->Store,
+ 'store' => (string)$fetch->Store,
'options' => array()
);
// try to fetch element from namespace AirSync
$airSync = $fetch->children('uri:AirSync');
if (isset($airSync->CollectionId)) {
$fetchArray['collectionId'] = (string)$airSync->CollectionId;
$fetchArray['serverId'] = (string)$airSync->ServerId;
}
// try to fetch element from namespace Search
$search = $fetch->children('uri:Search');
if (isset($search->LongId)) {
$fetchArray['longId'] = (string)$search->LongId;
}
// try to fetch element from namespace AirSyncBase
$airSyncBase = $fetch->children('uri:AirSyncBase');
if (isset($airSyncBase->FileReference)) {
$fetchArray['fileReference'] = (string)$airSyncBase->FileReference;
}
if (isset($fetch->Options)) {
// try to fetch element from namespace AirSyncBase
$airSyncBase = $fetch->Options->children('uri:AirSyncBase');
if (isset($airSyncBase->BodyPreference)) {
foreach ($airSyncBase->BodyPreference as $bodyPreference) {
$type = (int) $bodyPreference->Type;
$fetchArray['options']['bodyPreferences'][$type] = array(
'type' => $type
);
-
+
// optional
if (isset($bodyPreference->TruncationSize)) {
$fetchArray['options']['bodyPreferences'][$type]['truncationSize'] = (int) $bodyPreference->TruncationSize;
}
// optional
if (isset($bodyPreference->AllOrNone)) {
$fetchArray['options']['bodyPreferences'][$type]['allOrNone'] = (int) $bodyPreference->AllOrNone;
}
}
}
+
+ if (isset($airSyncBase->Range)) {
+ $fetchArray['options']['range'] = (string) $airSyncBase->Range;
+ }
}
return $fetchArray;
}
+ /**
+ * handle empty folder request
+ *
+ * @param SimpleXMLElement $emptyFolderContent
+ * @return array
+ */
protected function _handleEmptyFolderContents(SimpleXMLElement $emptyFolderContent)
{
$folderArray = array(
'collectiondId' => null,
'options' => array('deleteSubFolders' => FALSE)
);
// try to fetch element from namespace AirSync
$airSync = $emptyFolderContent->children('uri:AirSync');
$folderArray['collectionId'] = (string)$airSync->CollectionId;
if (isset($emptyFolderContent->Options)) {
$folderArray['options']['deleteSubFolders'] = isset($emptyFolderContent->Options->DeleteSubFolders);
}
return $folderArray;
}
+
+ /**
+ * return length of data
+ *
+ * @param string|resource $data
+ * @return number
+ */
+ protected function _getDataSize($data)
+ {
+ if (is_resource($data)) {
+ rewind($data);
+ fseek($data, 0, SEEK_END);
+ return ftell($data);
+
+ } else {
+ return strlen($data);
+ }
+ }
}
diff --git a/lib/ext/Syncroton/Command/Ping.php b/lib/ext/Syncroton/Command/Ping.php
index 2e71fd2..8bdbe01 100644
--- a/lib/ext/Syncroton/Command/Ping.php
+++ b/lib/ext/Syncroton/Command/Ping.php
@@ -1,237 +1,239 @@
<?php
/**
* Syncroton
*
* @package Syncroton
* @subpackage Command
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2008-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync Ping command
*
* @package Syncroton
* @subpackage Command
*/
class Syncroton_Command_Ping extends Syncroton_Command_Wbxml
{
const STATUS_NO_CHANGES_FOUND = 1;
const STATUS_CHANGES_FOUND = 2;
const STATUS_MISSING_PARAMETERS = 3;
const STATUS_REQUEST_FORMAT_ERROR = 4;
const STATUS_INTERVAL_TO_GREAT_OR_SMALL = 5;
const STATUS_TO_MUCH_FOLDERS = 6;
const STATUS_FOLDER_NOT_FOUND = 7;
const STATUS_GENERAL_ERROR = 8;
+ const MAX_PING_INTERVAL = 3540; // 59 minutes limit defined in Activesync protocol spec.
+
protected $_skipValidatePolicyKey = true;
protected $_changesDetected = false;
/**
* @var Syncroton_Backend_StandAlone_Abstract
*/
protected $_dataBackend;
protected $_defaultNameSpace = 'uri:Ping';
protected $_documentElement = 'Ping';
protected $_foldersWithChanges = array();
/**
* process the XML file and add, change, delete or fetches data
*
* @todo can we get rid of LIBXML_NOWARNING
* @todo we need to stored the initial data for folders and lifetime as the phone is sending them only when they change
* @return resource
*/
public function handle()
{
$intervalStart = time();
$status = self::STATUS_NO_CHANGES_FOUND;
// the client does not send a wbxml document, if the Ping parameters did not change compared with the last request
if ($this->_requestBody instanceof DOMDocument) {
$xml = simplexml_import_dom($this->_requestBody);
$xml->registerXPathNamespace('Ping', 'Ping');
if(isset($xml->HeartbeatInterval)) {
$this->_device->pinglifetime = (int)$xml->HeartbeatInterval;
}
if (isset($xml->Folders->Folder)) {
$folders = array();
foreach ($xml->Folders->Folder as $folderXml) {
try {
// does the folder exist?
$folder = $this->_folderBackend->getFolder($this->_device, (string)$folderXml->Id);
$folders[$folder->id] = $folder;
} catch (Syncroton_Exception_NotFound $senf) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " " . $senf->getMessage());
$status = self::STATUS_FOLDER_NOT_FOUND;
break;
}
}
$this->_device->pingfolder = serialize(array_keys($folders));
}
}
-
+
$this->_device->lastping = new DateTime('now', new DateTimeZone('utc'));
if ($status == self::STATUS_NO_CHANGES_FOUND) {
$this->_device = $this->_deviceBackend->update($this->_device);
}
$lifeTime = $this->_device->pinglifetime;
$maxInterval = Syncroton_Registry::getPingInterval();
if ($maxInterval <= 0 || $maxInterval > Syncroton_Server::MAX_HEARTBEAT_INTERVAL) {
$maxInterval = Syncroton_Server::MAX_HEARTBEAT_INTERVAL;
}
if ($lifeTime > $maxInterval) {
$ping = $this->_outputDom->documentElement;
$ping->appendChild($this->_outputDom->createElementNS('uri:Ping', 'Status', self::STATUS_INTERVAL_TO_GREAT_OR_SMALL));
$ping->appendChild($this->_outputDom->createElementNS('uri:Ping', 'HeartbeatInterval', $maxInterval));
return;
}
$intervalEnd = $intervalStart + $lifeTime;
$secondsLeft = $intervalEnd;
$folders = unserialize($this->_device->pingfolder);
if ($status === self::STATUS_NO_CHANGES_FOUND && (!is_array($folders) || count($folders) == 0)) {
$status = self::STATUS_MISSING_PARAMETERS;
}
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " Folders to monitor($lifeTime / $intervalStart / $intervalEnd / $status): " . print_r($folders, true));
if ($status === self::STATUS_NO_CHANGES_FOUND) {
do {
// take a break to save battery lifetime
sleep(Syncroton_Registry::getPingTimeout());
try {
$device = $this->_deviceBackend->get($this->_device->id);
} catch (Syncroton_Exception_NotFound $e) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " " . $e->getMessage());
$status = self::STATUS_FOLDER_NOT_FOUND;
break;
} catch (Exception $e) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->err(__METHOD__ . '::' . __LINE__ . " " . $e->getMessage());
// do nothing, maybe temporal issue, should we stop?
continue;
}
// if another Ping command updated lastping property, we can stop processing this Ping command request
if ((isset($device->lastping) && $device->lastping instanceof DateTime) &&
$device->pingfolder === $this->_device->pingfolder &&
$device->lastping->getTimestamp() > $this->_device->lastping->getTimestamp() ) {
break;
}
$now = new DateTime('now', new DateTimeZone('utc'));
foreach ($folders as $folderId) {
try {
$folder = $this->_folderBackend->get($folderId);
$dataController = Syncroton_Data_Factory::factory($folder->class, $this->_device, $this->_syncTimeStamp);
} catch (Syncroton_Exception_NotFound $e) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " " . $e->getMessage());
$status = self::STATUS_FOLDER_NOT_FOUND;
break;
} catch (Exception $e) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->err(__METHOD__ . '::' . __LINE__ . " " . $e->getMessage());
// do nothing, maybe temporal issue, should we stop?
continue;
}
try {
$syncState = $this->_syncStateBackend->getSyncState($this->_device, $folder);
// another process synchronized data of this folder already. let's skip it
if ($syncState->lastsync > $this->_syncTimeStamp) {
continue;
}
// safe battery time by skipping folders which got synchronied less than Syncroton_Registry::getQuietTime() seconds ago
if (($now->getTimestamp() - $syncState->lastsync->getTimestamp()) < Syncroton_Registry::getQuietTime()) {
continue;
}
$foundChanges = $dataController->hasChanges($this->_contentStateBackend, $folder, $syncState);
} catch (Syncroton_Exception_NotFound $e) {
// folder got never synchronized to client
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " " . $e->getMessage());
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . ' syncstate not found. enforce sync for folder: ' . $folder->serverId);
$foundChanges = true;
}
if ($foundChanges == true) {
$this->_foldersWithChanges[] = $folder;
$status = self::STATUS_CHANGES_FOUND;
}
}
if ($status != self::STATUS_NO_CHANGES_FOUND) {
break;
}
$secondsLeft = $intervalEnd - time();
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " DeviceId: " . $this->_device->deviceid . " seconds left: " . $secondsLeft);
// See: http://www.tine20.org/forum/viewtopic.php?f=12&t=12146
//
// break if there are less than PingTimeout + 10 seconds left for the next loop
// otherwise the response will be returned after the client has finished his Ping
// request already maybe
- } while ($secondsLeft > (Syncroton_Registry::getPingTimeout() + 10));
+ } while (Syncroton_Server::validateSession() && $secondsLeft > (Syncroton_Registry::getPingTimeout() + 10));
}
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " DeviceId: " . $this->_device->deviceid . " Lifetime: $lifeTime SecondsLeft: $secondsLeft Status: $status)");
$ping = $this->_outputDom->documentElement;
$ping->appendChild($this->_outputDom->createElementNS('uri:Ping', 'Status', $status));
if($status === self::STATUS_CHANGES_FOUND) {
$folders = $ping->appendChild($this->_outputDom->createElementNS('uri:Ping', 'Folders'));
foreach($this->_foldersWithChanges as $changedFolder) {
$folder = $folders->appendChild($this->_outputDom->createElementNS('uri:Ping', 'Folder', $changedFolder->serverId));
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " DeviceId: " . $this->_device->deviceid . " changes in folder: " . $changedFolder->serverId);
}
}
}
/**
* generate ping command response
*
*/
public function getResponse()
{
return $this->_outputDom;
}
}
diff --git a/lib/ext/Syncroton/Command/Settings.php b/lib/ext/Syncroton/Command/Settings.php
index cc34b44..3a9c7c5 100644
--- a/lib/ext/Syncroton/Command/Settings.php
+++ b/lib/ext/Syncroton/Command/Settings.php
@@ -1,162 +1,156 @@
<?php
/**
* Syncroton
*
* @package Syncroton
* @subpackage Command
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2008-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync Settings command
*
* @package Syncroton
* @subpackage Command
*/
class Syncroton_Command_Settings extends Syncroton_Command_Wbxml
{
const STATUS_SUCCESS = 1;
protected $_defaultNameSpace = 'uri:Settings';
protected $_documentElement = 'Settings';
/**
* @var Syncroton_Model_DeviceInformation
*/
protected $_deviceInformation;
protected $_userInformationRequested = false;
protected $_OofGet;
protected $_OofSet;
/**
* process the XML file and add, change, delete or fetches data
*
*/
public function handle()
{
$xml = simplexml_import_dom($this->_requestBody);
if(isset($xml->DeviceInformation->Set)) {
$this->_deviceInformation = new Syncroton_Model_DeviceInformation($xml->DeviceInformation->Set);
$this->_device->model = $this->_deviceInformation->model;
$this->_device->imei = $this->_deviceInformation->iMEI;
$this->_device->friendlyname = $this->_deviceInformation->friendlyName;
$this->_device->os = $this->_deviceInformation->oS;
$this->_device->oslanguage = $this->_deviceInformation->oSLanguage;
$this->_device->phonenumber = $this->_deviceInformation->phoneNumber;
if ($this->_device->isDirty()) {
$this->_device = $this->_deviceBackend->update($this->_device);
}
}
if(isset($xml->UserInformation->Get)) {
$this->_userInformationRequested = true;
}
if (isset($xml->Oof)) {
if (isset($xml->Oof->Get)) {
$this->_OofGet = array('bodyType' => $xml->Oof->Get->BodyType);
- }
- else if (isset($xml->Oof->Set)) {
+ } else if (isset($xml->Oof->Set)) {
$this->_OofSet = new Syncroton_Model_Oof($xml->Oof->Set);
}
}
}
/**
* this function generates the response for the client
*
*/
public function getResponse()
{
$settings = $this->_outputDom->documentElement;
$settings->appendChild($this->_outputDom->createElementNS('uri:Settings', 'Status', self::STATUS_SUCCESS));
if ($this->_deviceInformation instanceof Syncroton_Model_DeviceInformation) {
$deviceInformation = $settings->appendChild($this->_outputDom->createElementNS('uri:Settings', 'DeviceInformation'));
$set = $deviceInformation->appendChild($this->_outputDom->createElementNS('uri:Settings', 'Set'));
$set->appendChild($this->_outputDom->createElementNS('uri:Settings', 'Status', self::STATUS_SUCCESS));
}
if ($this->_userInformationRequested === true) {
$userInformation = $settings->appendChild($this->_outputDom->createElementNS('uri:Settings', 'UserInformation'));
$userInformation->appendChild($this->_outputDom->createElementNS('uri:Settings', 'Status', self::STATUS_SUCCESS));
$get = $userInformation->appendChild($this->_outputDom->createElementNS('uri:Settings', 'Get'));
/*
$smtpAddresses = array();
if (!empty($smtpAddresses)) {
$emailAddresses = $get->appendChild($this->_outputDom->createElementNS('uri:Settings', 'EmailAddresses'));
foreach($smtpAddresses as $smtpAddress) {
$emailAddresses->appendChild($this->_outputDom->createElementNS('uri:Settings', 'SMTPAddress', $smtpAddress));
}
}
*/
$userAccounts = $this->_deviceBackend->userAccounts($this->_device);
if (!empty($userAccounts)) {
$accounts = $get->appendChild($this->_outputDom->createElementNS('uri:Settings', 'Accounts'));
foreach ((array) $userAccounts as $account) {
$element = $accounts->appendChild($this->_outputDom->createElementNS('uri:Settings', 'Account'));
$account->appendXML($element, $this->_device);
}
}
}
// Out-of-Office
if (!empty($this->_OofGet)) {
try {
$OofGet = $this->_deviceBackend->getOOF($this->_OofGet);
- }
- catch (Exception $e) {
+ } catch (Exception $e) {
if ($e instanceof Syncroton_Exception_Status) {
$OofStatus = $e->getCode();
- }
- else {
+ } else {
$OofStatus = Syncroton_Exception_Status::SERVER_ERROR;
}
if ($this->_logger instanceof Zend_Log) {
$this->_logger->warn(__METHOD__ . '::' . __LINE__ . " Setting OOF failed: " . $e->getMessage());
}
}
// expected empty result if OOF is not supported by the server
if ($OofGet instanceof Syncroton_Model_Oof) {
$Oof = $settings->appendChild($this->_outputDom->createElementNS('uri:Settings', 'Oof'));
$Oof->appendChild($this->_outputDom->createElementNS('uri:Settings', 'Status', $OofStatus));
$Get = $Oof->appendChild($this->_outputDom->createElementNS('uri:Settings', 'Get'));
$OofGet->appendXML($Get, $this->_device);
}
- }
- else if (!empty($this->_OofSet)) {
+ } else if (!empty($this->_OofSet)) {
try {
$this->_deviceBackend->setOOF($this->_OofSet);
$OofStatus = self::STATUS_SUCCESS;
- }
- catch (Exception $e) {
+ } catch (Exception $e) {
if ($e instanceof Syncroton_Exception_Status) {
$OofStatus = $e->getCode();
- }
- else {
+ } else {
$OofStatus = Syncroton_Exception_Status::SERVER_ERROR;
}
if ($this->_logger instanceof Zend_Log) {
$this->_logger->warn(__METHOD__ . '::' . __LINE__ . " Setting OOF failed: " . $e->getMessage());
}
}
$Oof = $settings->appendChild($this->_outputDom->createElementNS('uri:Settings', 'Oof'));
$Oof->appendChild($this->_outputDom->createElementNS('uri:Settings', 'Status', $OofStatus));
}
return $this->_outputDom;
}
}
diff --git a/lib/ext/Syncroton/Command/Sync.php b/lib/ext/Syncroton/Command/Sync.php
index 052eb4c..b52fe5b 100644
--- a/lib/ext/Syncroton/Command/Sync.php
+++ b/lib/ext/Syncroton/Command/Sync.php
@@ -1,1121 +1,1121 @@
<?php
/**
* Syncroton
*
* @package Syncroton
* @subpackage Command
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync Sync command
*
* @package Syncroton
* @subpackage Command
*/
class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
{
const STATUS_SUCCESS = 1;
const STATUS_PROTOCOL_VERSION_MISMATCH = 2;
const STATUS_INVALID_SYNC_KEY = 3;
const STATUS_PROTOCOL_ERROR = 4;
const STATUS_SERVER_ERROR = 5;
const STATUS_ERROR_IN_CLIENT_SERVER_CONVERSION = 6;
const STATUS_CONFLICT_MATCHING_THE_CLIENT_AND_SERVER_OBJECT = 7;
const STATUS_OBJECT_NOT_FOUND = 8;
const STATUS_USER_ACCOUNT_MAYBE_OUT_OF_DISK_SPACE = 9;
const STATUS_ERROR_SETTING_NOTIFICATION_GUID = 10;
const STATUS_DEVICE_NOT_PROVISIONED_FOR_NOTIFICATIONS = 11;
const STATUS_FOLDER_HIERARCHY_HAS_CHANGED = 12;
const STATUS_RESEND_FULL_XML = 13;
const STATUS_WAIT_INTERVAL_OUT_OF_RANGE = 14;
const CONFLICT_OVERWRITE_SERVER = 0;
const CONFLICT_OVERWRITE_PIM = 1;
const MIMESUPPORT_DONT_SEND_MIME = 0;
const MIMESUPPORT_SMIME_ONLY = 1;
const MIMESUPPORT_SEND_MIME = 2;
const BODY_TYPE_PLAIN_TEXT = 1;
const BODY_TYPE_HTML = 2;
const BODY_TYPE_RTF = 3;
const BODY_TYPE_MIME = 4;
/**
* truncate types
*/
const TRUNCATE_ALL = 0;
const TRUNCATE_4096 = 1;
const TRUNCATE_5120 = 2;
const TRUNCATE_7168 = 3;
const TRUNCATE_10240 = 4;
const TRUNCATE_20480 = 5;
const TRUNCATE_51200 = 6;
const TRUNCATE_102400 = 7;
const TRUNCATE_NOTHING = 8;
-
+
/**
* filter types
*/
const FILTER_NOTHING = 0;
const FILTER_1_DAY_BACK = 1;
const FILTER_3_DAYS_BACK = 2;
const FILTER_1_WEEK_BACK = 3;
const FILTER_2_WEEKS_BACK = 4;
const FILTER_1_MONTH_BACK = 5;
const FILTER_3_MONTHS_BACK = 6;
const FILTER_6_MONTHS_BACK = 7;
const FILTER_INCOMPLETE = 8;
-
+
protected $_defaultNameSpace = 'uri:AirSync';
protected $_documentElement = 'Sync';
/**
* list of collections
*
* @var array
*/
protected $_collections = array();
protected $_modifications = array();
-
+
/**
* the global WindowSize
*
* @var integer
*/
protected $_globalWindowSize;
/**
* there are more entries than WindowSize available
* the MoreAvailable tag hot added to the xml output
*
* @var boolean
*/
protected $_moreAvailable = false;
/**
* @var Syncroton_Model_SyncState
*/
protected $_syncState;
protected $_maxWindowSize = 100;
protected $_heartbeatInterval = null;
/**
* process the XML file and add, change, delete or fetches data
*/
public function handle()
{
// input xml
$requestXML = simplexml_import_dom($this->_mergeSyncRequest($this->_requestBody, $this->_device));
if (! isset($requestXML->Collections)) {
$this->_outputDom->documentElement->appendChild(
$this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_RESEND_FULL_XML)
);
return $this->_outputDom;
}
if (isset($requestXML->HeartbeatInterval)) {
$intervalDiv = 1;
$this->_heartbeatInterval = (int)$requestXML->HeartbeatInterval;
} else if (isset($requestXML->Wait)) {
$intervalDiv = 60;
$this->_heartbeatInterval = (int)$requestXML->Wait * $intervalDiv;
}
-
+
$maxInterval = Syncroton_Registry::getPingInterval();
if ($maxInterval <= 0 || $maxInterval > Syncroton_Server::MAX_HEARTBEAT_INTERVAL) {
$maxInterval = Syncroton_Server::MAX_HEARTBEAT_INTERVAL;
}
-
+
if ($this->_heartbeatInterval && $this->_heartbeatInterval > $maxInterval) {
$sync = $this->_outputDom->documentElement;
$sync->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_WAIT_INTERVAL_OUT_OF_RANGE));
$sync->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Limit', floor($maxInterval/$intervalDiv)));
$this->_heartbeatInterval = null;
}
$this->_globalWindowSize = isset($requestXML->WindowSize) ? (int)$requestXML->WindowSize : 100;
if (!$this->_globalWindowSize || $this->_globalWindowSize > 512) {
$this->_globalWindowSize = 512;
}
-
+
if ($this->_globalWindowSize > $this->_maxWindowSize) {
$this->_globalWindowSize = $this->_maxWindowSize;
}
-
+
// load options from lastsynccollection
$lastSyncCollection = array('options' => array());
if (!empty($this->_device->lastsynccollection)) {
$lastSyncCollection = Zend_Json::decode($this->_device->lastsynccollection);
if (!array_key_exists('options', $lastSyncCollection) || !is_array($lastSyncCollection['options'])) {
$lastSyncCollection['options'] = array();
}
}
$collections = array();
foreach ($requestXML->Collections->Collection as $xmlCollection) {
$collectionId = (string)$xmlCollection->CollectionId;
$collections[$collectionId] = new Syncroton_Model_SyncCollection($xmlCollection);
// do we have to reuse the options from the previous request?
if (!isset($xmlCollection->Options) && array_key_exists($collectionId, $lastSyncCollection['options'])) {
$collections[$collectionId]->options = $lastSyncCollection['options'][$collectionId];
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " restored options to " . print_r($collections[$collectionId]->options, TRUE));
}
// store current options for next Sync command request (sticky options)
$lastSyncCollection['options'][$collectionId] = $collections[$collectionId]->options;
}
$this->_device->lastsynccollection = Zend_Json::encode($lastSyncCollection);
if ($this->_device->isDirty()) {
Syncroton_Registry::getDeviceBackend()->update($this->_device);
}
foreach ($collections as $collectionData) {
// has the folder been synchronised to the device already
try {
$collectionData->folder = $this->_folderBackend->getFolder($this->_device, $collectionData->collectionId);
} catch (Syncroton_Exception_NotFound $senf) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->warn(__METHOD__ . '::' . __LINE__ . " folder {$collectionData->collectionId} not found");
// trigger INVALID_SYNCKEY instead of OBJECT_NOTFOUND when synckey is higher than 0
// to avoid a syncloop for the iPhone
if ($collectionData->syncKey > 0) {
$collectionData->folder = new Syncroton_Model_Folder(array(
'deviceId' => $this->_device,
'serverId' => $collectionData->collectionId
));
}
$this->_collections[$collectionData->collectionId] = $collectionData;
continue;
}
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " SyncKey is {$collectionData->syncKey} Class: {$collectionData->folder->class} CollectionId: {$collectionData->collectionId}");
// initial synckey
if($collectionData->syncKey === 0) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " initial client synckey 0 provided");
// reset sync state for this folder
$this->_syncStateBackend->resetState($this->_device, $collectionData->folder);
$this->_contentStateBackend->resetState($this->_device, $collectionData->folder);
$collectionData->syncState = new Syncroton_Model_SyncState(array(
'device_id' => $this->_device,
'counter' => 0,
'type' => $collectionData->folder,
'lastsync' => $this->_syncTimeStamp
));
$this->_collections[$collectionData->collectionId] = $collectionData;
continue;
}
// check for invalid sycnkey
if(($collectionData->syncState = $this->_syncStateBackend->validate($this->_device, $collectionData->folder, $collectionData->syncKey)) === false) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->warn(__METHOD__ . '::' . __LINE__ . " invalid synckey {$collectionData->syncKey} provided");
// reset sync state for this folder
$this->_syncStateBackend->resetState($this->_device, $collectionData->folder);
$this->_contentStateBackend->resetState($this->_device, $collectionData->folder);
$this->_collections[$collectionData->collectionId] = $collectionData;
continue;
}
$dataController = Syncroton_Data_Factory::factory($collectionData->folder->class, $this->_device, $this->_syncTimeStamp);
switch($collectionData->folder->class) {
case Syncroton_Data_Factory::CLASS_CALENDAR:
$dataClass = 'Syncroton_Model_Event';
break;
case Syncroton_Data_Factory::CLASS_CONTACTS:
$dataClass = 'Syncroton_Model_Contact';
break;
case Syncroton_Data_Factory::CLASS_EMAIL:
$dataClass = 'Syncroton_Model_Email';
break;
case Syncroton_Data_Factory::CLASS_NOTES:
$dataClass = 'Syncroton_Model_Note';
break;
case Syncroton_Data_Factory::CLASS_TASKS:
$dataClass = 'Syncroton_Model_Task';
break;
default:
throw new Syncroton_Exception_UnexpectedValue('invalid class provided');
break;
}
$clientModifications = array(
'added' => array(),
'changed' => array(),
'deleted' => array(),
'forceAdd' => array(),
'forceChange' => array(),
'toBeFetched' => array(),
);
// handle incoming data
if($collectionData->hasClientAdds()) {
$adds = $collectionData->getClientAdds();
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " found " . count($adds) . " entries to be added to server");
foreach ($adds as $add) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " add entry with clientId " . (string) $add->ClientId);
try {
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " adding entry as new");
$serverId = $dataController->createEntry($collectionData->collectionId, new $dataClass($add->ApplicationData));
$clientModifications['added'][$serverId] = array(
'clientId' => (string)$add->ClientId,
'serverId' => $serverId,
'status' => self::STATUS_SUCCESS,
'contentState' => $this->_contentStateBackend->create(new Syncroton_Model_Content(array(
'device_id' => $this->_device,
'folder_id' => $collectionData->folder,
'contentid' => $serverId,
'creation_time' => $this->_syncTimeStamp,
'creation_synckey' => $collectionData->syncKey + 1
)))
);
} catch (Exception $e) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->warn(__METHOD__ . '::' . __LINE__ . " failed to add entry " . $e->getMessage());
$clientModifications['added'][] = array(
'clientId' => (string)$add->ClientId,
'status' => self::STATUS_SERVER_ERROR
);
}
}
}
// handle changes, but only if not first sync
if($collectionData->syncKey > 1 && $collectionData->hasClientChanges()) {
$changes = $collectionData->getClientChanges();
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " found " . count($changes) . " entries to be updated on server");
foreach ($changes as $change) {
$serverId = (string)$change->ServerId;
try {
$dataController->updateEntry($collectionData->collectionId, $serverId, new $dataClass($change->ApplicationData));
$clientModifications['changed'][$serverId] = self::STATUS_SUCCESS;
} catch (Syncroton_Exception_AccessDenied $e) {
$clientModifications['changed'][$serverId] = self::STATUS_CONFLICT_MATCHING_THE_CLIENT_AND_SERVER_OBJECT;
$clientModifications['forceChange'][$serverId] = $serverId;
} catch (Syncroton_Exception_NotFound $e) {
// entry does not exist anymore, will get deleted automaticaly
$clientModifications['changed'][$serverId] = self::STATUS_OBJECT_NOT_FOUND;
} catch (Exception $e) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->warn(__METHOD__ . '::' . __LINE__ . " failed to update entry " . $e);
// something went wrong while trying to update the entry
$clientModifications['changed'][$serverId] = self::STATUS_SERVER_ERROR;
}
}
}
// handle deletes, but only if not first sync
if($collectionData->hasClientDeletes()) {
$deletes = $collectionData->getClientDeletes();
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " found " . count($deletes) . " entries to be deleted on server");
foreach ($deletes as $delete) {
$serverId = (string)$delete->ServerId;
try {
// check if we have sent this entry to the phone
$state = $this->_contentStateBackend->getContentState($this->_device, $collectionData->folder, $serverId);
try {
$dataController->deleteEntry($collectionData->collectionId, $serverId, $collectionData);
} catch(Syncroton_Exception_NotFound $e) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->crit(__METHOD__ . '::' . __LINE__ . ' tried to delete entry ' . $serverId . ' but entry was not found');
} catch (Syncroton_Exception $e) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . ' tried to delete entry ' . $serverId . ' but a error occured: ' . $e->getMessage());
$clientModifications['forceAdd'][$serverId] = $serverId;
}
$this->_contentStateBackend->delete($state);
} catch (Syncroton_Exception_NotFound $senf) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . ' ' . $serverId . ' should have been removed from client already');
// should we send a special status???
//$collectionData->deleted[$serverId] = self::STATUS_SUCCESS;
}
$clientModifications['deleted'][$serverId] = self::STATUS_SUCCESS;
}
}
// handle fetches, but only if not first sync
if($collectionData->syncKey > 1 && $collectionData->hasClientFetches()) {
// the default value for GetChanges is 1. If the phone don't want the changes it must set GetChanges to 0
// some prevoius versions of iOS did not set GetChanges to 0 for fetches. Let's enforce getChanges to false here.
$collectionData->getChanges = false;
$fetches = $collectionData->getClientFetches();
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " found " . count($fetches) . " entries to be fetched from server");
$toBeFecthed = array();
foreach ($fetches as $fetch) {
$serverId = (string)$fetch->ServerId;
$toBeFetched[$serverId] = $serverId;
}
$collectionData->toBeFetched = $toBeFetched;
}
$this->_collections[$collectionData->collectionId] = $collectionData;
$this->_modifications[$collectionData->collectionId] = $clientModifications;
}
}
/**
* (non-PHPdoc)
* @see Syncroton_Command_Wbxml::getResponse()
*/
public function getResponse()
{
$sync = $this->_outputDom->documentElement;
$collections = $this->_outputDom->createElementNS('uri:AirSync', 'Collections');
$totalChanges = 0;
// Detect devices that do not support empty Sync reponse
$emptySyncSupported = !preg_match('/(meego|nokian800)/i', $this->_device->useragent);
// continue only if there are changes or no time is left
if ($this->_heartbeatInterval > 0) {
$intervalStart = time();
do {
// take a break to save battery lifetime
sleep(Syncroton_Registry::getPingTimeout());
$now = new DateTime(null, new DateTimeZone('utc'));
foreach($this->_collections as $collectionData) {
// continue immediately if folder does not exist
if (! ($collectionData->folder instanceof Syncroton_Model_IFolder)) {
break 2;
// countinue immediately if syncstate is invalid
} elseif (! ($collectionData->syncState instanceof Syncroton_Model_ISyncState)) {
break 2;
} else {
if ($collectionData->getChanges !== true) {
continue;
}
try {
// just check if the folder still exists
$this->_folderBackend->get($collectionData->folder);
} catch (Syncroton_Exception_NotFound $senf) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " collection does not exist anymore: " . $collectionData->collectionId);
$collectionData->getChanges = false;
// make sure this is the last while loop
// no break 2 here, as we like to check the other folders too
$intervalStart -= $this->_heartbeatInterval;
}
// check that the syncstate still exists and is still valid
try {
$syncState = $this->_syncStateBackend->getSyncState($this->_device, $collectionData->folder);
// another process synchronized data of this folder already. let's skip it
if ($syncState->id !== $collectionData->syncState->id) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " syncstate changed during heartbeat interval for collection: " . $collectionData->folder->serverId);
$collectionData->getChanges = false;
// make sure this is the last while loop
// no break 2 here, as we like to check the other folders too
$intervalStart -= $this->_heartbeatInterval;
}
} catch (Syncroton_Exception_NotFound $senf) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " no syncstate found anymore for collection: " . $collectionData->folder->serverId);
$collectionData->syncState = null;
// make sure this is the last while loop
// no break 2 here, as we like to check the other folders too
$intervalStart -= $this->_heartbeatInterval;
}
// safe battery time by skipping folders which got synchronied less than Syncroton_Command_Ping::$quietTime seconds ago
if ( ! $collectionData->syncState instanceof Syncroton_Model_SyncState ||
($now->getTimestamp() - $collectionData->syncState->lastsync->getTimestamp()) < Syncroton_Registry::getQuietTime()) {
continue;
}
$dataController = Syncroton_Data_Factory::factory($collectionData->folder->class , $this->_device, $this->_syncTimeStamp);
// countinue immediately if there are any changes available
if($dataController->hasChanges($this->_contentStateBackend, $collectionData->folder, $collectionData->syncState)) {
break 2;
}
}
}
// See: http://www.tine20.org/forum/viewtopic.php?f=12&t=12146
//
// break if there are less than PingTimeout + 10 seconds left for the next loop
// otherwise the response will be returned after the client has finished his Ping
// request already maybe
- } while (time() - $intervalStart < $this->_heartbeatInterval - (Syncroton_Registry::getPingTimeout() + 10));
+ } while (Syncroton_Server::validateSession() && time() - $intervalStart < $this->_heartbeatInterval - (Syncroton_Registry::getPingTimeout() + 10));
}
foreach($this->_collections as $collectionData) {
$collectionChanges = 0;
/**
* keep track of entries added on server side
*/
$newContentStates = array();
/**
* keep track of entries deleted on server side
*/
$deletedContentStates = array();
// invalid collectionid provided
if (! ($collectionData->folder instanceof Syncroton_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 Syncroton_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 = Syncroton_Data_Factory::factory($collectionData->folder->class , $this->_device, $this->_syncTimeStamp);
$clientModifications = $this->_modifications[$collectionData->collectionId];
$serverModifications = array(
'added' => array(),
'changed' => array(),
'deleted' => array(),
);
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 ");
$serverModifications = $collectionData->syncState->pendingdata;
} elseif ($dataController->hasChanges($this->_contentStateBackend, $collectionData->folder, $collectionData->syncState)) {
// update _syncTimeStamp as $dataController->hasChanges might have spent some time
$this->_syncTimeStamp = new DateTime(null, new DateTimeZone('utc'));
try {
// fetch entries added since last sync
$allClientEntries = $this->_contentStateBackend->getFolderState(
$this->_device,
$collectionData->folder
);
$allServerEntries = $dataController->getServerEntries(
$collectionData->collectionId,
$collectionData->options['filterType']
);
// add entries
$serverDiff = array_diff($allServerEntries, $allClientEntries);
// add entries which produced problems during delete from client
$serverModifications['added'] = $clientModifications['forceAdd'];
// add entries not yet sent to client
$serverModifications['added'] = array_unique(array_merge($serverModifications['added'], $serverDiff));
// @todo still needed?
foreach($serverModifications['added'] as $id => $serverId) {
// skip entries added by client during this sync session
if(isset($clientModifications['added'][$serverId]) && !isset($clientModifications['forceAdd'][$serverId])) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped added entry: " . $serverId);
unset($serverModifications['added'][$id]);
}
}
// entries to be deleted
$serverModifications['deleted'] = array_diff($allClientEntries, $allServerEntries);
// fetch entries changed since last sync
$serverModifications['changed'] = $dataController->getChangedEntries(
$collectionData->collectionId,
$collectionData->syncState->lastsync,
$this->_syncTimeStamp,
$collectionData->options['filterType']
);
$serverModifications['changed'] = array_merge($serverModifications['changed'], $clientModifications['forceChange']);
foreach($serverModifications['changed'] as $id => $serverId) {
// skip entry, if it got changed by client during current sync
if(isset($clientModifications['changed'][$serverId]) && !isset($clientModifications['forceChange'][$serverId])) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped changed entry: " . $serverId);
unset($serverModifications['changed'][$id]);
}
// skip entry, make sure we don't sent entries already added by client in this request
else if (isset($clientModifications['added'][$serverId]) && !isset($clientModifications['forceAdd'][$serverId])) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " skipped change for added entry: " . $serverId);
unset($serverModifications['changed'][$id]);
}
}
// entries comeing in scope are already in $serverModifications['added'] and do not need to
// be send with $serverCanges
$serverModifications['changed'] = array_diff($serverModifications['changed'], $serverModifications['added']);
} catch (Exception $e) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->crit(__METHOD__ . '::' . __LINE__ . " Folder state checking failed: " . $e->getMessage());
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " Folder state checking failed: " . $e->getTraceAsString());
// Prevent from removing client entries when getServerEntries() fails
// @todo: should we set Status and break the loop here?
$serverModifications = array(
'added' => array(),
'changed' => array(),
'deleted' => array(),
);
}
}
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " found (added/changed/deleted) " . count($serverModifications['added']) . '/' . count($serverModifications['changed']) . '/' . count($serverModifications['deleted']) . ' entries for sync from server to client');
}
// collection header
$collection = $this->_outputDom->createElementNS('uri:AirSync', 'Collection');
if (!empty($collectionData->folder->class)) {
$collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Class', $collectionData->folder->class));
}
$syncKeyElement = $collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'SyncKey'));
$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($clientModifications['added'])) {
foreach($clientModifications['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($clientModifications['changed'])) {
foreach($clientModifications['changed'] as $serverId => $status) {
if ($status !== Syncroton_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)) {
// unset all truncation settings as entries are not allowed to be truncated during fetch
$fetchCollectionData = clone $collectionData;
// unset truncationSize
if (isset($fetchCollectionData->options['bodyPreferences']) && is_array($fetchCollectionData->options['bodyPreferences'])) {
foreach($fetchCollectionData->options['bodyPreferences'] as $key => $bodyPreference) {
unset($fetchCollectionData->options['bodyPreferences'][$key]['truncationSize']);
}
}
$fetchCollectionData->options['mimeTruncation'] = Syncroton_Command_Sync::TRUNCATE_NOTHING;
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
->getEntry($fetchCollectionData, $serverId)
->appendXML($applicationData, $this->_device);
$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());
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " unable to convert entry to xml: " . $e->getTraceAsString());
$fetch->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_OBJECT_NOT_FOUND));
}
}
}
if ($responses->hasChildNodes() === true) {
$collection->appendChild($responses);
}
$commands = $this->_outputDom->createElementNS('uri:AirSync', 'Commands');
foreach($serverModifications['added'] as $id => $serverId) {
if($collectionChanges == $collectionData->windowSize || $totalChanges + $collectionChanges >= $this->_globalWindowSize) {
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($serverModifications['added'][$id]);
# continue;
#
#} catch (Syncroton_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
->getEntry($collectionData, $serverId)
->appendXML($applicationData, $this->_device);
$commands->appendChild($add);
$collectionChanges++;
} catch (Syncroton_Exception_MemoryExhausted $seme) {
// continue to next entry, as there is not enough memory left for the current entry
// this will lead to MoreAvailable at the end and the entry will be synced during the next Sync command
if ($this->_logger instanceof Zend_Log)
$this->_logger->warn(__METHOD__ . '::' . __LINE__ . " memory exhausted for entry: " . $serverId);
continue;
} catch (Exception $e) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->warn(__METHOD__ . '::' . __LINE__ . " unable to convert entry to xml: " . $e->getMessage());
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " unable to convert entry to xml: " . $e->getTraceAsString());
}
// mark as sent to the client, even the conversion to xml might have failed
$newContentStates[] = new Syncroton_Model_Content(array(
'device_id' => $this->_device,
'folder_id' => $collectionData->folder,
'contentid' => $serverId,
'creation_time' => $this->_syncTimeStamp,
'creation_synckey' => $collectionData->syncState->counter + 1
));
unset($serverModifications['added'][$id]);
}
/**
* process entries changed on server side
*/
foreach($serverModifications['changed'] as $id => $serverId) {
if($collectionChanges == $collectionData->windowSize || $totalChanges + $collectionChanges >= $this->_globalWindowSize) {
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
->getEntry($collectionData, $serverId)
->appendXML($applicationData, $this->_device);
$commands->appendChild($change);
$collectionChanges++;
} catch (Syncroton_Exception_MemoryExhausted $seme) {
// continue to next entry, as there is not enough memory left for the current entry
// this will lead to MoreAvailable at the end and the entry will be synced during the next Sync command
if ($this->_logger instanceof Zend_Log)
$this->_logger->warn(__METHOD__ . '::' . __LINE__ . " memory exhausted for entry: " . $serverId);
continue;
} catch (Exception $e) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->warn(__METHOD__ . '::' . __LINE__ . " unable to convert entry to xml: " . $e->getMessage());
}
unset($serverModifications['changed'][$id]);
}
foreach($serverModifications['deleted'] as $id => $serverId) {
if($collectionChanges == $collectionData->windowSize || $totalChanges + $collectionChanges >= $this->_globalWindowSize) {
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);
$collectionChanges++;
} catch (Exception $e) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->warn(__METHOD__ . '::' . __LINE__ . " unable to convert entry to xml: " . $e->getMessage());
}
unset($serverModifications['deleted'][$id]);
}
$countOfPendingChanges = (count($serverModifications['added']) + count($serverModifications['changed']) + count($serverModifications['deleted']));
if ($countOfPendingChanges > 0) {
$collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'MoreAvailable'));
} else {
$serverModifications = null;
}
if ($commands->hasChildNodes() === true) {
$collection->appendChild($commands);
}
$totalChanges += $collectionChanges;
// increase SyncKey if needed
if ((
// sent the clients updates... ?
!empty($clientModifications['added']) ||
!empty($clientModifications['changed']) ||
!empty($clientModifications['deleted'])
) || (
// is the server sending updates to the client... ?
$commands->hasChildNodes() === true
) || (
// changed the pending data... ?
$collectionData->syncState->pendingdata != $serverModifications
)
) {
// ...then increase SyncKey
$collectionData->syncState->counter++;
}
$syncKeyElement->appendChild($this->_outputDom->createTextNode($collectionData->syncState->counter));
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " current synckey is ". $collectionData->syncState->counter);
if (!$emptySyncSupported || $collection->childNodes->length > 4 || $collectionData->syncState->counter != $collectionData->syncKey) {
$collections->appendChild($collection);
}
}
if (isset($collectionData->syncState) &&
$collectionData->syncState instanceof Syncroton_Model_ISyncState &&
$collectionData->syncState->counter != $collectionData->syncKey
) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " update syncState for collection: " . $collectionData->collectionId);
// store pending data in sync state when needed
if(isset($countOfPendingChanges) && $countOfPendingChanges > 0) {
$collectionData->syncState->pendingdata = array(
'added' => (array)$serverModifications['added'],
'changed' => (array)$serverModifications['changed'],
'deleted' => (array)$serverModifications['deleted']
);
} else {
$collectionData->syncState->pendingdata = null;
}
if (!empty($clientModifications['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 = clone $this->_syncTimeStamp;
// increment sync timestamp by 1 second
$collectionData->syncState->lastsync->modify('+1 sec');
try {
$transactionId = Syncroton_Registry::getTransactionManager()->startTransaction(Syncroton_Registry::getDatabase());
// store new synckey
$this->_syncStateBackend->create($collectionData->syncState, $keepPreviousSyncKey);
// store contentstates for new entries added to client
foreach($newContentStates as $state) {
$this->_contentStateBackend->create($state);
}
// remove contentstates for entries to be deleted on client
foreach($deletedContentStates as $state) {
$this->_contentStateBackend->delete($state);
}
Syncroton_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($clientModifications['added'])) {
foreach ($clientModifications['added'] as $added) {
$this->_contentStateBackend->delete($added['contentState']);
$dataController->deleteEntry($collectionData->collectionId, $added['serverId'], array());
}
}
Syncroton_Registry::getTransactionManager()->rollBack();
throw $zdse;
}
}
// store current filter type
try {
$folderState = $this->_folderBackend->get($collectionData->folder);
$folderState->lastfiltertype = $collectionData->options['filterType'];
if ($folderState->isDirty()) {
$this->_folderBackend->update($folderState);
}
} catch (Syncroton_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->warn(__METHOD__ . '::' . __LINE__ . ' failed to get folder state for: ' . $collectionData->collectionId);
}
}
if ($collections->hasChildNodes() === true) {
$sync->appendChild($collections);
}
if ($sync->hasChildNodes()) {
return $this->_outputDom;
}
return null;
}
/**
* remove Commands and Supported from collections XML tree
*
* @param DOMDocument $document
* @return DOMDocument
*/
protected function _cleanUpXML(DOMDocument $document)
{
$cleanedDocument = clone $document;
$xpath = new DomXPath($cleanedDocument);
$xpath->registerNamespace('AirSync', 'uri:AirSync');
$collections = $xpath->query("//AirSync:Sync/AirSync:Collections/AirSync:Collection");
// remove Commands and Supported elements
foreach ($collections as $collection) {
foreach (array('Commands', 'Supported') as $element) {
$childrenToRemove = $collection->getElementsByTagName($element);
foreach ($childrenToRemove as $childToRemove) {
$collection->removeChild($childToRemove);
}
}
}
return $cleanedDocument;
}
/**
* merge a partial XML document with the XML document from the previous request
*
* @param DOMDocument|null $requestBody
* @return SimpleXMLElement
*/
protected function _mergeSyncRequest($requestBody, Syncroton_Model_Device $device)
{
$lastSyncCollection = array();
if (!empty($device->lastsynccollection)) {
$lastSyncCollection = Zend_Json::decode($device->lastsynccollection);
if (!empty($lastSyncCollection['lastXML'])) {
$lastXML = new DOMDocument();
$lastXML->loadXML($lastSyncCollection['lastXML']);
}
}
if (! $requestBody instanceof DOMDocument && isset($lastXML) && $lastXML instanceof DOMDocument) {
$requestBody = $lastXML;
} elseif (! $requestBody instanceof DOMDocument) {
throw new Syncroton_Exception_UnexpectedValue('no xml body found');
}
if ($requestBody->getElementsByTagName('Partial')->length > 0) {
$partialBody = clone $requestBody;
$requestBody = $lastXML;
$xpath = new DomXPath($requestBody);
$xpath->registerNamespace('AirSync', 'uri:AirSync');
foreach ($partialBody->documentElement->childNodes as $child) {
if (! $child instanceof DOMElement) {
continue;
}
if ($child->tagName == 'Partial') {
continue;
}
if ($child->tagName == 'Collections') {
foreach ($child->getElementsByTagName('Collection') as $updatedCollection) {
$collectionId = $updatedCollection->getElementsByTagName('CollectionId')->item(0)->nodeValue;
$existingCollections = $xpath->query("//AirSync:Sync/AirSync:Collections/AirSync:Collection[AirSync:CollectionId='$collectionId']");
if ($existingCollections->length > 0) {
$existingCollection = $existingCollections->item(0);
foreach ($updatedCollection->childNodes as $updatedCollectionChild) {
if (! $updatedCollectionChild instanceof DOMElement) {
continue;
}
$duplicateChild = $existingCollection->getElementsByTagName($updatedCollectionChild->tagName);
if ($duplicateChild->length > 0) {
$existingCollection->replaceChild($requestBody->importNode($updatedCollectionChild, TRUE), $duplicateChild->item(0));
} else {
$existingCollection->appendChild($requestBody->importNode($updatedCollectionChild, TRUE));
}
}
} else {
$importedCollection = $requestBody->importNode($updatedCollection, TRUE);
}
}
} else {
$duplicateChild = $xpath->query("//AirSync:Sync/AirSync:{$child->tagName}");
if ($duplicateChild->length > 0) {
$requestBody->documentElement->replaceChild($requestBody->importNode($child, TRUE), $duplicateChild->item(0));
} else {
$requestBody->documentElement->appendChild($requestBody->importNode($child, TRUE));
}
}
}
}
$lastSyncCollection['lastXML'] = $this->_cleanUpXML($requestBody)->saveXML();
$device->lastsynccollection = Zend_Json::encode($lastSyncCollection);
return $requestBody;
}
}
diff --git a/lib/ext/Syncroton/Model/AXMLEntry.php b/lib/ext/Syncroton/Model/AXMLEntry.php
index d243aea..e2332b9 100644
--- a/lib/ext/Syncroton/Model/AXMLEntry.php
+++ b/lib/ext/Syncroton/Model/AXMLEntry.php
@@ -1,326 +1,323 @@
<?php
/**
* Syncroton
*
* @package Syncroton
* @subpackage Model
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2012-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* abstract class to handle ActiveSync entry
*
* @package Syncroton
* @subpackage Model
*/
abstract class Syncroton_Model_AXMLEntry extends Syncroton_Model_AEntry implements Syncroton_Model_IXMLEntry
{
protected $_xmlBaseElement;
protected $_properties = array();
protected $_dateTimeFormat = "Y-m-d\TH:i:s.000\Z";
/**
* (non-PHPdoc)
* @see Syncroton_Model_IEntry::__construct()
*/
public function __construct($properties = null)
{
if ($properties instanceof SimpleXMLElement) {
$this->setFromSimpleXMLElement($properties);
} elseif (is_array($properties)) {
$this->setFromArray($properties);
}
$this->_isDirty = false;
}
/**
* (non-PHPdoc)
* @see Syncroton_Model_IEntry::appendXML()
*/
public function appendXML(DOMElement $domParrent, Syncroton_Model_IDevice $device)
{
$this->_addXMLNamespaces($domParrent);
foreach($this->_elements as $elementName => $value) {
// skip empty values
if($value === null || $value === '' || (is_array($value) && empty($value))) {
continue;
}
list ($nameSpace, $elementProperties) = $this->_getElementProperties($elementName);
if ($nameSpace == 'Internal') {
continue;
}
$elementVersion = isset($elementProperties['supportedSince']) ? $elementProperties['supportedSince'] : '12.0';
if (version_compare($device->acsversion, $elementVersion, '<')) {
continue;
}
$nameSpace = 'uri:' . $nameSpace;
if (isset($elementProperties['childElement'])) {
$element = $domParrent->ownerDocument->createElementNS($nameSpace, ucfirst($elementName));
foreach($value as $subValue) {
$subElement = $domParrent->ownerDocument->createElementNS($nameSpace, ucfirst($elementProperties['childElement']));
$this->_appendXMLElement($device, $subElement, $elementProperties, $subValue);
$element->appendChild($subElement);
}
$domParrent->appendChild($element);
- }
- else if ($elementProperties['type'] == 'container' && !empty($elementProperties['multiple'])) {
+ } else if ($elementProperties['type'] == 'container' && !empty($elementProperties['multiple'])) {
foreach ($value as $element) {
$container = $domParrent->ownerDocument->createElementNS($nameSpace, ucfirst($elementName));
$element->appendXML($container, $device);
$domParrent->appendChild($container);
}
- }
- else if ($elementProperties['type'] == 'none') {
+ } else if ($elementProperties['type'] == 'none') {
if ($value) {
$element = $domParrent->ownerDocument->createElementNS($nameSpace, ucfirst($elementName));
$domParrent->appendChild($element);
}
- }
- else {
+ } else {
$element = $domParrent->ownerDocument->createElementNS($nameSpace, ucfirst($elementName));
$this->_appendXMLElement($device, $element, $elementProperties, $value);
$domParrent->appendChild($element);
}
}
}
/**
* (non-PHPdoc)
* @see Syncroton_Model_IEntry::getProperties()
*/
public function getProperties($selectedNamespace = null)
{
$properties = array();
foreach($this->_properties as $namespace => $namespaceProperties) {
if ($selectedNamespace !== null && $namespace != $selectedNamespace) {
continue;
}
$properties = array_merge($properties, array_keys($namespaceProperties));
}
return $properties;
}
/**
* set properties from SimpleXMLElement object
*
* @param SimpleXMLElement $xmlCollection
* @throws InvalidArgumentException
*/
public function setFromSimpleXMLElement(SimpleXMLElement $properties)
{
if (!in_array($properties->getName(), (array) $this->_xmlBaseElement)) {
throw new InvalidArgumentException('Unexpected element name: ' . $properties->getName());
}
foreach (array_keys($this->_properties) as $namespace) {
if ($namespace == 'Internal') {
continue;
}
$this->_parseNamespace($namespace, $properties);
}
return;
}
/**
* add needed xml namespaces to DomDocument
*
* @param unknown_type $domParrent
*/
protected function _addXMLNamespaces(DOMElement $domParrent)
{
foreach($this->_properties as $namespace => $namespaceProperties) {
// don't add default namespace again
if($domParrent->ownerDocument->documentElement->namespaceURI != 'uri:'.$namespace) {
$domParrent->ownerDocument->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:'.$namespace, 'uri:'.$namespace);
}
}
}
protected function _appendXMLElement(Syncroton_Model_IDevice $device, DOMElement $element, $elementProperties, $value)
{
if ($value instanceof Syncroton_Model_IEntry) {
$value->appendXML($element, $device);
} else {
if ($value instanceof DateTime) {
$value = $value->format($this->_dateTimeFormat);
} elseif (isset($elementProperties['encoding']) && $elementProperties['encoding'] == 'base64') {
if (is_resource($value)) {
rewind($value);
$value = stream_get_contents($value);
}
$value = base64_encode($value);
}
if ($elementProperties['type'] == 'byteArray') {
$element->setAttributeNS('uri:Syncroton', 'Syncroton:encoding', 'opaque');
// encode to base64; the wbxml encoder will base64_decode it again
// this way we can also transport data, which would break the xmlparser otherwise
$element->appendChild($element->ownerDocument->createCDATASection(base64_encode($value)));
} else {
// strip off any non printable control characters
if (!ctype_print($value)) {
$value = $this->_removeControlChars($value);
}
$element->appendChild($element->ownerDocument->createTextNode($this->_enforceUTF8($value)));
}
}
}
/**
* removed control chars from string which are not allowd in XML values
*
* @param string|array $_dirty
* @return string
*/
protected function _removeControlChars($dirty)
{
return preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F]/', null, $dirty);
}
/**
* enforce >valid< utf-8 encoding
*
* @param string $dirty the string with maybe invalid utf-8 data
* @return string string with valid utf-8
*/
protected function _enforceUTF8($dirty)
{
if (function_exists('iconv')) {
if (($clean = @iconv('UTF-8', 'UTF-8//IGNORE', $dirty)) !== false) {
return $clean;
}
}
if (function_exists('mb_convert_encoding')) {
if (($clean = mb_convert_encoding($dirty, 'UTF-8', 'UTF-8')) !== false) {
return $clean;
}
}
return $dirty;
}
/**
*
* @param unknown_type $element
* @throws InvalidArgumentException
* @return multitype:unknown
*/
protected function _getElementProperties($element)
{
foreach($this->_properties as $namespace => $namespaceProperties) {
if (array_key_exists($element, $namespaceProperties)) {
return array($namespace, $namespaceProperties[$element]);
}
}
throw new InvalidArgumentException("$element is no valid property of " . get_class($this));
}
protected function _parseNamespace($nameSpace, SimpleXMLElement $properties)
{
// fetch data from Contacts namespace
$children = $properties->children("uri:$nameSpace");
foreach ($children as $elementName => $xmlElement) {
$elementName = lcfirst($elementName);
if (!isset($this->_properties[$nameSpace][$elementName])) {
continue;
}
list (, $elementProperties) = $this->_getElementProperties($elementName);
switch ($elementProperties['type']) {
case 'container':
if (!empty($elementProperties['multiple'])) {
$property = (array) $this->$elementName;
+
if (isset($elementProperties['class'])) {
$property[] = new $elementProperties['class']($xmlElement);
} else {
$property[] = (string) $xmlElement;
}
- }
- else if (isset($elementProperties['childElement'])) {
+ } else if (isset($elementProperties['childElement'])) {
$property = array();
$childElement = ucfirst($elementProperties['childElement']);
foreach ($xmlElement->$childElement as $subXmlElement) {
if (isset($elementProperties['class'])) {
$property[] = new $elementProperties['class']($subXmlElement);
} else {
$property[] = (string) $subXmlElement;
}
}
} else {
$subClassName = isset($elementProperties['class']) ? $elementProperties['class'] : get_class($this) . ucfirst($elementName);
$property = new $subClassName($xmlElement);
}
break;
case 'datetime':
$property = new DateTime((string) $xmlElement, new DateTimeZone('UTC'));
break;
case 'number':
$property = (int) $xmlElement;
break;
default:
$property = (string) $xmlElement;
break;
}
if (isset($elementProperties['encoding']) && $elementProperties['encoding'] == 'base64') {
$property = base64_decode($property);
}
$this->$elementName = $property;
}
}
public function &__get($name)
{
$this->_getElementProperties($name);
return $this->_elements[$name];
}
public function __set($name, $value)
{
list ($nameSpace, $properties) = $this->_getElementProperties($name);
if ($properties['type'] == 'datetime' && !$value instanceof DateTime) {
throw new InvalidArgumentException("value for $name must be an instance of DateTime");
}
if (!array_key_exists($name, $this->_elements) || $this->_elements[$name] != $value) {
$this->_elements[$name] = $value;
$this->_isDirty = true;
}
}
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Registry.php b/lib/ext/Syncroton/Registry.php
index f67e2d3..e129f8d 100644
--- a/lib/ext/Syncroton/Registry.php
+++ b/lib/ext/Syncroton/Registry.php
@@ -1,445 +1,481 @@
<?php
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/license/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@zend.com so we can send you a copy immediately.
*
* @package Syncroton
* @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
* @version $Id: Registry.php 10020 2009-08-18 14:34:09Z j.fischer@metaways.de $
*/
/**
* Generic storage class helps to manage global data.
*
* @package Syncroton
* @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Syncroton_Registry extends ArrayObject
{
const CALENDAR_DATA_CLASS = 'calendar_data_class';
const CONTACTS_DATA_CLASS = 'contacts_data_class';
const EMAIL_DATA_CLASS = 'email_data_class';
const NOTES_DATA_CLASS = 'notes_data_class';
const TASKS_DATA_CLASS = 'tasks_data_class';
const GAL_DATA_CLASS = 'gal_data_class';
const DEFAULT_POLICY = 'default_policy';
const PING_TIMEOUT = 'ping_timeout';
const PING_INTERVAL = 'ping_interval';
const QUIET_TIME = 'quiet_time';
+ const SESSION_VALIDATOR = 'session_validator';
const DATABASE = 'database';
const TRANSACTIONMANAGER = 'transactionmanager';
const CONTENTSTATEBACKEND = 'contentstatebackend';
const DEVICEBACKEND = 'devicebackend';
const FOLDERBACKEND = 'folderbackend';
const POLICYBACKEND = 'policybackend';
const SYNCSTATEBACKEND = 'syncstatebackend';
+ const LOGGERBACKEND = 'loggerBackend';
/**
* Class name of the singleton registry object.
* @var string
*/
private static $_registryClassName = 'Syncroton_Registry';
/**
* Registry object provides storage for shared objects.
* @var Syncroton_Registry
*/
private static $_registry = null;
/**
* Retrieves the default registry instance.
*
* @return Syncroton_Registry
*/
public static function getInstance()
{
if (self::$_registry === null) {
self::init();
}
return self::$_registry;
}
/**
* @return Zend_Db_Adapter_Abstract
*/
public static function getDatabase()
{
return self::get(self::DATABASE);
}
/**
* return transaction manager class
*
* @return Syncroton_TransactionManagerInterface
*/
public static function getTransactionManager()
{
if (!self::isRegistered(self::TRANSACTIONMANAGER)) {
self::set(self::TRANSACTIONMANAGER, Syncroton_TransactionManager::getInstance());
}
return self::get(self::TRANSACTIONMANAGER);
}
/**
* Set the default registry instance to a specified instance.
*
* @param Syncroton_Registry $registry An object instance of type Syncroton_Registry,
* or a subclass.
* @return void
* @throws Zend_Exception if registry is already initialized.
*/
public static function setInstance(Syncroton_Registry $registry)
{
if (self::$_registry !== null) {
require_once 'Zend/Exception.php';
throw new Zend_Exception('Registry is already initialized');
}
self::setClassName(get_class($registry));
self::$_registry = $registry;
}
/**
* Initialize the default registry instance.
*
* @return void
*/
protected static function init()
{
self::setInstance(new self::$_registryClassName());
}
/**
* Set the class name to use for the default registry instance.
* Does not affect the currently initialized instance, it only applies
* for the next time you instantiate.
*
* @param string $registryClassName
* @return void
* @throws Zend_Exception if the registry is initialized or if the
* class name is not valid.
*/
public static function setClassName($registryClassName = 'Syncroton_Registry')
{
if (self::$_registry !== null) {
require_once 'Zend/Exception.php';
throw new Zend_Exception('Registry is already initialized');
}
if (!is_string($registryClassName)) {
require_once 'Zend/Exception.php';
throw new Zend_Exception("Argument is not a class name");
}
/**
* @see Zend_Loader
*/
if (!class_exists($registryClassName)) {
require_once 'Zend/Loader.php';
Zend_Loader::loadClass($registryClassName);
}
self::$_registryClassName = $registryClassName;
}
/**
* Unset the default registry instance.
* Primarily used in tearDown() in unit tests.
* @returns void
*/
public static function _unsetInstance()
{
self::$_registry = null;
}
/**
* getter method, basically same as offsetGet().
*
* This method can be called from an object of type Syncroton_Registry, or it
* can be called statically. In the latter case, it uses the default
* static instance stored in the class.
*
* @param string $index - get the value associated with $index
* @return mixed
* @throws Zend_Exception if no entry is registerd for $index.
*/
public static function get($index)
{
$instance = self::getInstance();
if (!$instance->offsetExists($index)) {
require_once 'Zend/Exception.php';
throw new Zend_Exception("No entry is registered for key '$index'");
}
return $instance->offsetGet($index);
}
/**
* returns content state backend
*
* creates Syncroton_Backend_Content on the fly if not before via
* Syncroton_Registry::set(self::CONTENTSTATEBACKEND, $backend);
*
* @return Syncroton_Backend_IContent
*/
public static function getContentStateBackend()
{
if (!self::isRegistered(self::CONTENTSTATEBACKEND)) {
self::set(self::CONTENTSTATEBACKEND, new Syncroton_Backend_Content(self::getDatabase()));
}
return self::get(self::CONTENTSTATEBACKEND);
}
/**
* returns device backend
*
* creates Syncroton_Backend_Device on the fly if not before via
* Syncroton_Registry::set(self::DEVICEBACKEND, $backend);
*
* @return Syncroton_Backend_IDevice
*/
public static function getDeviceBackend()
{
if (!self::isRegistered(self::DEVICEBACKEND)) {
self::set(self::DEVICEBACKEND, new Syncroton_Backend_Device(self::getDatabase()));
}
return self::get(self::DEVICEBACKEND);
}
/**
* returns folder backend
*
* creates Syncroton_Backend_Folder on the fly if not before via
* Syncroton_Registry::set(self::FOLDERBACKEND, $backend);
*
* @return Syncroton_Backend_IFolder
*/
public static function getFolderBackend()
{
if (!self::isRegistered(self::FOLDERBACKEND)) {
self::set(self::FOLDERBACKEND, new Syncroton_Backend_Folder(self::getDatabase()));
}
return self::get(self::FOLDERBACKEND);
}
-
+
/**
* Return maximum ping interval (HeartbeatInterval) value (in seconds)
*
* @return int
*/
public static function getPingInterval()
{
if (!self::isRegistered(self::PING_INTERVAL)) {
return 3540; // 59 minutes limit defined in Activesync protocol spec.
}
return self::get(self::PING_INTERVAL);
}
+
+ /**
+ /**
+ * Return maximum ping interval (HeartbeatInterval) value (in seconds)
+ *
+ * @return int
+ */
+ public static function getMaxPingInterval()
+ {
+ if (!self::isRegistered(self::MAX_PING_INTERVAL)) {
+ return Syncroton_Command_Ping::MAX_PING_INTERVAL;
+ }
+
+ return self::get(self::MAX_PING_INTERVAL);
+ }
/**
* return ping timeout
*
* sleep "ping timeout" seconds between folder checks in Ping and Sync command
*
* @return int
*/
public static function getPingTimeout()
{
if (!self::isRegistered(self::PING_TIMEOUT)) {
return 60;
}
return self::get(self::PING_TIMEOUT);
}
/**
* returns policy backend
*
* creates Syncroton_Backend_Policy on the fly if not set before via
* Syncroton_Registry::set(self::POLICYBACKEND, $backend);
*
* @return Syncroton_Backend_ISyncState
*/
public static function getPolicyBackend()
{
if (!self::isRegistered(self::POLICYBACKEND)) {
self::set(self::POLICYBACKEND, new Syncroton_Backend_Policy(self::getDatabase()));
}
return self::get(self::POLICYBACKEND);
}
/**
* return quiet time
*
* don't check folders if last sync was "quiet time" seconds ago
*
* @return int
*/
public static function getQuietTime()
{
if (!self::isRegistered(self::QUIET_TIME)) {
return 180;
}
return self::get(self::QUIET_TIME);
}
-
+
+ /**
+ * return session validation function
+ *
+ * This function is used in long running requests like ping & sync to
+ * validate user session. Returns false if session is not valid any longer
+ *
+ * @return callable
+ */
+ public static function getSessionValidator()
+ {
+ if (!self::isRegistered(self::SESSION_VALIDATOR)) {
+ self::set(self::SESSION_VALIDATOR, function() {
+ return true;
+ });
+ }
+
+ return self::get(self::SESSION_VALIDATOR);
+ }
+
/**
* returns syncstate backend
*
* creates Syncroton_Backend_SyncState on the fly if not before via
* Syncroton_Registry::set(self::SYNCSTATEBACKEND, $backend);
*
* @return Syncroton_Backend_ISyncState
*/
public static function getSyncStateBackend()
{
if (!self::isRegistered(self::SYNCSTATEBACKEND)) {
self::set(self::SYNCSTATEBACKEND, new Syncroton_Backend_SyncState(self::getDatabase()));
}
return self::get(self::SYNCSTATEBACKEND);
}
/**
* setter method, basically same as offsetSet().
*
* This method can be called from an object of type Syncroton_Registry, or it
* can be called statically. In the latter case, it uses the default
* static instance stored in the class.
*
* @param string $index The location in the ArrayObject in which to store
* the value.
* @param mixed $value The object to store in the ArrayObject.
* @return void
*/
public static function set($index, $value)
{
$instance = self::getInstance();
$instance->offsetSet($index, $value);
}
public static function setDatabase(Zend_Db_Adapter_Abstract $db)
{
self::set(self::DATABASE, $db);
}
public static function setCalendarDataClass($className)
{
if (!class_exists($className)) {
throw new InvalidArgumentException('invalid $_className provided');
}
self::set(self::CALENDAR_DATA_CLASS, $className);
}
public static function setContactsDataClass($className)
{
if (!class_exists($className)) {
throw new InvalidArgumentException('invalid $_className provided');
}
self::set(self::CONTACTS_DATA_CLASS, $className);
}
public static function setEmailDataClass($className)
{
if (!class_exists($className)) {
throw new InvalidArgumentException('invalid $_className provided');
}
self::set(self::EMAIL_DATA_CLASS, $className);
}
public static function setNotesDataClass($className)
{
if (!class_exists($className)) {
throw new InvalidArgumentException('invalid $_className provided');
}
self::set(self::NOTES_DATA_CLASS, $className);
}
public static function setTasksDataClass($className)
{
if (!class_exists($className)) {
throw new InvalidArgumentException('invalid $_className provided');
}
self::set(self::TASKS_DATA_CLASS, $className);
}
public static function setGALDataClass($className)
{
if (!class_exists($className)) {
throw new InvalidArgumentException('invalid $_className provided');
}
self::set(self::GAL_DATA_CLASS, $className);
}
public static function setTransactionManager($manager)
{
self::set(self::TRANSACTIONMANAGER, $manager);
}
/**
* 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);
}
/**
* Constructs a parent ArrayObject with default
* ARRAY_AS_PROPS to allow acces as an object
*
* @param array $array data array
* @param integer $flags ArrayObject flags
*/
public function __construct($array = array(), $flags = parent::ARRAY_AS_PROPS)
{
parent::__construct($array, $flags);
}
/**
* @param string $index
* @returns mixed
*
* Workaround for http://bugs.php.net/bug.php?id=40442 (ZF-960).
*/
public function offsetExists($index)
{
return array_key_exists($index, $this);
}
}
diff --git a/lib/ext/Syncroton/Server.php b/lib/ext/Syncroton/Server.php
index 0fa4855..d60ab9a 100644
--- a/lib/ext/Syncroton/Server.php
+++ b/lib/ext/Syncroton/Server.php
@@ -1,446 +1,452 @@
<?php
/**
* Syncroton
*
* @package Syncroton
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2009-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle incoming http ActiveSync requests
*
* @package Syncroton
*/
class Syncroton_Server
{
const PARAMETER_ATTACHMENTNAME = 0;
const PARAMETER_COLLECTIONID = 1;
const PARAMETER_ITEMID = 3;
const PARAMETER_OPTIONS = 7;
const MAX_HEARTBEAT_INTERVAL = 3540; // 59 minutes
protected $_body;
/**
* informations about the currently device
*
* @var Syncroton_Backend_IDevice
*/
protected $_deviceBackend;
/**
* @var Zend_Log
*/
protected $_logger;
/**
* @var Zend_Controller_Request_Http
*/
protected $_request;
protected $_userId;
public function __construct($userId, Zend_Controller_Request_Http $request = null, $body = null)
{
if (Syncroton_Registry::isRegistered('loggerBackend')) {
$this->_logger = Syncroton_Registry::get('loggerBackend');
}
$this->_userId = $userId;
$this->_request = $request instanceof Zend_Controller_Request_Http ? $request : new Zend_Controller_Request_Http();
$this->_body = $body !== null ? $body : fopen('php://input', 'r');
$this->_deviceBackend = Syncroton_Registry::getDeviceBackend();
}
public function handle()
{
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . ' REQUEST METHOD: ' . $this->_request->getMethod());
switch($this->_request->getMethod()) {
case 'OPTIONS':
$this->_handleOptions();
break;
case 'POST':
$this->_handlePost();
break;
case 'GET':
echo "It works!<br>Your userid is: {$this->_userId} and your IP address is: {$_SERVER['REMOTE_ADDR']}.";
break;
}
}
/**
* handle options request
*/
protected function _handleOptions()
{
$command = new Syncroton_Command_Options();
$this->_sendHeaders($command->getHeaders());
}
protected function _sendHeaders(array $headers)
{
foreach ($headers as $name => $value) {
header($name . ': ' . $value);
}
}
/**
* handle post request
*/
protected function _handlePost()
{
$requestParameters = $this->_getRequestParameters($this->_request);
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . ' REQUEST ' . print_r($requestParameters, true));
$className = 'Syncroton_Command_' . $requestParameters['command'];
if(!class_exists($className)) {
- if ($this->_logger instanceof Zend_Log)
- $this->_logger->crit(__METHOD__ . '::' . __LINE__ . " command not supported: " . $requestParameters['command']);
+ if ($this->_logger instanceof Zend_Log)
+ $this->_logger->crit(__METHOD__ . '::' . __LINE__ . " command not supported: " . $requestParameters['command']);
header("HTTP/1.1 501 not implemented");
return;
}
// get user device
$device = $this->_getUserDevice($this->_userId, $requestParameters);
if ($requestParameters['contentType'] == 'application/vnd.ms-sync.wbxml' || $requestParameters['contentType'] == 'application/vnd.ms-sync') {
// decode wbxml request
try {
$decoder = new Syncroton_Wbxml_Decoder($this->_body);
$requestBody = $decoder->decode();
if ($this->_logger instanceof Zend_Log) {
$requestBody->formatOutput = true;
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " xml request:\n" . $requestBody->saveXML());
}
} catch(Syncroton_Wbxml_Exception_UnexpectedEndOfFile $e) {
$requestBody = NULL;
}
} else {
$requestBody = $this->_body;
}
header("MS-Server-ActiveSync: 14.00.0536.000");
// avoid sending HTTP header "Content-Type: text/html" for empty sync responses
ini_set('default_mimetype', 'application/vnd.ms-sync.wbxml');
try {
$command = new $className($requestBody, $device, $requestParameters);
$command->handle();
$response = $command->getResponse();
} catch (Syncroton_Exception_ProvisioningNeeded $sepn) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " provisioning needed");
header("HTTP/1.1 449 Retry after sending a PROVISION command");
if (version_compare($device->acsversion, '14.0', '>=')) {
$response = $sepn->domDocument;
} else {
// pre 14.0 method
return;
}
} catch (Exception $e) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->crit(__METHOD__ . '::' . __LINE__ . " unexpected exception occured: " . get_class($e));
if ($this->_logger instanceof Zend_Log)
$this->_logger->crit(__METHOD__ . '::' . __LINE__ . " exception message: " . $e->getMessage());
if ($this->_logger instanceof Zend_Log)
$this->_logger->crit(__METHOD__ . '::' . __LINE__ . " " . $e->getTraceAsString());
header("HTTP/1.1 500 Internal server error");
return;
}
if ($response instanceof DOMDocument) {
if ($this->_logger instanceof Zend_Log) {
$this->_logDomDocument(Zend_Log::DEBUG, $response, __METHOD__, __LINE__);
}
if (isset($command) && $command instanceof Syncroton_Command_ICommand) {
$this->_sendHeaders($command->getHeaders());
}
$outputStream = fopen("php://temp", 'r+');
$encoder = new Syncroton_Wbxml_Encoder($outputStream, 'UTF-8', 3);
try {
$encoder->encode($response);
} catch (Syncroton_Wbxml_Exception $swe) {
if ($this->_logger instanceof Zend_Log) {
$this->_logger->err(__METHOD__ . '::' . __LINE__ . " Could not encode output: " . $swe);
- $this->_logDomDocument(Zend_Log::ERR, $response, __METHOD__, __LINE__);
+ $this->_logDomDocument(Zend_Log::WARN, $response, __METHOD__, __LINE__);
}
header("HTTP/1.1 500 Internal server error");
return;
}
if ($requestParameters['acceptMultipart'] == true) {
$parts = $command->getParts();
// output multipartheader
$bodyPartCount = 1 + count($parts);
// number of parts (4 bytes)
$header = pack('i', $bodyPartCount);
$partOffset = 4 + (($bodyPartCount * 2) * 4);
// wbxml body start and length
$streamStat = fstat($outputStream);
$header .= pack('ii', $partOffset, $streamStat['size']);
$partOffset += $streamStat['size'];
// calculate start and length of parts
foreach ($parts as $partId => $partStream) {
rewind($partStream);
$streamStat = fstat($partStream);
// part start and length
$header .= pack('ii', $partOffset, $streamStat['size']);
$partOffset += $streamStat['size'];
}
echo $header;
}
// output body
rewind($outputStream);
fpassthru($outputStream);
// output multiparts
if (isset($parts)) {
foreach ($parts as $partStream) {
rewind($partStream);
fpassthru($partStream);
}
}
}
}
/**
* write (possible big) DOMDocument in smaller chunks to log file
*
* @param unknown $priority
* @param DOMDocument $dom
* @param string $method
* @param int $line
*/
protected function _logDomDocument($priority, DOMDocument $dom, $method, $line)
{
$loops = 0;
$tempStream = fopen('php://temp/maxmemory:5242880', 'r+');
$dom->formatOutput = true;
fwrite($tempStream, $dom->saveXML());
$dom->formatOutput = false;
rewind($tempStream);
// log data in 1MByte chunks
while (!feof($tempStream)) {
$this->_logger->log($method . '::' . $line . " xml response($loops):\n" . fread($tempStream, 1048576), $priority);
$loops++;
}
fclose($tempStream);
}
/**
* return request params
*
* @return array
*/
protected function _getRequestParameters(Zend_Controller_Request_Http $request)
{
if (strpos($request->getRequestUri(), '&') === false) {
$commands = array(
0 => 'Sync',
1 => 'SendMail',
2 => 'SmartForward',
3 => 'SmartReply',
4 => 'GetAttachment',
9 => 'FolderSync',
10 => 'FolderCreate',
11 => 'FolderDelete',
12 => 'FolderUpdate',
13 => 'MoveItems',
14 => 'GetItemEstimate',
15 => 'MeetingResponse',
16 => 'Search',
17 => 'Settings',
18 => 'Ping',
19 => 'ItemOperations',
20 => 'Provision',
21 => 'ResolveRecipients',
22 => 'ValidateCert'
);
$requestParameters = substr($request->getRequestUri(), strpos($request->getRequestUri(), '?'));
$stream = fopen("php://temp", 'r+');
fwrite($stream, base64_decode($requestParameters));
rewind($stream);
// unpack the first 4 bytes
$unpacked = unpack('CprotocolVersion/Ccommand/vlocale', fread($stream, 4));
// 140 => 14.0
$protocolVersion = substr($unpacked['protocolVersion'], 0, -1) . '.' . substr($unpacked['protocolVersion'], -1);
$command = $commands[$unpacked['command']];
$locale = $unpacked['locale'];
// unpack deviceId
$length = ord(fread($stream, 1));
if ($length > 0) {
$toUnpack = fread($stream, $length);
$unpacked = unpack("H" . ($length * 2) . "string", $toUnpack);
$deviceId = $unpacked['string'];
}
// unpack policyKey
$length = ord(fread($stream, 1));
if ($length > 0) {
$unpacked = unpack('Vstring', fread($stream, $length));
$policyKey = $unpacked['string'];
- }
+ }
- // unpack device type
+ // unpack device type
$length = ord(fread($stream, 1));
- if ($length > 0) {
+ if ($length > 0) {
$unpacked = unpack('A' . $length . 'string', fread($stream, $length));
$deviceType = $unpacked['string'];
- }
+ }
while (! feof($stream)) {
$tag = ord(fread($stream, 1));
$length = ord(fread($stream, 1));
switch ($tag) {
case self::PARAMETER_ATTACHMENTNAME:
$unpacked = unpack('A' . $length . 'string', fread($stream, $length));
$attachmentName = $unpacked['string'];
break;
case self::PARAMETER_COLLECTIONID:
$unpacked = unpack('A' . $length . 'string', fread($stream, $length));
$collectionId = $unpacked['string'];
break;
case self::PARAMETER_ITEMID:
$unpacked = unpack('A' . $length . 'string', fread($stream, $length));
$itemId = $unpacked['string'];
break;
case self::PARAMETER_OPTIONS:
$options = ord(fread($stream, 1));
$saveInSent = !!($options & 0x01);
$acceptMultiPart = !!($options & 0x02);
break;
default:
if ($this->_logger instanceof Zend_Log)
$this->_logger->crit(__METHOD__ . '::' . __LINE__ . " found unhandled command parameters");
}
}
$result = array(
'protocolVersion' => $protocolVersion,
'command' => $command,
'deviceId' => $deviceId,
'deviceType' => isset($deviceType) ? $deviceType : null,
'policyKey' => isset($policyKey) ? $policyKey : null,
'saveInSent' => isset($saveInSent) ? $saveInSent : false,
'collectionId' => isset($collectionId) ? $collectionId : null,
'itemId' => isset($itemId) ? $itemId : null,
'attachmentName' => isset($attachmentName) ? $attachmentName : null,
'acceptMultipart' => isset($acceptMultiPart) ? $acceptMultiPart : false
);
- } else {
+ } else {
$result = array(
'protocolVersion' => $request->getServer('HTTP_MS_ASPROTOCOLVERSION'),
'command' => $request->getQuery('Cmd'),
'deviceId' => $request->getQuery('DeviceId'),
'deviceType' => $request->getQuery('DeviceType'),
'policyKey' => $request->getServer('HTTP_X_MS_POLICYKEY'),
- 'saveInSent' => $request->getQuery('SaveInSent') == 'T',
- 'collectionId' => $request->getQuery('CollectionId'),
+ 'saveInSent' => $request->getQuery('SaveInSent') == 'T',
+ 'collectionId' => $request->getQuery('CollectionId'),
'itemId' => $request->getQuery('ItemId'),
'attachmentName' => $request->getQuery('AttachmentName'),
- 'acceptMultipart' => $request->getServer('HTTP_MS_ASACCEPTMULTIPART') == 'T'
+ 'acceptMultipart' => $request->getServer('HTTP_MS_ASACCEPTMULTIPART') == 'T'
);
}
-
- $result['userAgent'] = $request->getServer('HTTP_USER_AGENT', $result['deviceType']);
- $result['contentType'] = $request->getServer('CONTENT_TYPE');
+
+ $result['userAgent'] = $request->getServer('HTTP_USER_AGENT', $result['deviceType']);
+ $result['contentType'] = $request->getServer('CONTENT_TYPE');
return $result;
}
/**
* get existing device of owner or create new device for owner
*
* @param unknown_type $ownerId
* @param unknown_type $deviceId
* @param unknown_type $deviceType
* @param unknown_type $userAgent
* @param unknown_type $protocolVersion
* @return Syncroton_Model_Device
*/
protected function _getUserDevice($ownerId, $requestParameters)
{
try {
$device = $this->_deviceBackend->getUserDevice($ownerId, $requestParameters['deviceId']);
$device->useragent = $requestParameters['userAgent'];
$device->acsversion = $requestParameters['protocolVersion'];
if ($device->isDirty()) {
$device = $this->_deviceBackend->update($device);
}
} catch (Syncroton_Exception_NotFound $senf) {
$device = $this->_deviceBackend->create(new Syncroton_Model_Device(array(
'owner_id' => $ownerId,
'deviceid' => $requestParameters['deviceId'],
'devicetype' => $requestParameters['deviceType'],
'useragent' => $requestParameters['userAgent'],
'acsversion' => $requestParameters['protocolVersion'],
- 'policyId' => Syncroton_Registry::isRegistered(Syncroton_Registry::DEFAULT_POLICY) ? Syncroton_Registry::get(Syncroton_Registry::DEFAULT_POLICY) : null
+ 'policyId' => Syncroton_Registry::isRegistered(Syncroton_Registry::DEFAULT_POLICY) ? Syncroton_Registry::get(Syncroton_Registry::DEFAULT_POLICY) : null
)));
}
return $device;
}
+
+ public static function validateSession()
+ {
+ $validatorFunction = Syncroton_Registry::getSessionValidator();
+ return $validatorFunction();
+ }
}
diff --git a/lib/ext/Syncroton/Wbxml/Dtd/ActiveSync/CodePage18.php b/lib/ext/Syncroton/Wbxml/Dtd/ActiveSync/CodePage18.php
index 6d14fe9..0bd3383 100644
--- a/lib/ext/Syncroton/Wbxml/Dtd/ActiveSync/CodePage18.php
+++ b/lib/ext/Syncroton/Wbxml/Dtd/ActiveSync/CodePage18.php
@@ -1,65 +1,65 @@
<?php
/**
* Syncroton
*
* @package Wbxml
* @subpackage ActiveSync
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2008-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class documentation
*
* @package Wbxml
* @subpackage ActiveSync
*/
class Syncroton_Wbxml_Dtd_ActiveSync_CodePage18 extends Syncroton_Wbxml_Dtd_ActiveSync_Abstract
{
protected $_codePageNumber = 18;
protected $_codePageName = 'Settings';
protected $_tags = array(
'Settings' => 0x05,
'Status' => 0x06,
'Get' => 0x07,
'Set' => 0x08,
'Oof' => 0x09,
'OofState' => 0x0a,
'StartTime' => 0x0b,
'EndTime' => 0x0c,
'OofMessage' => 0x0d,
'AppliesToInternal' => 0x0e,
'AppliesToExternalKnown' => 0x0f,
'AppliesToExternalUnknown' => 0x10,
'Enabled' => 0x11,
'ReplyMessage' => 0x12,
'BodyType' => 0x13,
'DevicePassword' => 0x14,
'Password' => 0x15,
'DeviceInformation' => 0x16,
'Model' => 0x17,
'IMEI' => 0x18,
'FriendlyName' => 0x19,
'OS' => 0x1a,
'OSLanguage' => 0x1b,
'PhoneNumber' => 0x1c,
'UserInformation' => 0x1d,
'EmailAddresses' => 0x1e,
'SMTPAddress' => 0x1f,
'UserAgent' => 0x20,
'EnableOutboundSMS' => 0x21,
'MobileOperator' => 0x22,
'PrimarySmtpAddress' => 0x23,
'Accounts' => 0x24,
'Account' => 0x25,
'AccountId' => 0x26,
'AccountName' => 0x27,
'UserDisplayName' => 0x28,
'SendDisabled' => 0x29,
'RightsManagementInformation' => 0x2b,
);
-}
\ No newline at end of file
+}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Tue, Feb 3, 4:51 AM (1 d, 13 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
427050
Default Alt Text
(163 KB)
Attached To
Mode
R4 syncroton
Attached
Detach File
Event Timeline
Log In to Comment