Page MenuHomePhorge

No OneTemporary

Size
226 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/ext/Syncroton/Command/Ping.php b/lib/ext/Syncroton/Command/Ping.php
index 9a056e1..9ac1b35 100644
--- a/lib/ext/Syncroton/Command/Ping.php
+++ b/lib/ext/Syncroton/Command/Ping.php
@@ -1,204 +1,195 @@
<?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;
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)) {
- $lifeTime = (int)$xml->HeartBeatInterval;
- if ($this->_device->pinglifetime != $lifeTime) {
- $this->_device->pinglifetime = $lifeTime;
- $need_update = true;
- }
+ 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;
}
}
-
- $pingFolders = serialize(array_keys($folders));
- if ($this->_device->pingfolder != $pingFolders) {
- $this->_device->pingfolder = $pingFolders;
- $need_update = true;
- }
+ $this->_device->pingfolder = serialize(array_keys($folders));
}
- if ($need_update && $status == self::STATUS_NO_CHANGES_FOUND) {
+ if ($this->_device->isDirty() && $status == self::STATUS_NO_CHANGES_FOUND) {
$this->_device = $this->_deviceBackend->update($this->_device);
}
}
$lifeTime = $this->_device->pinglifetime;
#Tinebase_Core::setExecutionLifeTime($lifeTime);
$intervalEnd = $intervalStart + $lifeTime;
$secondsLeft = $intervalEnd;
$folders = unserialize($this->_device->pingfolder);
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) {
$folderWithChanges = array();
do {
// take a break to save battery lifetime
sleep(Syncroton_Registry::getPingTimeout());
$now = new DateTime('now', new DateTimeZone('utc'));
foreach ((array) $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->getCountOfChanges($this->_contentStateBackend, $folder, $syncState);
+ $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));
}
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/Sync.php b/lib/ext/Syncroton/Command/Sync.php
index ba28d37..b59bae6 100644
--- a/lib/ext/Syncroton/Command/Sync.php
+++ b/lib/ext/Syncroton/Command/Sync.php
@@ -1,939 +1,938 @@
<?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
$xml = simplexml_import_dom($this->_requestBody);
if (isset($xml->HeartbeatInterval)) {
$this->_heartbeatInterval = (int)$xml->HeartbeatInterval;
} elseif (isset($xml->Wait)) {
$this->_heartbeatInterval = (int)$xml->Wait * 60;
}
$this->_globalWindowSize = isset($xml->WindowSize) ? (int)$xml->WindowSize : 100;
if ($this->_globalWindowSize > $this->_maxWindowSize) {
$this->_globalWindowSize = $this->_maxWindowSize;
}
$collections = array();
$isPartialRequest = isset($xml->Partial);
// try to restore collections from previous request
if ($isPartialRequest) {
$decodedCollections = Zend_Json::decode($this->_device->lastsynccollection);
if (is_array($decodedCollections)) {
foreach ($decodedCollections as $collection) {
$collections[$collection['collectionId']] = new Syncroton_Model_SyncCollection($collection);
}
}
}
// Collections element is optional when Partial element is sent
if (isset($xml->Collections)) {
foreach ($xml->Collections->Collection as $xmlCollection) {
$collectionId = (string)$xmlCollection->CollectionId;
// do we have to update a collection sent in previous sync request?
if ($isPartialRequest && isset($collections[$collectionId])) {
$collections[$collectionId]->setFromSimpleXMLElement($xmlCollection);
} else {
$collections[$collectionId] = new Syncroton_Model_SyncCollection($xmlCollection);
}
}
// store current value of $collections for next Sync command request
$collectionsToSave = array();
foreach ($collections as $collection) {
$collectionsToSave[$collection->collectionId] = $collection->toArray();
}
-
- $lastSyncCollection = Zend_Json::encode($collectionsToSave);
- if ($this->_device->lastsynccollection != $lastSyncCollection) {
- $this->_device->lastsynccollection = $lastSyncCollection;
-
+
+ $this->_device->lastsynccollection = Zend_Json::encode($collectionsToSave);
+
+ 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_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;
if (count($this->_collections) == 0) {
$sync->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_RESEND_FULL_XML));
return $this->_outputDom;
}
$collections = $sync->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Collections'));
$totalChanges = 0;
// 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) {
// countinue 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 (($now->getTimestamp() - $collectionData->syncState->lastsync->getTimestamp()) < Syncroton_Registry::getQuietTime()) {
continue;
}
-
+
$dataController = Syncroton_Data_Factory::factory($collectionData->folder->class , $this->_device, $this->_syncTimeStamp);
- $estimate = $dataController->getCountOfChanges($this->_contentStateBackend, $collectionData->folder, $collectionData->syncState);
+ $hasChanges = $dataController->hasChanges($this->_contentStateBackend, $collectionData->folder, $collectionData->syncState);
- // countinue immediately if there are changes available
- if ($estimate > 0) {
+ // countinue immediately if there are any changes available
+ if ($hasChanges) {
break 2;
}
}
}
$this->_syncTimeStamp = clone $now;
// 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));
}
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;
} else {
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);
$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 = $collections->appendChild($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)) {
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($collectionData, $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 (Exception $e) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->warn(__METHOD__ . '::' . __LINE__ . " unable to convert entry to xml: " . $e->getMessage());
}
// mark as send 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 (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'])
) || (
// sends the server 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__ . " new synckey is ". $collectionData->syncState->counter);
}
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->getFolder($this->_device, $collectionData->collectionId);
$folderState->lastfiltertype = $collectionData->options['filterType'];
$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->crit(__METHOD__ . '::' . __LINE__ . ' failed to get content state for: ' . $collectionData->collectionId);
}
}
}
return $this->_outputDom;
}
}
diff --git a/lib/ext/Syncroton/Data/AData.php b/lib/ext/Syncroton/Data/AData.php
index 1382f68..97a821d 100644
--- a/lib/ext/Syncroton/Data/AData.php
+++ b/lib/ext/Syncroton/Data/AData.php
@@ -1,235 +1,244 @@
<?php
/**
* Syncroton
*
* @package Model
* @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 Model
*/
abstract class Syncroton_Data_AData implements Syncroton_Data_IData
{
const LONGID_DELIMITER = "\xe2\x87\x94"; # UTF8 ⇔
/**
* used by unit tests only to simulated added folders
*/
public static $changedEntries = array();
public function __construct(Syncroton_Model_IDevice $_device, DateTime $_timeStamp)
{
$this->_device = $_device;
$this->_timestamp = $_timeStamp;
$this->_db = Syncroton_Registry::getDatabase();
$this->_tablePrefix = 'Syncroton_';
$this->_ownerId = '1234';
}
public function getFolder($id)
{
$select = $this->_db->select()
->from($this->_tablePrefix . 'data_folder')
->where('owner_id = ?', $this->_ownerId)
->where('id = ?', $id);
$stmt = $this->_db->query($select);
$folder = $stmt->fetch();
$stmt = null; # see https://bugs.php.net/bug.php?id=44081
if ($folder === false) {
throw new Syncroton_Exception_NotFound("folder $id not found");
}
return new Syncroton_Model_Folder(array(
'serverId' => $folder['id'],
'displayName' => $folder['name'],
'type' => $folder['type'],
'parentId' => !empty($folder['parent_id']) ? $folder['parent_id'] : null
));
}
public function createFolder(Syncroton_Model_IFolder $folder)
{
if (!in_array($folder->type, $this->_supportedFolderTypes)) {
throw new Syncroton_Exception_UnexpectedValue();
}
$id = !empty($folder->serverId) ? $folder->serverId : sha1(mt_rand(). microtime());
$this->_db->insert($this->_tablePrefix . 'data_folder', array(
'id' => $id,
'type' => $folder->type,
'name' => $folder->displayName,
'owner_id' => $this->_ownerId,
'parent_id' => $folder->parentId
));
return $this->getFolder($id);
}
public function createEntry($_folderId, Syncroton_Model_IEntry $_entry)
{
$id = sha1(mt_rand(). microtime());
$this->_db->insert($this->_tablePrefix . 'data', array(
'id' => $id,
'class' => get_class($_entry),
'folder_id' => $_folderId,
'data' => serialize($_entry)
));
return $id;
}
public function deleteEntry($_folderId, $_serverId, $_collectionData)
{
$folderId = $_folderId instanceof Syncroton_Model_IFolder ? $_folderId->serverId : $_folderId;
$result = $this->_db->delete($this->_tablePrefix . 'data', array('id = ?' => $_serverId));
return (bool) $result;
}
public function deleteFolder($_folderId)
{
$folderId = $_folderId instanceof Syncroton_Model_IFolder ? $_folderId->serverId : $_folderId;
$result = $this->_db->delete($this->_tablePrefix . 'data', array('folder_id = ?' => $folderId));
$result = $this->_db->delete($this->_tablePrefix . 'data_folder', array('id = ?' => $folderId));
return (bool) $result;
}
public function getAllFolders()
{
$select = $this->_db->select()
->from($this->_tablePrefix . 'data_folder')
->where('type IN (?)', $this->_supportedFolderTypes)
->where('owner_id = ?', $this->_ownerId);
$stmt = $this->_db->query($select);
$folders = $stmt->fetchAll();
$stmt = null; # see https://bugs.php.net/bug.php?id=44081
$result = array();
foreach ((array) $folders as $folder) {
$result[$folder['id']] = new Syncroton_Model_Folder(array(
'serverId' => $folder['id'],
'displayName' => $folder['name'],
'type' => $folder['type'],
'parentId' => $folder['parent_id']
));
}
return $result;
}
public function getChangedEntries($_folderId, DateTime $_startTimeStamp, DateTime $_endTimeStamp = NULL)
{
if (!isset(Syncroton_Data_AData::$changedEntries[get_class($this)])) {
return array();
} else {
return Syncroton_Data_AData::$changedEntries[get_class($this)];
}
}
/**
* @param Syncroton_Model_IFolder|string $_folderId
* @param string $_filter
* @return array
*/
public function getServerEntries($_folderId, $_filter)
{
$folderId = $_folderId instanceof Syncroton_Model_IFolder ? $_folderId->id : $_folderId;
$select = $this->_db->select()
->from($this->_tablePrefix . 'data', array('id'))
->where('folder_id = ?', $_folderId);
$ids = array();
$stmt = $this->_db->query($select);
while ($id = $stmt->fetchColumn()) {
$ids[] = $id;
}
return $ids;
}
public function getCountOfChanges(Syncroton_Backend_IContent $contentBackend, Syncroton_Model_IFolder $folder, Syncroton_Model_ISyncState $syncState)
{
$allClientEntries = $contentBackend->getFolderState($this->_device, $folder);
$allServerEntries = $this->getServerEntries($folder->serverId, $folder->lastfiltertype);
$addedEntries = array_diff($allServerEntries, $allClientEntries);
$deletedEntries = array_diff($allClientEntries, $allServerEntries);
$changedEntries = $this->getChangedEntries($folder->serverId, $syncState->lastsync);
return count($addedEntries) + count($deletedEntries) + count($changedEntries);
}
public function getFileReference($fileReference)
{
throw new Syncroton_Exception_NotFound('filereference not found');
}
/**
* (non-PHPdoc)
* @see Syncroton_Data_IData::getEntry()
*/
public function getEntry(Syncroton_Model_SyncCollection $collection, $serverId)
{
$select = $this->_db->select()
->from($this->_tablePrefix . 'data', array('data'))
->where('id = ?', $serverId);
$stmt = $this->_db->query($select);
$entry = $stmt->fetchColumn();
if ($entry === false) {
throw new Syncroton_Exception_NotFound("entry $serverId not found in folder {$collection->collectionId}");
}
return unserialize($entry);
}
+
+ /**
+ * (non-PHPdoc)
+ * @see Syncroton_Data_IData::hasChanges()
+ */
+ public function hasChanges(Syncroton_Backend_IContent $contentBackend, Syncroton_Model_IFolder $folder, Syncroton_Model_ISyncState $syncState)
+ {
+ return !!$this->getCountOfChanges($contentBackend, $folder, $syncState);
+ }
public function moveItem($_srcFolderId, $_serverId, $_dstFolderId)
{
$this->_db->update($this->_tablePrefix . 'data', array(
'folder_id' => $_dstFolderId,
), array(
'id = ?' => $_serverId
));
return $_serverId;
}
public function updateEntry($_folderId, $_serverId, Syncroton_Model_IEntry $_entry)
{
$this->_db->update($this->_tablePrefix . 'data', array(
'folder_id' => $_folderId,
'data' => serialize($_entry)
), array(
'id = ?' => $_serverId
));
}
public function updateFolder(Syncroton_Model_IFolder $folder)
{
$this->_db->update($this->_tablePrefix . 'data_folder', array(
'name' => $folder->displayName,
'parent_id' => $folder->parentId
), array(
'id = ?' => $folder->serverId
));
}
}
diff --git a/lib/ext/Syncroton/Data/IData.php b/lib/ext/Syncroton/Data/IData.php
index 41aba4b..db286d3 100644
--- a/lib/ext/Syncroton/Data/IData.php
+++ b/lib/ext/Syncroton/Data/IData.php
@@ -1,96 +1,106 @@
<?php
/**
* Syncroton
*
* @package Model
* @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 Model
*/
interface Syncroton_Data_IData
{
/**
* create new entry
*
* @param string $folderId
* @param Syncroton_Model_IEntry $entry
* @return string id of created entry
*/
public function createEntry($folderId, Syncroton_Model_IEntry $entry);
/**
* create a new folder in backend
*
* @param Syncroton_Model_IFolder $folder
* @return Syncroton_Model_IFolder
*/
public function createFolder(Syncroton_Model_IFolder $folder);
/**
* delete entry in backend
*
* @param string $_folderId
* @param string $_serverId
* @param unknown_type $_collectionData
*/
public function deleteEntry($_folderId, $_serverId, $_collectionData);
public function deleteFolder($_folderId);
/**
* return list off all folders
* @return array of Syncroton_Model_IFolder
*/
public function getAllFolders();
public function getChangedEntries($folderId, DateTime $startTimeStamp, DateTime $endTimeStamp = NULL);
public function getCountOfChanges(Syncroton_Backend_IContent $contentBackend, Syncroton_Model_IFolder $folder, Syncroton_Model_ISyncState $syncState);
/**
*
* @param Syncroton_Model_SyncCollection $collection
* @param string $serverId
* @return Syncroton_Model_IEntry
*/
public function getEntry(Syncroton_Model_SyncCollection $collection, $serverId);
/**
*
* @param unknown_type $fileReference
* @return Syncroton_Model_FileReference
*/
public function getFileReference($fileReference);
/**
* return array of all id's stored in folder
*
* @param Syncroton_Model_IFolder|string $folderId
* @param string $filter
* @return array
*/
public function getServerEntries($folderId, $filter);
+
+ /**
+ * return true if any data got modified in the backend
+ *
+ * @param Syncroton_Backend_IContent $contentBackend
+ * @param Syncroton_Model_IFolder $folder
+ * @param Syncroton_Model_ISyncState $syncState
+ * @return bool
+ */
+ public function hasChanges(Syncroton_Backend_IContent $contentBackend, Syncroton_Model_IFolder $folder, Syncroton_Model_ISyncState $syncState);
public function moveItem($srcFolderId, $serverId, $dstFolderId);
/**
* update existing entry
*
* @param string $folderId
* @param string $serverId
* @param Syncroton_Model_IEntry $entry
* @return string id of updated entry
*/
public function updateEntry($folderId, $serverId, Syncroton_Model_IEntry $entry);
public function updateFolder(Syncroton_Model_IFolder $folder);
}
diff --git a/lib/ext/Syncroton/Model/AEntry.php b/lib/ext/Syncroton/Model/AEntry.php
index 1a58827..d2ad8be 100644
--- a/lib/ext/Syncroton/Model/AEntry.php
+++ b/lib/ext/Syncroton/Model/AEntry.php
@@ -1,326 +1,105 @@
<?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_AEntry implements Syncroton_Model_IEntry, IteratorAggregate, Countable
{
- protected $_xmlBaseElement;
-
protected $_elements = array();
- protected $_properties = array();
-
- protected $_dateTimeFormat = "Y-m-d\TH:i:s.000\Z";
+ protected $_isDirty;
/**
* (non-PHPdoc)
* @see Syncroton_Model_IEntry::__construct()
*/
public function __construct($properties = null)
{
- if ($properties instanceof SimpleXMLElement) {
- $this->setFromSimpleXMLElement($properties);
- } elseif (is_array($properties)) {
+ if (is_array($properties)) {
$this->setFromArray($properties);
}
- }
-
- /**
- * (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 {
- $element = $domParrent->ownerDocument->createElementNS($nameSpace, ucfirst($elementName));
-
- $this->_appendXMLElement($device, $element, $elementProperties, $value);
-
- $domParrent->appendChild($element);
- }
-
- }
+ $this->_isDirty = false;
}
+
/**
* (non-PHPdoc)
* @see Countable::count()
*/
public function count()
{
return count($this->_elements);
}
/**
* (non-PHPdoc)
* @see IteratorAggregate::getIterator()
*/
public function getIterator()
{
return new ArrayIterator($this->_elements);
}
/**
* (non-PHPdoc)
- * @see Syncroton_Model_IEntry::getProperties()
+ * @see Syncroton_Model_IEntry::isDirty()
*/
- public function getProperties()
+ public function isDirty()
{
- $properties = array();
-
- foreach($this->_properties as $namespace => $namespaceProperties) {
- $properties = array_merge($properties, array_keys($namespaceProperties));
- }
-
- return $properties;
-
+ return $this->_isDirty;
}
/**
* (non-PHPdoc)
* @see Syncroton_Model_IEntry::setFromArray()
*/
public function setFromArray(array $properties)
{
foreach($properties as $key => $value) {
try {
$this->$key = $value; //echo __LINE__ . PHP_EOL;
} catch (InvalidArgumentException $iae) {
//ignore invalid properties
//echo __LINE__ . PHP_EOL; echo $iae->getMessage(); echo $iae->getTraceAsString();
}
}
}
-
- /**
- * 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($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);
- }
-
- /**
- *
- * @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 (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;
}
-
- $this->_elements[$name] = $value;
}
public function __isset($name)
{
return isset($this->_elements[$name]);
}
public function __unset($name)
{
unset($this->_elements[$name]);
}
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/AEntry.php b/lib/ext/Syncroton/Model/AXMLEntry.php
similarity index 88%
copy from lib/ext/Syncroton/Model/AEntry.php
copy to lib/ext/Syncroton/Model/AXMLEntry.php
index 1a58827..6dc1e9f 100644
--- a/lib/ext/Syncroton/Model/AEntry.php
+++ b/lib/ext/Syncroton/Model/AXMLEntry.php
@@ -1,326 +1,286 @@
<?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_AEntry implements Syncroton_Model_IEntry, IteratorAggregate, Countable
+abstract class Syncroton_Model_AXMLEntry extends Syncroton_Model_AEntry implements Syncroton_Model_IXMLEntry
{
protected $_xmlBaseElement;
- protected $_elements = array();
-
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 {
$element = $domParrent->ownerDocument->createElementNS($nameSpace, ucfirst($elementName));
$this->_appendXMLElement($device, $element, $elementProperties, $value);
$domParrent->appendChild($element);
}
}
}
- /**
- * (non-PHPdoc)
- * @see Countable::count()
- */
- public function count()
- {
- return count($this->_elements);
- }
-
- /**
- * (non-PHPdoc)
- * @see IteratorAggregate::getIterator()
- */
- public function getIterator()
- {
- return new ArrayIterator($this->_elements);
- }
-
/**
* (non-PHPdoc)
* @see Syncroton_Model_IEntry::getProperties()
*/
public function getProperties()
{
$properties = array();
foreach($this->_properties as $namespace => $namespaceProperties) {
$properties = array_merge($properties, array_keys($namespaceProperties));
}
return $properties;
}
- /**
- * (non-PHPdoc)
- * @see Syncroton_Model_IEntry::setFromArray()
- */
- public function setFromArray(array $properties)
- {
- foreach($properties as $key => $value) {
- try {
- $this->$key = $value; //echo __LINE__ . PHP_EOL;
- } catch (InvalidArgumentException $iae) {
- //ignore invalid properties
- //echo __LINE__ . PHP_EOL; echo $iae->getMessage(); echo $iae->getTraceAsString();
- }
- }
- }
-
/**
* 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($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);
}
/**
*
* @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 (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");
}
-
- $this->_elements[$name] = $value;
- }
-
- public function __isset($name)
- {
- return isset($this->_elements[$name]);
+
+ if (!array_key_exists($name, $this->_elements) || $this->_elements[$name] != $value) {
+ $this->_elements[$name] = $value;
+
+ $this->_isDirty = true;
+ }
}
-
- public function __unset($name)
- {
- unset($this->_elements[$name]);
- }
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/Contact.php b/lib/ext/Syncroton/Model/Contact.php
index 16485c2..6b90a07 100644
--- a/lib/ext/Syncroton/Model/Contact.php
+++ b/lib/ext/Syncroton/Model/Contact.php
@@ -1,103 +1,103 @@
<?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>
*/
/**
* class to handle ActiveSync contact
*
* @package Syncroton
* @subpackage Model
* @property string Alias
* @property DateTime Anniversary
* @property string AssistantName
* @property string AssistantPhoneNumber
* @property DateTime Birthday
* @property string Business2PhoneNumber
* @property string BusinessAddressCity
* @property Syncroton_Model_EmailBody Body
*/
-class Syncroton_Model_Contact extends Syncroton_Model_AEntry
+class Syncroton_Model_Contact extends Syncroton_Model_AXMLEntry
{
protected $_xmlBaseElement = 'ApplicationData';
protected $_properties = array(
'AirSyncBase' => array(
'body' => array('type' => 'container', 'class' => 'Syncroton_Model_EmailBody')
),
'Contacts' => array(
'alias' => array('type' => 'string', 'supportedSince' => '14.0'),
'anniversary' => array('type' => 'datetime'),
'assistantName' => array('type' => 'string'),
'assistantPhoneNumber' => array('type' => 'string'),
'birthday' => array('type' => 'datetime'),
'business2PhoneNumber' => array('type' => 'string'),
'businessAddressCity' => array('type' => 'string'),
'businessAddressCountry' => array('type' => 'string'),
'businessAddressPostalCode' => array('type' => 'string'),
'businessAddressState' => array('type' => 'string'),
'businessAddressStreet' => array('type' => 'string'),
'businessFaxNumber' => array('type' => 'string'),
'businessPhoneNumber' => array('type' => 'string'),
'carPhoneNumber' => array('type' => 'string'),
'categories' => array('type' => 'container', 'childElement' => 'category'),
'children' => array('type' => 'container', 'childElement' => 'child'),
'companyName' => array('type' => 'string'),
'department' => array('type' => 'string'),
'email1Address' => array('type' => 'string'),
'email2Address' => array('type' => 'string'),
'email3Address' => array('type' => 'string'),
'fileAs' => array('type' => 'string'),
'firstName' => array('type' => 'string'),
'home2PhoneNumber' => array('type' => 'string'),
'homeAddressCity' => array('type' => 'string'),
'homeAddressCountry' => array('type' => 'string'),
'homeAddressPostalCode' => array('type' => 'string'),
'homeAddressState' => array('type' => 'string'),
'homeAddressStreet' => array('type' => 'string'),
'homeFaxNumber' => array('type' => 'string'),
'homePhoneNumber' => array('type' => 'string'),
'jobTitle' => array('type' => 'string'),
'lastName' => array('type' => 'string'),
'middleName' => array('type' => 'string'),
'mobilePhoneNumber' => array('type' => 'string'),
'officeLocation' => array('type' => 'string'),
'otherAddressCity' => array('type' => 'string'),
'otherAddressCountry' => array('type' => 'string'),
'otherAddressPostalCode' => array('type' => 'string'),
'otherAddressState' => array('type' => 'string'),
'otherAddressStreet' => array('type' => 'string'),
'pagerNumber' => array('type' => 'string'),
'picture' => array('type' => 'string', 'encoding' => 'base64'),
'padioPhoneNumber' => array('type' => 'string'),
'rtf' => array('type' => 'string'),
'spouse' => array('type' => 'string'),
'suffix' => array('type' => 'string'),
'title' => array('type' => 'string'),
'webPage' => array('type' => 'string'),
'weightedRank' => array('type' => 'string', 'supportedSince' => '14.0'),
'yomiCompanyName' => array('type' => 'string'),
'yomiFirstName' => array('type' => 'string'),
'yomiLastName' => array('type' => 'string'),
),
'Contacts2' => array(
'accountName' => array('type' => 'string'),
'companyMainPhone' => array('type' => 'string'),
'customerId' => array('type' => 'string'),
'governmentId' => array('type' => 'string'),
'iMAddress' => array('type' => 'string'),
'iMAddress2' => array('type' => 'string'),
'iMAddress3' => array('type' => 'string'),
'managerName' => array('type' => 'string'),
'mMS' => array('type' => 'string'),
'nickName' => array('type' => 'string'),
)
);
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/Device.php b/lib/ext/Syncroton/Model/Device.php
index 3a0fdd6..a46ef2d 100644
--- a/lib/ext/Syncroton/Model/Device.php
+++ b/lib/ext/Syncroton/Model/Device.php
@@ -1,58 +1,46 @@
<?php
/**
* Syncroton
*
* @package Model
* @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 Model
*/
-class Syncroton_Model_Device implements Syncroton_Model_IDevice
+class Syncroton_Model_Device extends Syncroton_Model_AEntry implements Syncroton_Model_IDevice
{
const TYPE_IPHONE = 'iphone';
const TYPE_WEBOS = 'webos';
const TYPE_ANDROID = 'android';
const TYPE_ANDROID_40 = 'android40';
const TYPE_SMASUNGGALAXYS2 = 'samsunggti9100'; // Samsung Galaxy S-3
- public function __construct(array $_data = array())
- {
- $this->setFromArray($_data);
- }
-
- public function setFromArray(array $_data)
- {
- foreach($_data as $key => $value) {
- $this->$key = $value;
- }
- }
-
/**
* Returns major firmware version of this device
*
* @return int/string
*/
public function getMajorVersion()
{
switch ($this->devicetype) {
case Syncroton_Model_Device::TYPE_IPHONE:
if (preg_match('/(.+)\/(\d+)\.(\d+)/', $this->useragent, $matches)) {
list(, $name, $majorVersion, $minorVersion) = $matches;
return $majorVersion;
}
break;
default:
break;
}
return 0;
}
}
diff --git a/lib/ext/Syncroton/Model/DeviceInformation.php b/lib/ext/Syncroton/Model/DeviceInformation.php
index adab7b0..0a5c516 100644
--- a/lib/ext/Syncroton/Model/DeviceInformation.php
+++ b/lib/ext/Syncroton/Model/DeviceInformation.php
@@ -1,40 +1,40 @@
<?php
/**
* Syncroton
*
* @package 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>
*/
/**
* class to handle ActiveSync device information
*
* @package Model
* @property string friendlyName
* @property string iMEI
* @property string mobileOperator
* @property string model
* @property string oS
* @property string oSLanguage
* @property string phoneNumber
*/
-class Syncroton_Model_DeviceInformation extends Syncroton_Model_AEntry
+class Syncroton_Model_DeviceInformation extends Syncroton_Model_AXMLEntry
{
protected $_xmlBaseElement = 'Set';
protected $_properties = array(
'Settings' => array(
'enableOutboundSMS' => array('type' => 'number'),
'friendlyName' => array('type' => 'string'),
'iMEI' => array('type' => 'string'),
'mobileOperator' => array('type' => 'string'),
'model' => array('type' => 'string'),
'oS' => array('type' => 'string'),
'oSLanguage' => array('type' => 'string'),
'phoneNumber' => array('type' => 'string')
),
);
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/Email.php b/lib/ext/Syncroton/Model/Email.php
index 35f2661..29fd495 100644
--- a/lib/ext/Syncroton/Model/Email.php
+++ b/lib/ext/Syncroton/Model/Email.php
@@ -1,88 +1,88 @@
<?php
/**
* Syncroton
*
* @package 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>
*/
/**
* class to handle ActiveSync email
*
* @package Model
* @property array attachments
* @property string contentType
* @property array flag
* @property Syncroton_Model_EmailBody body
* @property array cc
* @property array to
* @property int lastVerbExecuted
* @property DateTime lastVerbExecutionTime
* @property int read
*/
-class Syncroton_Model_Email extends Syncroton_Model_AEntry
+class Syncroton_Model_Email extends Syncroton_Model_AXMLEntry
{
const LASTVERB_UNKNOWN = 0;
const LASTVERB_REPLYTOSENDER = 1;
const LASTVERB_REPLYTOALL = 2;
const LASTVERB_FORWARD = 3;
protected $_xmlBaseElement = 'ApplicationData';
protected $_properties = array(
'AirSyncBase' => array(
'attachments' => array('type' => 'container', 'childElement' => 'attachment', 'class' => 'Syncroton_Model_EmailAttachment'),
'contentType' => array('type' => 'string'),
'body' => array('type' => 'container', 'class' => 'Syncroton_Model_EmailBody'),
'nativeBodyType' => array('type' => 'number'),
),
'Email' => array(
'busyStatus' => array('type' => 'number'),
'categories' => array('type' => 'container', 'childElement' => 'category', 'supportedSince' => '14.0'),
'cc' => array('type' => 'string'),
'completeTime' => array('type' => 'datetime'),
'contentClass' => array('type' => 'string'),
'dateReceived' => array('type' => 'datetime'),
'disallowNewTimeProposal' => array('type' => 'number'),
'displayTo' => array('type' => 'string'),
'dTStamp' => array('type' => 'datetime'),
'endTime' => array('type' => 'datetime'),
'flag' => array('type' => 'container', 'class' => 'Syncroton_Model_EmailFlag'),
'from' => array('type' => 'string'),
'globalObjId' => array('type' => 'string'),
'importance' => array('type' => 'number'),
'instanceType' => array('type' => 'number'),
'internetCPID' => array('type' => 'string'),
'location' => array('type' => 'string'),
'meetingRequest' => array('type' => 'container', 'class' => 'Syncroton_Model_EmailMeetingRequest'),
'messageClass' => array('type' => 'string'),
'organizer' => array('type' => 'string'),
'read' => array('type' => 'number'),
'recurrences' => array('type' => 'container'),
'reminder' => array('type' => 'number'),
'replyTo' => array('type' => 'string'),
'responseRequested' => array('type' => 'number'),
'sensitivity' => array('type' => 'number'),
'startTime' => array('type' => 'datetime'),
'status' => array('type' => 'number'),
'subject' => array('type' => 'string'),
'threadTopic' => array('type' => 'string'),
'timeZone' => array('type' => 'timezone'),
'to' => array('type' => 'string'),
),
'Email2' => array(
'accountId' => array('type' => 'string', 'supportedSince' => '14.1'),
'conversationId' => array('type' => 'byteArray', 'supportedSince' => '14.0'),
'conversationIndex' => array('type' => 'byteArray', 'supportedSince' => '14.0'),
'lastVerbExecuted' => array('type' => 'number', 'supportedSince' => '14.0'),
'lastVerbExecutionTime' => array('type' => 'datetime', 'supportedSince' => '14.0'),
'meetingMessageType' => array('type' => 'number', 'supportedSince' => '14.1'),
'receivedAsBcc' => array('type' => 'number', 'supportedSince' => '14.0'),
'sender' => array('type' => 'string', 'supportedSince' => '14.0'),
'umCallerID' => array('type' => 'string', 'supportedSince' => '14.0'),
'umUserNotes' => array('type' => 'string', 'supportedSince' => '14.0'),
),
);
}
diff --git a/lib/ext/Syncroton/Model/EmailAttachment.php b/lib/ext/Syncroton/Model/EmailAttachment.php
index e415ad4..48d9866 100644
--- a/lib/ext/Syncroton/Model/EmailAttachment.php
+++ b/lib/ext/Syncroton/Model/EmailAttachment.php
@@ -1,41 +1,41 @@
<?php
/**
* Syncroton
*
* @package 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>
*/
/**
* class to handle ActiveSync event
*
* @package Model
* @property string class
* @property string collectionId
* @property bool deletesAsMoves
* @property bool getChanges
* @property string syncKey
* @property int windowSize
*/
-class Syncroton_Model_EmailAttachment extends Syncroton_Model_AEntry
+class Syncroton_Model_EmailAttachment extends Syncroton_Model_AXMLEntry
{
protected $_xmlBaseElement = 'Attachment';
protected $_properties = array(
'AirSyncBase' => array(
'contentId' => array('type' => 'string'),
'contentLocation' => array('type' => 'string'),
'displayName' => array('type' => 'string'),
'estimatedDataSize' => array('type' => 'string'),
'fileReference' => array('type' => 'string'),
'isInline' => array('type' => 'number'),
'method' => array('type' => 'string'),
),
'Email2' => array(
'umAttDuration' => array('type' => 'number', 'supportedSince' => '14.0'),
'umAttOrder' => array('type' => 'number', 'supportedSince' => '14.0'),
),
);
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/EmailBody.php b/lib/ext/Syncroton/Model/EmailBody.php
index 3b0f16b..3d465b8 100644
--- a/lib/ext/Syncroton/Model/EmailBody.php
+++ b/lib/ext/Syncroton/Model/EmailBody.php
@@ -1,42 +1,42 @@
<?php
/**
* Syncroton
*
* @package 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>
*/
/**
* class to handle AirSyncBase:Body
*
* @package Model
* @property int EstimatedDataSize
* @property string Data
* @property string Part
* @property string Preview
* @property bool Truncated
* @property string Type
*/
-class Syncroton_Model_EmailBody extends Syncroton_Model_AEntry
+class Syncroton_Model_EmailBody extends Syncroton_Model_AXMLEntry
{
const TYPE_PLAINTEXT = 1;
const TYPE_HTML = 2;
const TYPE_RTF = 3;
const TYPE_MIME = 4;
protected $_xmlBaseElement = 'Body';
protected $_properties = array(
'AirSyncBase' => array(
'type' => array('type' => 'string'),
'estimatedDataSize' => array('type' => 'string'),
'data' => array('type' => 'string'),
'truncated' => array('type' => 'number'),
'part' => array('type' => 'number'),
'preview' => array('type' => 'string', 'supportedSince' => '14.0'),
),
);
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/EmailFlag.php b/lib/ext/Syncroton/Model/EmailFlag.php
index c284987..258fd46 100644
--- a/lib/ext/Syncroton/Model/EmailFlag.php
+++ b/lib/ext/Syncroton/Model/EmailFlag.php
@@ -1,58 +1,58 @@
<?php
/**
* Syncroton
*
* @package Model
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2012-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @copyright Copyright (c) 2012-2012 Kolab Systems AG (http://www.kolabsys.com)
* @author Lars Kneschke <l.kneschke@metaways.de>
* @author Aleksander Machniak <machniak@kolabsys.com>
*/
/**
* class to handle ActiveSync Flag element
*
* @package Model
* @property DateTime CompleteTime
* @property DateTime DateCompleted
* @property DateTime DueDate
* @property string FlagType
* @property DateTime OrdinalDate
* @property int ReminderSet
* @property DateTime ReminderTime
* @property DateTime StartDate
* @property string Status
* @property string Subject
* @property string SubOrdinalDate
* @property DateTime UtcDueDate
* @property DateTime UtcStartDate
*/
-class Syncroton_Model_EmailFlag extends Syncroton_Model_AEntry
+class Syncroton_Model_EmailFlag extends Syncroton_Model_AXMLEntry
{
const STATUS_CLEARED = 0;
const STATUS_COMPLETE = 1;
const STATUS_ACTIVE = 2;
protected $_xmlBaseElement = 'Flag';
protected $_properties = array(
'Email' => array(
'completeTime' => array('type' => 'datetime'),
'flagType' => array('type' => 'string'),
'status' => array('type' => 'number'),
),
'Tasks' => array(
'dateCompleted' => array('type' => 'datetime'),
'dueDate' => array('type' => 'datetime'),
'ordinalDate' => array('type' => 'datetime'),
'reminderSet' => array('type' => 'number'),
'reminderTime' => array('type' => 'datetime'),
'startDate' => array('type' => 'datetime'),
'subject' => array('type' => 'string'),
'subOrdinalDate' => array('type' => 'string'),
'utcStartDate' => array('type' => 'datetime'),
'utcDueDate' => array('type' => 'datetime'),
),
);
}
diff --git a/lib/ext/Syncroton/Model/Event.php b/lib/ext/Syncroton/Model/Event.php
index cf0de60..84fec18 100644
--- a/lib/ext/Syncroton/Model/Event.php
+++ b/lib/ext/Syncroton/Model/Event.php
@@ -1,112 +1,112 @@
<?php
/**
* Syncroton
*
* @package 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>
*/
/**
* class to handle ActiveSync event
*
* @package Model
* @property string class
* @property string collectionId
* @property bool deletesAsMoves
* @property bool getChanges
* @property string syncKey
* @property int windowSize
*/
-class Syncroton_Model_Event extends Syncroton_Model_AEntry
+class Syncroton_Model_Event extends Syncroton_Model_AXMLEntry
{
/**
* busy status constants
*/
const BUSY_STATUS_FREE = 0;
const BUSY_STATUS_TENATTIVE = 1;
const BUSY_STATUS_BUSY = 2;
protected $_dateTimeFormat = "Ymd\THis\Z";
protected $_xmlBaseElement = 'ApplicationData';
protected $_properties = array(
'AirSyncBase' => array(
'body' => array('type' => 'container', 'class' => 'Syncroton_Model_EmailBody')
),
'Calendar' => array(
'allDayEvent' => array('type' => 'number'),
'appointmentReplyTime' => array('type' => 'datetime'),
'attendees' => array('type' => 'container', 'childElement' => 'attendee', 'class' => 'Syncroton_Model_EventAttendee'),
'busyStatus' => array('type' => 'number'),
'categories' => array('type' => 'container', 'childElement' => 'category'),
'disallowNewTimeProposal' => array('type' => 'number'),
'dtStamp' => array('type' => 'datetime'),
'endTime' => array('type' => 'datetime'),
'exceptions' => array('type' => 'container', 'childElement' => 'exception', 'class' => 'Syncroton_Model_EventException'),
'location' => array('type' => 'string'),
'meetingStatus' => array('type' => 'number'),
'onlineMeetingConfLink' => array('type' => 'string'),
'onlineMeetingExternalLink' => array('type' => 'string'),
'organizerEmail' => array('type' => 'string'),
'organizerName' => array('type' => 'string'),
'recurrence' => array('type' => 'container'),
'reminder' => array('type' => 'number'),
'responseRequested' => array('type' => 'number'),
'responseType' => array('type' => 'number'),
'sensitivity' => array('type' => 'number'),
'startTime' => array('type' => 'datetime'),
'subject' => array('type' => 'string'),
'timezone' => array('type' => 'timezone'),
'uID' => array('type' => 'string'),
)
);
public function setFromArray(array $properties)
{
parent::setFromArray($properties);
$this->_copyFieldsFromParent();
}
/**
* set properties from SimpleXMLElement object
*
* @param SimpleXMLElement $xmlCollection
* @throws InvalidArgumentException
*/
public function setFromSimpleXMLElement(SimpleXMLElement $properties)
{
parent::setFromSimpleXMLElement($properties);
$this->_copyFieldsFromParent();
}
/**
* copy some fileds of the main event to the exception if they are missing
* these fields can be left out, if they have the same value in the main event
* and the exception
*/
protected function _copyFieldsFromParent()
{
if (isset($this->_elements['exceptions']) && is_array($this->_elements['exceptions'])) {
foreach ($this->_elements['exceptions'] as $exception) {
// no need to update deleted exceptions
if ($exception->deleted == 1) {
continue;
}
$parentFields = array('allDayEvent', 'attendees', 'busyStatus', 'meetingStatus', 'sensitivity', 'subject');
foreach ($parentFields as $field) {
if (!isset($exception->$field) && isset($this->_elements[$field])) {
$exception->$field = $this->_elements[$field];
}
}
}
}
}
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/EventAttendee.php b/lib/ext/Syncroton/Model/EventAttendee.php
index a439650..82257db 100644
--- a/lib/ext/Syncroton/Model/EventAttendee.php
+++ b/lib/ext/Syncroton/Model/EventAttendee.php
@@ -1,51 +1,51 @@
<?php
/**
* Syncroton
*
* @package 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>
*/
/**
* class to handle ActiveSync event
*
* @package Model
* @property string class
* @property string collectionId
* @property bool deletesAsMoves
* @property bool getChanges
* @property string syncKey
* @property int windowSize
*/
-class Syncroton_Model_EventAttendee extends Syncroton_Model_AEntry
+class Syncroton_Model_EventAttendee extends Syncroton_Model_AXMLEntry
{
protected $_xmlBaseElement = 'Attendee';
/**
* attendee status
*/
const ATTENDEE_STATUS_UNKNOWN = 0;
const ATTENDEE_STATUS_TENTATIVE = 2;
const ATTENDEE_STATUS_ACCEPTED = 3;
const ATTENDEE_STATUS_DECLINED = 4;
const ATTENDEE_STATUS_NOTRESPONDED = 5;
/**
* attendee types
*/
const ATTENDEE_TYPE_REQUIRED = 1;
const ATTENDEE_TYPE_OPTIONAL = 2;
const ATTENDEE_TYPE_RESOURCE = 3;
protected $_properties = array(
'Calendar' => array(
'attendeeStatus' => array('type' => 'number'),
'attendeeType' => array('type' => 'number'),
'email' => array('type' => 'string'),
'name' => array('type' => 'string'),
)
);
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/EventException.php b/lib/ext/Syncroton/Model/EventException.php
index 7d7224b..0dab808 100644
--- a/lib/ext/Syncroton/Model/EventException.php
+++ b/lib/ext/Syncroton/Model/EventException.php
@@ -1,49 +1,49 @@
<?php
/**
* Syncroton
*
* @package 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>
*/
/**
* class to handle ActiveSync event
*
* @package Model
* @property string class
* @property string collectionId
* @property bool deletesAsMoves
* @property bool getChanges
* @property string syncKey
* @property int windowSize
*/
-class Syncroton_Model_EventException extends Syncroton_Model_AEntry
+class Syncroton_Model_EventException extends Syncroton_Model_AXMLEntry
{
protected $_xmlBaseElement = 'Exception';
protected $_dateTimeFormat = "Ymd\THis\Z";
protected $_properties = array(
'Calendar' => array(
'allDayEvent' => array('type' => 'number'),
'appointmentReplyTime' => array('type' => 'datetime'),
'attendees' => array('type' => 'container', 'childElement' => 'attendee', 'class' => 'Syncroton_Model_EventAttendee'),
'busyStatus' => array('type' => 'number'),
'categories' => array('type' => 'container', 'childElement' => 'category'),
'deleted' => array('type' => 'number'),
'dtStamp' => array('type' => 'datetime'),
'endTime' => array('type' => 'datetime'),
'exceptionStartTime' => array('type' => 'datetime'),
'location' => array('type' => 'string'),
'meetingStatus' => array('type' => 'number'),
'reminder' => array('type' => 'number'),
'responseType' => array('type' => 'number'),
'sensitivity' => array('type' => 'number'),
'startTime' => array('type' => 'datetime'),
'subject' => array('type' => 'string'),
)
);
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/EventRecurrence.php b/lib/ext/Syncroton/Model/EventRecurrence.php
index 367a2bc..3fe0c94 100644
--- a/lib/ext/Syncroton/Model/EventRecurrence.php
+++ b/lib/ext/Syncroton/Model/EventRecurrence.php
@@ -1,70 +1,70 @@
<?php
/**
* Syncroton
*
* @package 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>
*/
/**
* class to handle ActiveSync event
*
* @package Model
* @property int CalendarType
* @property int DayOfMonth
* @property int DayOfWeek
* @property int FirstDayOfWeek
* @property int Interval
* @property int IsLeapMonth
* @property int MonthOfYear
* @property int Occurrences
* @property int Type
* @property DateTime Until
* @property int WeekOfMonth
*/
-class Syncroton_Model_EventRecurrence extends Syncroton_Model_AEntry
+class Syncroton_Model_EventRecurrence extends Syncroton_Model_AXMLEntry
{
protected $_xmlBaseElement = 'Recurrence';
/**
* recur types
*/
const TYPE_DAILY = 0; // Recurs daily.
const TYPE_WEEKLY = 1; // Recurs weekly
const TYPE_MONTHLY = 2; // Recurs monthly
const TYPE_MONTHLY_DAYN = 3; // Recurs monthly on the nth day
const TYPE_YEARLY = 5; // Recurs yearly
const TYPE_YEARLY_DAYN = 6; // Recurs yearly on the nth day
/**
* day of week constants
*/
const RECUR_DOW_SUNDAY = 1;
const RECUR_DOW_MONDAY = 2;
const RECUR_DOW_TUESDAY = 4;
const RECUR_DOW_WEDNESDAY = 8;
const RECUR_DOW_THURSDAY = 16;
const RECUR_DOW_FRIDAY = 32;
const RECUR_DOW_SATURDAY = 64;
protected $_dateTimeFormat = "Ymd\THis\Z";
protected $_properties = array(
'Calendar' => array(
'calendarType' => array('type' => 'number'),
'dayOfMonth' => array('type' => 'number'),
'dayOfWeek' => array('type' => 'number'),
'firstDayOfWeek' => array('type' => 'number'),
'interval' => array('type' => 'number'),
'isLeapMonth' => array('type' => 'number'),
'monthOfYear' => array('type' => 'number'),
'occurrences' => array('type' => 'number'),
'type' => array('type' => 'number'),
'until' => array('type' => 'datetime'),
'weekOfMonth' => array('type' => 'number'),
)
);
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/FileReference.php b/lib/ext/Syncroton/Model/FileReference.php
index 9f0bbad..b54bb7d 100644
--- a/lib/ext/Syncroton/Model/FileReference.php
+++ b/lib/ext/Syncroton/Model/FileReference.php
@@ -1,44 +1,44 @@
<?php
/**
* Syncroton
*
* @package 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>
*/
/**
* class to handle ActiveSync event
*
* @package Model
* @property string ContentType
* @property string Data
*/
-class Syncroton_Model_FileReference extends Syncroton_Model_AEntry
+class Syncroton_Model_FileReference extends Syncroton_Model_AXMLEntry
{
protected $_xmlBaseElement = 'ApplicationData';
protected $_properties = array(
'AirSyncBase' => array(
'contentType' => array('type' => 'string'),
),
'ItemOperations' => array(
'data' => array('type' => 'string', 'encoding' => 'base64'),
'part' => array('type' => 'number')
)
);
/**
*
* @param SimpleXMLElement $xmlCollection
* @throws InvalidArgumentException
*/
public function setFromSimpleXMLElement(SimpleXMLElement $properties)
{
//do nothing
return;
}
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/Folder.php b/lib/ext/Syncroton/Model/Folder.php
index dfabc34..9655f9a 100644
--- a/lib/ext/Syncroton/Model/Folder.php
+++ b/lib/ext/Syncroton/Model/Folder.php
@@ -1,38 +1,38 @@
<?php
/**
* Syncroton
*
* @package Model
* @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 Model
*/
-class Syncroton_Model_Folder extends Syncroton_Model_AEntry implements Syncroton_Model_IFolder
+class Syncroton_Model_Folder extends Syncroton_Model_AXMLEntry implements Syncroton_Model_IFolder
{
protected $_xmlBaseElement = array('FolderUpdate', 'FolderCreate');
protected $_properties = array(
'FolderHierarchy' => array(
'parentId' => array('type' => 'string'),
'serverId' => array('type' => 'string'),
'displayName' => array('type' => 'string'),
'type' => array('type' => 'number')
),
'Internal' => array(
'id' => array('type' => 'string'),
'deviceId' => array('type' => 'string'),
'ownerId' => array('type' => 'string'),
'class' => array('type' => 'string'),
'creationTime' => array('type' => 'datetime'),
'lastfiltertype' => array('type' => 'number')
),
);
}
diff --git a/lib/ext/Syncroton/Model/GAL.php b/lib/ext/Syncroton/Model/GAL.php
index 917c5e5..64765ce 100644
--- a/lib/ext/Syncroton/Model/GAL.php
+++ b/lib/ext/Syncroton/Model/GAL.php
@@ -1,51 +1,51 @@
<?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)
* @copyright Copyright (c) 2012-2012 Kolab Systems AG (http://www.kolabsys.com)
* @author Lars Kneschke <l.kneschke@metaways.de>
* @author Aleksander Machniak <machniak@kolabsys.com>
*/
/**
* class to handle ActiveSync GAL result
*
* @package Syncroton
* @subpackage Model
*
* @property string Alias
* @property string Company
* @property string DisplayName
* @property string EmailAddress
* @property string FirstName
* @property string LastName
* @property string MobilePhone
* @property string Office
* @property string Phone
* @property string Picture
* @property string Title
*/
-class Syncroton_Model_GAL extends Syncroton_Model_AEntry
+class Syncroton_Model_GAL extends Syncroton_Model_AXMLEntry
{
protected $_xmlBaseElement = 'ApplicationData';
protected $_properties = array(
'GAL' => array(
'alias' => array('type' => 'string', 'supportedSince' => '2.5'),
'company' => array('type' => 'string', 'supportedSince' => '2.5'),
'displayName' => array('type' => 'string', 'supportedSince' => '2.5'),
'emailAddress' => array('type' => 'string', 'supportedSince' => '2.5'),
'firstName' => array('type' => 'string', 'supportedSince' => '2.5'),
'lastName' => array('type' => 'string', 'supportedSince' => '2.5'),
'mobilePhone' => array('type' => 'string', 'supportedSince' => '2.5'),
'office' => array('type' => 'string', 'supportedSince' => '2.5'),
'phone' => array('type' => 'string', 'supportedSince' => '2.5'),
'picture' => array('type' => 'container', 'supportedSince' => '14.0'),
'title' => array('type' => 'string', 'supportedSince' => '2.5'),
)
);
}
diff --git a/lib/ext/Syncroton/Model/GALPicture.php b/lib/ext/Syncroton/Model/GALPicture.php
index d7a208a..8122490 100644
--- a/lib/ext/Syncroton/Model/GALPicture.php
+++ b/lib/ext/Syncroton/Model/GALPicture.php
@@ -1,40 +1,40 @@
<?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)
* @copyright Copyright (c) 2012-2012 Kolab Systems AG (http://www.kolabsys.com)
* @author Lars Kneschke <l.kneschke@metaways.de>
* @author Aleksander Machniak <machniak@kolabsys.com>
*/
/**
* class to handle ActiveSync GAL Picture element
*
* @package Syncroton
* @subpackage Model
*
* @property string Status
* @property string Data
*/
-class Syncroton_Model_GALPicture extends Syncroton_Model_AEntry
+class Syncroton_Model_GALPicture extends Syncroton_Model_AXMLEntry
{
const STATUS_SUCCESS = 1;
const STATUS_NOPHOTO = 173;
const STATUS_TOOLARGE = 174;
const STATUS_OVERLIMIT = 175;
protected $_xmlBaseElement = 'ApplicationData';
protected $_properties = array(
'AirSync' => array(
'status' => array('type' => 'number'),
),
'GAL' => array(
'data' => array('type' => 'byteArray'),
),
);
}
diff --git a/lib/ext/Syncroton/Model/IDevice.php b/lib/ext/Syncroton/Model/IDevice.php
index e0c59ab..944a1f7 100644
--- a/lib/ext/Syncroton/Model/IDevice.php
+++ b/lib/ext/Syncroton/Model/IDevice.php
@@ -1,51 +1,51 @@
<?php
/**
* Syncroton
*
* @package Model
* @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 Model
* @property string id
* @property string deviceid
* @property string devicetype
* @property string policykey
* @property string policyId
* @property string ownerId
* @property string acsversion
* @property string pingfolder
* @property string pinglifetime
* @property string remotewipe
* @property string useragent
* @property string imei
* @property string model
* @property string friendlyname
* @property string os
* @property string oslanguage
* @property string phonenumber
* @property string pinglifetime
* @property string pingfolder
* @property string contactsfilter_id
* @property string calendarfilter_id
* @property string tasksfilter_id
* @property string emailfilter_id
* @property string lastsynccollection
*/
-interface Syncroton_Model_IDevice
+interface Syncroton_Model_IDevice extends Syncroton_Model_IEntry
{
/**
* Returns major firmware version of this device
*
* @return int/string
*/
public function getMajorVersion();
}
diff --git a/lib/ext/Syncroton/Model/IEntry.php b/lib/ext/Syncroton/Model/IEntry.php
index 34f6d53..c2c9ce7 100644
--- a/lib/ext/Syncroton/Model/IEntry.php
+++ b/lib/ext/Syncroton/Model/IEntry.php
@@ -1,61 +1,41 @@
<?php
/**
* Syncroton
*
* @package 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>
*/
/**
* class to handle ActiveSync contact
*
* @package Model
* @property string class
* @property string collectionId
* @property bool deletesAsMoves
* @property bool getChanges
* @property string syncKey
* @property int windowSize
*/
interface Syncroton_Model_IEntry
{
/**
*
* @param unknown_type $properties
*/
public function __construct($properties = null);
/**
- *
- * @param DOMElement $_domParrent
- */
- /**
- *
- * @param DOMElement $_domParrent
- * @param Syncroton_Model_IDevice $device
+ * return true if data have got changed after initial data got loaded via constructor
*/
- public function appendXML(DOMElement $_domParrent, Syncroton_Model_IDevice $device);
-
- /**
- * return array of valid properties
- *
- * @return array
- */
- public function getProperties();
+ public function isDirty();
/**
*
* @param array $properties
*/
- public function setFromArray(array $properties);
-
- /**
- *
- * @param SimpleXMLElement $xmlCollection
- * @throws InvalidArgumentException
- */
- public function setFromSimpleXMLElement(SimpleXMLElement $properties);
+ public function setFromArray(array $properties);
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/IEntry.php b/lib/ext/Syncroton/Model/IXMLEntry.php
similarity index 56%
copy from lib/ext/Syncroton/Model/IEntry.php
copy to lib/ext/Syncroton/Model/IXMLEntry.php
index 34f6d53..7b3ad0f 100644
--- a/lib/ext/Syncroton/Model/IEntry.php
+++ b/lib/ext/Syncroton/Model/IXMLEntry.php
@@ -1,61 +1,39 @@
<?php
/**
* Syncroton
*
* @package Model
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
- * @copyright Copyright (c) 2012-2012 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @copyright Copyright (c) 2012-2013 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync contact
*
* @package Model
- * @property string class
- * @property string collectionId
- * @property bool deletesAsMoves
- * @property bool getChanges
- * @property string syncKey
- * @property int windowSize
*/
-interface Syncroton_Model_IEntry
+interface Syncroton_Model_IXMLEntry extends Syncroton_Model_IEntry
{
- /**
- *
- * @param unknown_type $properties
- */
- public function __construct($properties = null);
-
- /**
- *
- * @param DOMElement $_domParrent
- */
/**
*
* @param DOMElement $_domParrent
* @param Syncroton_Model_IDevice $device
*/
public function appendXML(DOMElement $_domParrent, Syncroton_Model_IDevice $device);
/**
* return array of valid properties
*
* @return array
*/
public function getProperties();
- /**
- *
- * @param array $properties
- */
- public function setFromArray(array $properties);
-
/**
*
* @param SimpleXMLElement $xmlCollection
* @throws InvalidArgumentException
*/
public function setFromSimpleXMLElement(SimpleXMLElement $properties);
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/MeetingResponse.php b/lib/ext/Syncroton/Model/MeetingResponse.php
index 4d1e171..fded62d 100644
--- a/lib/ext/Syncroton/Model/MeetingResponse.php
+++ b/lib/ext/Syncroton/Model/MeetingResponse.php
@@ -1,46 +1,46 @@
<?php
/**
* Syncroton
*
* @package 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>
*/
/**
* class to handle MeetingResponse request
*
* @package Model
* @property int userResponse
* @property string collectionId
* @property string calendarId
* @property string requestId
* @property string instanceId
* @property string longId
*/
-class Syncroton_Model_MeetingResponse extends Syncroton_Model_AEntry
+class Syncroton_Model_MeetingResponse extends Syncroton_Model_AXMLEntry
{
protected $_xmlBaseElement = 'Request';
/**
* attendee status
*/
const RESPONSE_ACCEPTED = 1;
const RESPONSE_TENTATIVE = 2;
const RESPONSE_DECLINED = 3;
protected $_properties = array(
'MeetingResponse' => array(
'userResponse' => array('type' => 'number'),
'collectionId' => array('type' => 'string'),
'calendarId' => array('type' => 'string'),
'requestId' => array('type' => 'string'),
'instanceId' => array('type' => 'datetime'),
),
'Search' => array(
'longId' => array('type' => 'string')
)
);
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/Policy.php b/lib/ext/Syncroton/Model/Policy.php
index 9b1dcf3..9031d51 100644
--- a/lib/ext/Syncroton/Model/Policy.php
+++ b/lib/ext/Syncroton/Model/Policy.php
@@ -1,75 +1,75 @@
<?php
/**
* Syncroton
*
* @package 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>
*/
/**
* class to handle ActiveSync Sync command
*
* @package Model
*/
-class Syncroton_Model_Policy extends Syncroton_Model_AEntry implements Syncroton_Model_IPolicy
+class Syncroton_Model_Policy extends Syncroton_Model_AXMLEntry implements Syncroton_Model_IPolicy
{
protected $_xmlBaseElement = 'EASProvisionDoc';
protected $_properties = array(
'Internal' => array(
'id' => array('type' => 'string'),
'description' => array('type' => 'string'),
'name' => array('type' => 'string'),
'policyKey' => array('type' => 'string'),
),
'Provision' => array(
'allowBluetooth' => array('type' => 'number'),
'allowSMIMEEncryptionAlgorithmNegotiation' => array('type' => 'number'),
'allowBrowser' => array('type' => 'number'),
'allowCamera' => array('type' => 'number'),
'allowConsumerEmail' => array('type' => 'number'),
'allowDesktopSync' => array('type' => 'number'),
'allowHTMLEmail' => array('type' => 'number'),
'allowInternetSharing' => array('type' => 'number'),
'allowIrDA' => array('type' => 'number'),
'allowPOPIMAPEmail' => array('type' => 'number'),
'allowRemoteDesktop' => array('type' => 'number'),
'allowSimpleDevicePassword' => array('type' => 'number'),
'allowSMIMEEncryptionAlgorithmNegotiation' => array('type' => 'number'),
'allowSMIMESoftCerts' => array('type' => 'number'),
'allowStorageCard' => array('type' => 'number'),
'allowTextMessaging' => array('type' => 'number'),
'allowUnsignedApplications' => array('type' => 'number'),
'allowUnsignedInstallationPackages' => array('type' => 'number'),
'allowWifi' => array('type' => 'number'),
'alphanumericDevicePasswordRequired' => array('type' => 'number'),
'approvedApplicationList' => array('type' => 'container', 'childName' => 'Hash'),
'attachmentsEnabled' => array('type' => 'number'),
'devicePasswordEnabled' => array('type' => 'number'),
'devicePasswordExpiration' => array('type' => 'number'),
'devicePasswordHistory' => array('type' => 'number'),
'maxAttachmentSize' => array('type' => 'number'),
'maxCalendarAgeFilter' => array('type' => 'number'),
'maxDevicePasswordFailedAttempts' => array('type' => 'number'),
'maxEmailAgeFilter' => array('type' => 'number'),
'maxEmailBodyTruncationSize' => array('type' => 'number'),
'maxEmailHTMLBodyTruncationSize' => array('type' => 'number'),
'maxInactivityTimeDeviceLock' => array('type' => 'number'),
'minDevicePasswordComplexCharacters' => array('type' => 'number'),
'minDevicePasswordLength' => array('type' => 'number'),
'passwordRecoveryEnabled' => array('type' => 'number'),
'requireDeviceEncryption' => array('type' => 'number'),
'requireEncryptedSMIMEMessages' => array('type' => 'number'),
'requireEncryptionSMIMEAlgorithm' => array('type' => 'number'),
'requireManualSyncWhenRoaming' => array('type' => 'number'),
'requireSignedSMIMEAlgorithm' => array('type' => 'number'),
'requireSignedSMIMEMessages' => array('type' => 'number'),
'requireStorageCardEncryption' => array('type' => 'number'),
'unapprovedInROMApplicationList' => array('type' => 'container', 'childName' => 'ApplicationName')
)
);
}
diff --git a/lib/ext/Syncroton/Model/SendMail.php b/lib/ext/Syncroton/Model/SendMail.php
index b11d9b9..f8641fd 100644
--- a/lib/ext/Syncroton/Model/SendMail.php
+++ b/lib/ext/Syncroton/Model/SendMail.php
@@ -1,33 +1,33 @@
<?php
/**
* Syncroton
*
* @package Model
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2012-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @copyright Copyright (c) 2012-2012 Kolab SYstems AG (http://www.kolabsys.com)
* @author Lars Kneschke <l.kneschke@metaways.de>
* @author Aleksander Machniak <machniak@kolabsys.com>
*/
/**
* Class to handle ActiveSync SendMail element
*
* @package Syncroton
* @subpackage Model
*/
-class Syncroton_Model_SendMail extends Syncroton_Model_AEntry
+class Syncroton_Model_SendMail extends Syncroton_Model_AXMLEntry
{
protected $_properties = array(
'ComposeMail' => array(
'accountId' => array('type' => 'string'),
'clientId' => array('type' => 'string'),
'mime' => array('type' => 'byteArray'),
'saveInSentItems' => array('type' => 'string'),
'status' => array('type' => 'number'),
),
'RightsManagement' => array(
'templateID' => array('type' => 'string'),
)
);
}
diff --git a/lib/ext/Syncroton/Model/SmartForward.php b/lib/ext/Syncroton/Model/SmartForward.php
index 40ae481..5d34c9e 100644
--- a/lib/ext/Syncroton/Model/SmartForward.php
+++ b/lib/ext/Syncroton/Model/SmartForward.php
@@ -1,35 +1,35 @@
<?php
/**
* Syncroton
*
* @package Model
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2012-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @copyright Copyright (c) 2012-2012 KolabSYstems AG (http://www.kolabsys.com)
* @author Lars Kneschke <l.kneschke@metaways.de>
* @author Aleksander Machniak <machniak@kolabsys.com>
*/
/**
* Class to handle ActiveSync SmartForward element
*
* @package Syncroton
* @subpackage Model
*/
-class Syncroton_Model_SmartForward extends Syncroton_Model_AEntry
+class Syncroton_Model_SmartForward extends Syncroton_Model_AXMLEntry
{
protected $_properties = array(
'ComposeMail' => array(
'accountId' => array('type' => 'string'),
'clientId' => array('type' => 'string'),
'mime' => array('type' => 'byteArray'),
'replaceMime' => array('type' => 'string'),
'saveInSentItems' => array('type' => 'string'),
'source' => array('type' => 'container'), // or string
'status' => array('type' => 'number'),
),
'RightsManagement' => array(
'templateID' => array('type' => 'string'),
)
);
}
diff --git a/lib/ext/Syncroton/Model/SmartReply.php b/lib/ext/Syncroton/Model/SmartReply.php
index b61cbc6..856872c 100644
--- a/lib/ext/Syncroton/Model/SmartReply.php
+++ b/lib/ext/Syncroton/Model/SmartReply.php
@@ -1,35 +1,35 @@
<?php
/**
* Syncroton
*
* @package Model
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2012-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @copyright Copyright (c) 2012-2012 Kolab Systems AG (http://www.kolabsys.com)
* @author Lars Kneschke <l.kneschke@metaways.de>
* @author Aleksander Machniak <machniak@kolabsys.com>
*/
/**
* Class to handle ActiveSync SmartReply element
*
* @package Syncroton
* @subpackage Model
*/
-class Syncroton_Model_SmartReply extends Syncroton_Model_AEntry
+class Syncroton_Model_SmartReply extends Syncroton_Model_AXMLEntry
{
protected $_properties = array(
'ComposeMail' => array(
'accountId' => array('type' => 'string'),
'clientId' => array('type' => 'string'),
'mime' => array('type' => 'byteArray'),
'replaceMime' => array('type' => 'string'),
'saveInSentItems' => array('type' => 'string'),
'source' => array('type' => 'container'), // or string
'status' => array('type' => 'number'),
),
'RightsManagement' => array(
'templateID' => array('type' => 'string'),
)
);
}
diff --git a/lib/ext/Syncroton/Model/StoreResponse.php b/lib/ext/Syncroton/Model/StoreResponse.php
index 84fe036..bd8855e 100644
--- a/lib/ext/Syncroton/Model/StoreResponse.php
+++ b/lib/ext/Syncroton/Model/StoreResponse.php
@@ -1,91 +1,91 @@
<?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>
*/
/**
* Class to handle ActiveSync Search/Response/Store element
*
* @package Syncroton
* @subpackage Model
* @property string status
* @property array result
* @property array range
* @property int total
*/
-class Syncroton_Model_StoreResponse extends Syncroton_Model_AEntry
+class Syncroton_Model_StoreResponse extends Syncroton_Model_AXMLEntry
{
/**
* status constants
*/
const STATUS_SUCCESS = 1;
const STATUS_INVALIDREQUEST = 2;
const STATUS_SERVERERROR = 3;
const STATUS_BADLINK = 4;
const STATUS_ACCESSDENIED = 5;
const STATUS_NOTFOUND = 6;
const STATUS_CONNECTIONFAILED = 7;
const STATUS_TOOCOMPLEX = 8;
const STATUS_TIMEDOUT = 10;
const STATUS_FOLDERSYNCREQUIRED = 11;
const STATUS_ENDOFRANGE = 12;
const STATUS_ACCESSBLOCKED = 13;
const STATUS_CREDENTIALSREQUIRED = 14;
protected $_xmlBaseElement = 'Store';
protected $_properties = array(
'Search' => array(
'status' => array('type' => 'number'),
'result' => array('type' => 'container', 'multiple' => true),
'range' => array('type' => 'string'),
'total' => array('type' => 'number'),
)
);
/**
* (non-PHPdoc)
- * @see Syncroton_Model_AEntry::appendXML()
+ * @see Syncroton_Model_AXMLEntry::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 === '') {
continue;
}
list ($nameSpace, $elementProperties) = $this->_getElementProperties($elementName);
$nameSpace = 'uri:' . $nameSpace;
switch ($elementName) {
case 'result':
foreach ($value as $result) {
$element = $_domParrent->ownerDocument->createElementNS($nameSpace, 'Result');
$result->appendXML($element, $device);
$_domParrent->appendChild($element);
}
break;
case 'range':
if (is_array($value) && count($value) == 2) {
$value = implode('-', $value);
}
default:
$element = $_domParrent->ownerDocument->createElementNS($nameSpace, ucfirst($elementName));
$element->appendChild($_domParrent->ownerDocument->createTextNode($value));
$_domParrent->appendChild($element);
}
}
}
}
diff --git a/lib/ext/Syncroton/Model/StoreResponseResult.php b/lib/ext/Syncroton/Model/StoreResponseResult.php
index b129fa7..e2deb91 100644
--- a/lib/ext/Syncroton/Model/StoreResponseResult.php
+++ b/lib/ext/Syncroton/Model/StoreResponseResult.php
@@ -1,31 +1,31 @@
<?php
/**
* Syncroton
*
* @package 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>
*/
/**
* class to handle ActiveSync Search/Response/Store/Result elements
*
* @package Syncroton
* @subpackage Model
*/
-class Syncroton_Model_StoreResponseResult extends Syncroton_Model_AEntry
+class Syncroton_Model_StoreResponseResult extends Syncroton_Model_AXMLEntry
{
protected $_xmlBaseElement = 'Result';
protected $_properties = array(
'AirSync' => array(
'class' => array('type' => 'string'),
'collectionId' => array('type' => 'string'),
),
'Search' => array(
'longId' => array('type' => 'string', 'supportedSince' => '2.5'),
'properties' => array('type' => 'container', 'supportedSince' => '2.5'),
)
);
}
diff --git a/lib/ext/Syncroton/Model/SyncCollection.php b/lib/ext/Syncroton/Model/SyncCollection.php
index 220226b..b6370dc 100644
--- a/lib/ext/Syncroton/Model/SyncCollection.php
+++ b/lib/ext/Syncroton/Model/SyncCollection.php
@@ -1,312 +1,312 @@
<?php
/**
* Syncroton
*
* @package 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>
*/
/**
* class to handle ActiveSync Sync collection
*
* @package Model
* @property string class
* @property string collectionId
* @property bool deletesAsMoves
* @property bool getChanges
* @property string syncKey
* @property int windowSize
*/
-class Syncroton_Model_SyncCollection extends Syncroton_Model_AEntry
+class Syncroton_Model_SyncCollection extends Syncroton_Model_AXMLEntry
{
protected $_elements = array(
'syncState' => null,
'folder' => null
);
protected $_xmlCollection;
protected $_xmlBaseElement = 'Collection';
public function __construct($properties = null)
{
if ($properties instanceof SimpleXMLElement) {
$this->setFromSimpleXMLElement($properties);
} elseif (is_array($properties)) {
$this->setFromArray($properties);
}
if (!isset($this->_elements['options'])) {
$this->_elements['options'] = array();
}
if (!isset($this->_elements['options']['filterType'])) {
$this->_elements['options']['filterType'] = Syncroton_Command_Sync::FILTER_NOTHING;
}
if (!isset($this->_elements['options']['mimeSupport'])) {
$this->_elements['options']['mimeSupport'] = Syncroton_Command_Sync::MIMESUPPORT_DONT_SEND_MIME;
}
if (!isset($this->_elements['options']['mimeTruncation'])) {
$this->_elements['options']['mimeTruncation'] = Syncroton_Command_Sync::TRUNCATE_NOTHING;
}
if (!isset($this->_elements['options']['bodyPreferences'])) {
$this->_elements['options']['bodyPreferences'] = array();
}
}
/**
* return XML element which holds all client Add commands
*
* @return SimpleXMLElement
*/
public function getClientAdds()
{
if (! $this->_xmlCollection instanceof SimpleXMLElement) {
throw new InvalidArgumentException('no collection xml element set');
}
return $this->_xmlCollection->Commands->Add;
}
/**
* return XML element which holds all client Change commands
*
* @return SimpleXMLElement
*/
public function getClientChanges()
{
if (! $this->_xmlCollection instanceof SimpleXMLElement) {
throw new InvalidArgumentException('no collection xml element set');
}
return $this->_xmlCollection->Commands->Change;
}
/**
* return XML element which holds all client Delete commands
*
* @return SimpleXMLElement
*/
public function getClientDeletes()
{
if (! $this->_xmlCollection instanceof SimpleXMLElement) {
throw new InvalidArgumentException('no collection xml element set');
}
return $this->_xmlCollection->Commands->Delete;
}
/**
* return XML element which holds all client Fetch commands
*
* @return SimpleXMLElement
*/
public function getClientFetches()
{
if (! $this->_xmlCollection instanceof SimpleXMLElement) {
throw new InvalidArgumentException('no collection xml element set');
}
return $this->_xmlCollection->Commands->Fetch;
}
/**
* check if client sent a Add command
*
* @throws InvalidArgumentException
* @return bool
*/
public function hasClientAdds()
{
if (! $this->_xmlCollection instanceof SimpleXMLElement) {
return false;
}
return isset($this->_xmlCollection->Commands->Add);
}
/**
* check if client sent a Change command
*
* @throws InvalidArgumentException
* @return bool
*/
public function hasClientChanges()
{
if (! $this->_xmlCollection instanceof SimpleXMLElement) {
return false;
}
return isset($this->_xmlCollection->Commands->Change);
}
/**
* check if client sent a Delete command
*
* @throws InvalidArgumentException
* @return bool
*/
public function hasClientDeletes()
{
if (! $this->_xmlCollection instanceof SimpleXMLElement) {
return false;
}
return isset($this->_xmlCollection->Commands->Delete);
}
/**
* check if client sent a Fetch command
*
* @throws InvalidArgumentException
* @return bool
*/
public function hasClientFetches()
{
if (! $this->_xmlCollection instanceof SimpleXMLElement) {
return false;
}
return isset($this->_xmlCollection->Commands->Fetch);
}
/**
* this functions does not only set from SimpleXMLElement but also does merge from SimpleXMLElement
* to support partial sync requests
*
* @param SimpleXMLElement $properties
* @throws InvalidArgumentException
*/
public function setFromSimpleXMLElement(SimpleXMLElement $properties)
{
if (!in_array($properties->getName(), (array) $this->_xmlBaseElement)) {
throw new InvalidArgumentException('Unexpected element name: ' . $properties->getName());
}
$this->_xmlCollection = $properties;
if (isset($properties->CollectionId)) {
$this->_elements['collectionId'] = (string)$properties->CollectionId;
}
if (isset($properties->SyncKey)) {
$this->_elements['syncKey'] = (int)$properties->SyncKey;
}
if (isset($properties->Class)) {
$this->_elements['class'] = (string)$properties->Class;
} elseif (!array_key_exists('class', $this->_elements)) {
$this->_elements['class'] = null;
}
if (isset($properties->WindowSize)) {
$this->_elements['windowSize'] = (string)$properties->WindowSize;
} elseif (!array_key_exists('windowSize', $this->_elements)) {
$this->_elements['windowSize'] = 100;
}
if (isset($properties->DeletesAsMoves)) {
if ((string)$properties->DeletesAsMoves === '0') {
$this->_elements['deletesAsMoves'] = false;
} else {
$this->_elements['deletesAsMoves'] = true;
}
} elseif (!array_key_exists('deletesAsMoves', $this->_elements)) {
$this->_elements['deletesAsMoves'] = true;
}
if (isset($properties->ConversationMode)) {
if ((string)$properties->ConversationMode === '0') {
$this->_elements['conversationMode'] = false;
} else {
$this->_elements['conversationMode'] = true;
}
} elseif (!array_key_exists('conversationMode', $this->_elements)) {
$this->_elements['conversationMode'] = true;
}
if (isset($properties->GetChanges)) {
if ((string)$properties->GetChanges === '0') {
$this->_elements['getChanges'] = false;
} else {
$this->_elements['getChanges'] = true;
}
} elseif (!array_key_exists('getChanges', $this->_elements)) {
$this->_elements['getChanges'] = true;
}
if (isset($properties->Supported)) {
// @todo collect supported elements
}
// process options
if (isset($properties->Options)) {
$this->_elements['options'] = array();
// optional parameters
if (isset($properties->Options->FilterType)) {
$this->_elements['options']['filterType'] = (int)$properties->Options->FilterType;
}
if (isset($properties->Options->MIMESupport)) {
$this->_elements['options']['mimeSupport'] = (int)$properties->Options->MIMESupport;
}
if (isset($properties->Options->MIMETruncation)) {
$this->_elements['options']['mimeTruncation'] = (int)$properties->Options->MIMETruncation;
}
if (isset($properties->Options->Class)) {
$this->_elements['options']['class'] = (string)$properties->Options->Class;
}
// try to fetch element from AirSyncBase:BodyPreference
$airSyncBase = $properties->Options->children('uri:AirSyncBase');
if (isset($airSyncBase->BodyPreference)) {
foreach ($airSyncBase->BodyPreference as $bodyPreference) {
$type = (int) $bodyPreference->Type;
$this->_elements['options']['bodyPreferences'][$type] = array(
'type' => $type
);
// optional
if (isset($bodyPreference->TruncationSize)) {
$this->_elements['options']['bodyPreferences'][$type]['truncationSize'] = (int) $bodyPreference->TruncationSize;
}
}
}
if (isset($airSyncBase->BodyPartPreference)) {
// process BodyPartPreference elements
}
}
}
public function toArray()
{
$result = array();
foreach (array('syncKey', 'collectionId', 'deletesAsMoves', 'conversationMode', 'getChanges', 'windowSize', 'class', 'options') as $key) {
if (isset($this->$key)) {
$result[$key] = $this->$key;
}
}
return $result;
}
public function &__get($name)
{
if (array_key_exists($name, $this->_elements)) {
return $this->_elements[$name];
}
echo $name . PHP_EOL;
return null;
}
public function __set($name, $value)
{
$this->_elements[$name] = $value;
}
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/Task.php b/lib/ext/Syncroton/Model/Task.php
index c4e032f..8ab473f 100644
--- a/lib/ext/Syncroton/Model/Task.php
+++ b/lib/ext/Syncroton/Model/Task.php
@@ -1,46 +1,46 @@
<?php
/**
* Syncroton
*
* @package 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>
*/
/**
* class to handle ActiveSync task
*
* @package Model
* @property string class
* @property string collectionId
* @property bool deletesAsMoves
* @property bool getChanges
* @property string syncKey
* @property int windowSize
*/
-class Syncroton_Model_Task extends Syncroton_Model_AEntry
+class Syncroton_Model_Task extends Syncroton_Model_AXMLEntry
{
protected $_xmlBaseElement = 'ApplicationData';
protected $_properties = array(
'AirSyncBase' => array(
'body' => array('type' => 'container', 'class' => 'Syncroton_Model_EmailBody')
),
'Tasks' => array(
'categories' => array('type' => 'container', 'childElement' => 'category'),
'complete' => array('type' => 'number'),
'dateCompleted' => array('type' => 'datetime'),
'dueDate' => array('type' => 'datetime'),
'importance' => array('type' => 'number'),
'recurrence' => array('type' => 'container'),
'reminderSet' => array('type' => 'number'),
'reminderTime' => array('type' => 'datetime'),
'sensitivity' => array('type' => 'number'),
'startDate' => array('type' => 'datetime'),
'subject' => array('type' => 'string'),
'utcDueDate' => array('type' => 'datetime'),
'utcStartDate' => array('type' => 'datetime'),
)
);
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Model/TaskRecurrence.php b/lib/ext/Syncroton/Model/TaskRecurrence.php
index eb0b4b0..116ce14 100644
--- a/lib/ext/Syncroton/Model/TaskRecurrence.php
+++ b/lib/ext/Syncroton/Model/TaskRecurrence.php
@@ -1,66 +1,66 @@
<?php
/**
* Syncroton
*
* @package 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>
*/
/**
* class to handle ActiveSync event
*
* @package Model
* @property string class
* @property string collectionId
* @property bool deletesAsMoves
* @property bool getChanges
* @property string syncKey
* @property int windowSize
*/
-class Syncroton_Model_TaskRecurrence extends Syncroton_Model_AEntry
+class Syncroton_Model_TaskRecurrence extends Syncroton_Model_AXMLEntry
{
protected $_xmlBaseElement = 'Recurrence';
/**
* recur types
*/
const TYPE_DAILY = 0; // Recurs daily.
const TYPE_WEEKLY = 1; // Recurs weekly
const TYPE_MONTHLY = 2; // Recurs monthly
const TYPE_MONTHLY_DAYN = 3; // Recurs monthly on the nth day
const TYPE_YEARLY = 5; // Recurs yearly
const TYPE_YEARLY_DAYN = 6; // Recurs yearly on the nth day
/**
* day of week constants
*/
const RECUR_DOW_SUNDAY = 1;
const RECUR_DOW_MONDAY = 2;
const RECUR_DOW_TUESDAY = 4;
const RECUR_DOW_WEDNESDAY = 8;
const RECUR_DOW_THURSDAY = 16;
const RECUR_DOW_FRIDAY = 32;
const RECUR_DOW_SATURDAY = 64;
protected $_properties = array(
'Tasks' => array(
'calendarType' => array('type' => 'number'),
'dayOfMonth' => array('type' => 'number'),
'dayOfWeek' => array('type' => 'number'),
'deadOccur' => array('type' => 'number'),
'firstDayOfWeek' => array('type' => 'number'),
'interval' => array('type' => 'number'),
'isLeapMonth' => array('type' => 'number'),
'monthOfYear' => array('type' => 'number'),
'occurrences' => array('type' => 'number'),
'regenerate' => array('type' => 'number'),
'start' => array('type' => 'datetime'),
'type' => array('type' => 'number'),
'until' => array('type' => 'datetime'),
'weekOfMonth' => array('type' => 'number'),
)
);
}
\ No newline at end of file
diff --git a/lib/ext/Syncroton/Server.php b/lib/ext/Syncroton/Server.php
index 2ae9a75..a938ffb 100644
--- a/lib/ext/Syncroton/Server.php
+++ b/lib/ext/Syncroton/Server.php
@@ -1,407 +1,402 @@
<?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;
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']);
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");
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");
if (version_compare($device->acsversion, '14.0', '>=')) {
$response = $sepn->domDocument;
} else {
// pre 14.0 method
header("HTTP/1.1 449 Retry after sending a PROVISION command");
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) {
$response->formatOutput = true;
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " xml response:\n" . $response->saveXML());
$response->formatOutput = false;
}
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);
$encoder->encode($response);
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);
}
}
}
}
/**
* 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
$length = ord(fread($stream, 1));
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 {
$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'),
'itemId' => $request->getQuery('ItemId'),
'attachmentName' => $request->getQuery('AttachmentName'),
'acceptMultipart' => $request->getServer('HTTP_MS_ASACCEPTMULTIPART') == 'T'
);
}
$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']);
-
- if ($device->useragent != $requestParameters['userAgent']) {
- $device->useragent = $requestParameters['userAgent'];
- $need_update = true;
- }
- if ($device->acsversion != $requestParameters['protocolVersion']) {
- $device->acsversion = $requestParameters['protocolVersion'];
- $need_update = true;
- }
-
- if ($need_update) {
+
+ $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
)));
}
return $device;
}
}
diff --git a/lib/kolab_sync_data.php b/lib/kolab_sync_data.php
index ee97657..1f2e384 100644
--- a/lib/kolab_sync_data.php
+++ b/lib/kolab_sync_data.php
@@ -1,1269 +1,1275 @@
<?php
/**
+--------------------------------------------------------------------------+
| Kolab Sync (ActiveSync for Kolab) |
| |
| Copyright (C) 2011-2012, Kolab Systems AG <contact@kolabsys.com> |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU Affero General Public License as published |
| by the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/> |
+--------------------------------------------------------------------------+
| Author: Aleksander Machniak <machniak@kolabsys.com> |
+--------------------------------------------------------------------------+
*/
/**
* Base class for Syncroton data backends
*/
abstract class kolab_sync_data implements Syncroton_Data_IData
{
/**
* ActiveSync protocol version
*
* @var int
*/
protected $asversion = 0;
/**
* information about the current device
*
* @var Syncroton_Model_IDevice
*/
protected $device;
/**
* timestamp to use for all sync requests
*
* @var DateTime
*/
protected $syncTimeStamp;
/**
* name of model to use
*
* @var string
*/
protected $modelName;
/**
* type of the default folder
*
* @var int
*/
protected $defaultFolderType;
/**
* default container for new entries
*
* @var string
*/
protected $defaultFolder;
/**
* type of user created folders
*
* @var int
*/
protected $folderType;
/**
* Internal cache for kolab_storage folder objects
*
* @var array
*/
protected $folders = array();
/**
* Timezone
*
* @var string
*/
protected $timezone;
const RESULT_OBJECT = 0;
const RESULT_UID = 1;
const RESULT_COUNT = 2;
/**
* Recurrence types
*/
const RECUR_TYPE_DAILY = 0; // Recurs daily.
const RECUR_TYPE_WEEKLY = 1; // Recurs weekly
const RECUR_TYPE_MONTHLY = 2; // Recurs monthly
const RECUR_TYPE_MONTHLY_DAYN = 3; // Recurs monthly on the nth day
const RECUR_TYPE_YEARLY = 5; // Recurs yearly
const RECUR_TYPE_YEARLY_DAYN = 6; // Recurs yearly on the nth day
/**
* Day of week constants
*/
const RECUR_DOW_SUNDAY = 1;
const RECUR_DOW_MONDAY = 2;
const RECUR_DOW_TUESDAY = 4;
const RECUR_DOW_WEDNESDAY = 8;
const RECUR_DOW_THURSDAY = 16;
const RECUR_DOW_FRIDAY = 32;
const RECUR_DOW_SATURDAY = 64;
const RECUR_DOW_LAST = 127; // The last day of the month. Used as a special value in monthly or yearly recurrences.
/**
* Mapping of recurrence types
*
* @var array
*/
protected $recurTypeMap = array(
self::RECUR_TYPE_DAILY => 'DAILY',
self::RECUR_TYPE_WEEKLY => 'WEEKLY',
self::RECUR_TYPE_MONTHLY => 'MONTHLY',
self::RECUR_TYPE_MONTHLY_DAYN => 'MONTHLY',
self::RECUR_TYPE_YEARLY => 'YEARLY',
self::RECUR_TYPE_YEARLY_DAYN => 'YEARLY',
);
/**
* Mapping of weekdays
* NOTE: ActiveSync uses a bitmask
*
* @var array
*/
protected $recurDayMap = array(
'SU' => self::RECUR_DOW_SUNDAY,
'MO' => self::RECUR_DOW_MONDAY,
'TU' => self::RECUR_DOW_TUESDAY,
'WE' => self::RECUR_DOW_WEDNESDAY,
'TH' => self::RECUR_DOW_THURSDAY,
'FR' => self::RECUR_DOW_FRIDAY,
'SA' => self::RECUR_DOW_SATURDAY,
);
/**
* the constructor
*
* @param Syncroton_Model_IDevice $device
* @param DateTime $syncTimeStamp
*/
public function __construct(Syncroton_Model_IDevice $device, DateTime $syncTimeStamp)
{
$this->backend = kolab_sync_backend::get_instance();
$this->device = $device;
$this->asversion = floatval($device->acsversion);
$this->syncTimeStamp = $syncTimeStamp;
$this->defaultRootFolder = $this->defaultFolder . '::Syncroton';
// set internal timezone of kolab_format to user timezone
try {
$this->timezone = rcube::get_instance()->config->get('timezone', 'GMT');
kolab_format::$timezone = new DateTimeZone($this->timezone);
}
catch (Exception $e) {
//rcube::raise_error($e, true);
$this->timezone = 'GMT';
kolab_format::$timezone = new DateTimeZone('GMT');
}
}
/**
* return list of supported folders for this backend
*
* @return array
*/
public function getAllFolders()
{
$list = array();
// device supports multiple folders ?
if (in_array(strtolower($this->device->devicetype), array('iphone', 'ipad', 'thundertine', 'windowsphone'))) {
// get the folders the user has access to
$list = $this->backend->folders_list($this->device->deviceid, $this->modelName);
}
else if ($default = $this->getDefaultFolder()) {
$list = array($default['serverId'] => $default);
}
// getAllFolders() is called only in FolderSync
// throw Syncroton_Exception_Status_FolderSync exception
if (!is_array($list)) {
throw new Syncroton_Exception_Status_FolderSync(Syncroton_Exception_Status_FolderSync::FOLDER_SERVER_ERROR);
}
foreach ($list as $idx => $folder) {
$list[$idx] = new Syncroton_Model_Folder($folder);
}
return $list;
}
/**
* Returns default folder for current class type.
*/
protected function getDefaultFolder()
{
// Check if there's any folder configured for sync
$folders = $this->backend->folders_list($this->device->deviceid, $this->modelName);
if (empty($folders)) {
return $folders;
}
foreach ($folders as $folder) {
if ($folder['type'] == $this->defaultFolderType) {
$default = $folder;
break;
}
}
// Return first on the list if there's no default
if (empty($default)) {
$key = array_shift(array_keys($folders));
$default = $folders[$key];
// make sure the type is default here
$default['type'] = $this->defaultFolderType;
}
// Remember real folder ID and set ID/name to root folder
$default['realid'] = $default['serverId'];
$default['serverId'] = $this->defaultRootFolder;
$default['displayName'] = $this->defaultFolder;
return $default;
}
/**
* Creates a folder
*/
public function createFolder(Syncroton_Model_IFolder $folder)
{
$parentid = $folder->parentId;
$type = $folder->type;
$display_name = $folder->displayName;
if ($parentid) {
$parent = $this->backend->folder_id2name($parentid, $this->device->deviceid);
}
$name = rcube_charset::convert($display_name, kolab_sync::CHARSET, 'UTF7-IMAP');
if ($parent !== null) {
$rcube = rcube::get_instance();
$storage = $rcube->get_storage();
$delim = $storage->get_hierarchy_delimiter();
$name = $parent . $delim . $name;
}
// Create IMAP folder
$result = $this->backend->folder_create($name, $type, $this->device->deviceid);
if ($result) {
$folder->serverId = $this->backend->folder_id($name);
return $folder;
}
// @TODO: throw exception
}
/**
* Updates a folder
*/
public function updateFolder(Syncroton_Model_IFolder $folder)
{
$parentid = $folder->parentId;
$type = $folder->type;
$display_name = $folder->displayName;
$old_name = $this->backend->folder_id2name($folder->serverId, $this->device->deviceid);
if ($parentid) {
$parent = $this->backend->folder_id2name($parentid, $this->device->deviceid);
}
$name = rcube_charset::convert($display_name, kolab_sync::CHARSET, 'UTF7-IMAP');
if ($parent !== null) {
$rcube = rcube::get_instance();
$storage = $rcube->get_storage();
$delim = $storage->get_hierarchy_delimiter();
$name = $parent . $delim . $name;
}
// Rename/move IMAP folder
if ($name == $old_name) {
$result = true;
// @TODO: folder type change?
}
else {
$result = $this->backend->folder_rename($old_name, $name, $type, $this->device->deviceid);
}
if ($result) {
$folder->serverId = $this->backend->folder_id($name);
return $folder;
}
// @TODO: throw exception
}
/**
* Deletes a folder
*/
public function deleteFolder($folder)
{
if ($folder instanceof Syncroton_Model_IFolder) {
$folder = $folder->serverId;
}
$name = $this->backend->folder_id2name($folder, $this->device->deviceid);
// @TODO: throw exception
return $this->backend->folder_delete($name, $this->device->deviceid);
}
/**
* Moves object into another location (folder)
*
* @param string $srcFolderId Source folder identifier
* @param string $serverId Object identifier
* @param string $dstFolderId Destination folder identifier
*
* @throws Syncroton_Exception_Status
* @return string New object identifier
*/
public function moveItem($srcFolderId, $serverId, $dstFolderId)
{
$item = $this->getObject($srcFolderId, $serverId, $folder);
if (!$item || !$folder) {
throw new Syncroton_Exception_Status_MoveItems(Syncroton_Exception_Status_MoveItems::INVALID_SOURCE);
}
$dstname = $this->backend->folder_id2name($dstFolderId, $this->device->deviceid);
if ($dstname === null) {
throw new Syncroton_Exception_Status_MoveItems(Syncroton_Exception_Status_MoveItems::INVALID_DESTINATION);
}
if (!$folder->move($serverId, $dstname)) {
throw new Syncroton_Exception_Status_MoveItems(Syncroton_Exception_Status_MoveItems::INVALID_SOURCE);
}
return $item['uid'];
}
/**
* Add entry
*
* @param string $folderId Folder identifier
* @param Syncroton_Model_IEntry $entry Entry object
*
* @return string ID of the created entry
*/
public function createEntry($folderId, Syncroton_Model_IEntry $entry)
{
$entry = $this->toKolab($entry, $folderId);
$entry = $this->createObject($folderId, $entry);
if (empty($entry)) {
throw new Syncroton_Exception_Status_Sync(Syncroton_Exception_Status_Sync::SYNC_SERVER_ERROR);
}
return $entry['uid'];
}
/**
* update existing entry
*
* @param string $folderId
* @param string $serverId
* @param SimpleXMLElement $entry
*
* @return string ID of the updated entry
*/
public function updateEntry($folderId, $serverId, Syncroton_Model_IEntry $entry)
{
$oldEntry = $this->getObject($folderId, $serverId);
if (empty($oldEntry)) {
throw new Syncroton_Exception_NotFound('id not found');
}
$entry = $this->toKolab($entry, $folderId, $oldEntry);
$entry = $this->updateObject($folderId, $serverId, $entry);
if (empty($entry)) {
throw new Syncroton_Exception_Status_Sync(Syncroton_Exception_Status_Sync::SYNC_SERVER_ERROR);
}
return $entry['uid'];
}
/**
* delete entry
*
* @param string $folderId
* @param string $serverId
* @param array $collectionData
*/
public function deleteEntry($folderId, $serverId, $collectionData)
{
$deleted = $this->deleteObject($folderId, $serverId);
if (!$deleted) {
throw new Syncroton_Exception_Status_Sync(Syncroton_Exception_Status_Sync::SYNC_SERVER_ERROR);
}
}
public function getFileReference($fileReference)
{
// to be implemented by Email data class
// @TODO: throw "unimplemented" exception here?
}
/**
* Search for existing entries
*
* @param string $folderid
* @param array $filter
* @param int $result_type Type of the result (see RESULT_* constants)
*
* @return array|int Search result as count or array of uids/objects
*/
protected function searchEntries($folderid, $filter = array(), $result_type = self::RESULT_UID)
{
if ($folderid == $this->defaultRootFolder) {
$folders = $this->backend->folders_list($this->device->deviceid, $this->modelName);
if (!is_array($folders)) {
throw new Syncroton_Exception_Status(Syncroton_Exception_Status::SERVER_ERROR);
}
$folders = array_keys($folders);
}
else {
$folders = array($folderid);
}
// there's a PHP Warning from kolab_storage if $filter isn't an array
if (empty($filter)) {
$filter = array();
}
$result = $result_type == self::RESULT_COUNT ? 0 : array();
foreach ($folders as $folderid) {
$foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid);
if ($foldername === null) {
continue;
}
$folder = $this->getFolderObject($foldername);
if (!$folder) {
continue;
}
switch ($result_type) {
case self::RESULT_COUNT:
$result += (int) $folder->count($filter);
break;
case self::RESULT_UID:
if ($uids = $folder->get_uids($filter)) {
$result = array_merge($result, $uids);
}
break;
case self::RESULT_OBJECT:
default:
if ($objects = $folder->select($filter)) {
$result = array_merge($result, $objects);
}
}
}
return $result;
}
/**
* Returns filter query array according to specified ActiveSync FilterType
*
* @param int $filter_type Filter type
*
* @param array Filter query
*/
protected function filter($filter_type = 0)
{
// overwrite by child class according to specified type
return array();
}
/**
* get all entries changed between to dates
*
* @param string $folderId
* @param DateTime $start
* @param DateTime $end
* @return array
*/
public function getChangedEntries($folderId, DateTime $start, DateTime $end = null)
{
$filter = array(array('changed', '>', $start));
if ($endTimeStamp) {
$filter[] = array('changed', '<=', $end);
}
$result = $this->searchEntries($folderId, $filter, self::RESULT_UID);
return $result;
}
/**
* get count of entries changed between two dates
*
* @param string $folderId
* @param DateTime $start
* @param DateTime $end
*
* @return int
*/
public function getChangedEntriesCount($folderId, DateTime $start, DateTime $end = null)
{
$filter = array(array('changed', '>', $start));
if ($endTimeStamp) {
$filter[] = array('changed', '<=', $end);
}
$result = $this->searchEntries($folderId, $filter, self::RESULT_COUNT);
return $result;
}
/**
* get id's of all entries available on the server
*
* @param string $folderId
* @param int $filterType
*
* @return array
*/
public function getServerEntries($folder_id, $filter_type)
{
$filter = $this->filter($filter_type);
$result = $this->searchEntries($folder_id, $filter, self::RESULT_UID);
return $result;
}
/**
* get count of all entries available on the server
*
* @param string $folderId
* @param int $filterType
*
* @return int
*/
public function getServerEntriesCount($folder_id, $filter_type)
{
$filter = $this->filter($filter_type);
$result = $this->searchEntries($folder_id, $filter, self::RESULT_COUNT);
return $result;
}
public function getCountOfChanges(Syncroton_Backend_IContent $contentBackend, Syncroton_Model_IFolder $folder, Syncroton_Model_ISyncState $syncState)
{
$allClientEntries = $contentBackend->getFolderState($this->device, $folder);
// @TODO: Consider looping over all folders here, not in getServerEntries() and
// getChangedEntriesCount(). This way we could break the loop and not check all folders (see @TODO below)
// or at least skip redundant cache sync of the same folder
$allServerEntries = $this->getServerEntries($folder->serverId, $folder->lastfiltertype);
$addedEntries = array_diff($allServerEntries, $allClientEntries);
$deletedEntries = array_diff($allClientEntries, $allServerEntries);
// @TODO: Count is needed only for GetItemEstimate command
// in Ping command we need only information that anything is changed/added/deleted
// so in case when count($addedEntries) + count($deletedEntries) != 0 we don't need
// to count changed entries here. We could also revert the order in such case
// and execute getChangedEntriesCount() before
$changedEntries = $this->getChangedEntriesCount($folder->serverId, $syncState->lastsync);
return count($addedEntries) + count($deletedEntries) + $changedEntries;
}
+ public function hasChanges(Syncroton_Backend_IContent $contentBackend, Syncroton_Model_IFolder $folder, Syncroton_Model_ISyncState $syncState)
+ {
+ return !!$this->getCountOfChanges($contentBackend, $folder, $syncState);
+ }
+
+
/**
* Fetches the entry from the backend
*/
protected function getObject($folderid, $entryid, &$folder = null)
{
if ($folderid instanceof Syncroton_Model_IFolder) {
$folderid = $folderid->serverId;
}
if ($folderid == $this->defaultRootFolder) {
$folders = $this->backend->folders_list($this->device->deviceid, $this->modelName);
if (!is_array($folders)) {
return null;
}
$folders = array_keys($folders);
}
else {
$folders = array($folderid);
}
foreach ($folders as $folderid) {
$foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid);
if ($foldername === null) {
continue;
}
$folder = $this->getFolderObject($foldername);
if ($folder && ($object = $folder->get_object($entryid))) {
$object['_folderid'] = $folderid;
return $object;
}
}
}
/**
* Saves the entry on the backend
*/
protected function createObject($folderid, $data)
{
if ($folderid == $this->defaultRootFolder) {
$default = $this->getDefaultFolder();
if (!is_array($default)) {
return null;
}
$folderid = isset($default['realid']) ? $default['realid'] : $default['serverId'];
}
$foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid);
$folder = $this->getFolderObject($foldername);
if ($folder && $folder->save($data)) {
return $data;
}
}
/**
* Updates the entry on the backend
*/
protected function updateObject($folderid, $entryid, $data)
{
$object = $this->getObject($folderid, $entryid);
if ($object) {
$folder = $this->getFolderObject($object['_mailbox']);
if ($folder && $folder->save($data)) {
return $data;
}
}
}
/**
* Removes the entry from the backend
*/
protected function deleteObject($folderid, $entryid)
{
$object = $this->getObject($folderid, $entryid);
if ($object) {
$folder = $this->getFolderObject($object['_mailbox']);
return $folder && $folder->delete($entryid);
}
}
/**
* Returns Folder object (uses internal cache)
*
* @param string $name Folder name (UTF7-IMAP)
*
* @return kolab_storage_folder Folder object
*/
protected function getFolderObject($name)
{
if ($name === null) {
return null;
}
if (!isset($this->folders[$name])) {
$this->folders[$name] = kolab_storage::get_folder($name);
}
return $this->folders[$name];
}
/**
* Returns ActiveSync settings of specified folder
*
* @param string $name Folder name (UTF7-IMAP)
*
* @return array Folder settings
*/
protected function getFolderConfig($name)
{
$metadata = $this->backend->folder_meta();
if (!is_array($metadata)) {
return array();
}
$deviceid = $this->device->deviceid;
$config = $metadata[$name]['FOLDER'][$deviceid];
return array(
'ALARMS' => $config['S'] == 2,
);
}
/**
* Convert contact from xml to kolab format
*
* @param Syncroton_Model_IEntry $data Contact data
* @param string $folderId Folder identifier
* @param array $entry Old Contact data for merge
*
* @return array
*/
abstract function toKolab(Syncroton_Model_IEntry $data, $folderId, $entry = null);
/**
* Extracts data from kolab data array
*/
protected function getKolabDataItem($data, $name)
{
$name_items = explode('.', $name);
$count = count($name_items);
// multi-level array (e.g. address, phone)
if ($count == 3) {
$name = $name_items[0];
$type = $name_items[1];
$key_name = $name_items[2];
if (!empty($data[$name]) && is_array($data[$name])) {
foreach ($data[$name] as $element) {
if ($element['type'] == $type) {
return $element[$key_name];
}
}
}
return null;
}
/*
// hash array e.g. organizer
else if ($count == 2) {
$name = $name_items[0];
$type = $name_items[1];
$key_name = $name_items[2];
if (!empty($data[$name]) && is_array($data[$name])) {
foreach ($data[$name] as $element) {
if ($element['type'] == $type) {
return $element[$key_name];
}
}
}
return null;
}
*/
$name_items = explode(':', $name);
$name = $name_items[0];
if (empty($data[$name])) {
return null;
}
// simple array (e.g. email)
if (count($name_items) == 2) {
return $data[$name][$name_items[1]];
}
return $data[$name];
}
/**
* Saves data in kolab data array
*/
protected function setKolabDataItem(&$data, $name, $value)
{
if (empty($value)) {
return $this->unsetKolabDataItem($data, $name);
}
$name_items = explode('.', $name);
// multi-level array (e.g. address, phone)
if (count($name_items) == 3) {
$name = $name_items[0];
$type = $name_items[1];
$key_name = $name_items[2];
if (!isset($data[$name])) {
$data[$name] = array();
}
foreach ($data[$name] as $idx => $element) {
if ($element['type'] == $type) {
$found = $idx;
break;
}
}
if (!isset($found)) {
$data[$name] = array_values($data[$name]);
$found = count($data[$name]);
$data[$name][$found] = array('type' => $type);
}
$data[$name][$found][$key_name] = $value;
return;
}
$name_items = explode(':', $name);
$name = $name_items[0];
// simple array (e.g. email)
if (count($name_items) == 2) {
$data[$name][$name_items[1]] = $value;
return;
}
$data[$name] = $value;
}
/**
* Unsets data item in kolab data array
*/
protected function unsetKolabDataItem(&$data, $name)
{
$name_items = explode('.', $name);
// multi-level array (e.g. address, phone)
if (count($name_items) == 3) {
$name = $name_items[0];
$type = $name_items[1];
$key_name = $name_items[2];
if (!isset($data[$name])) {
return;
}
foreach ($data[$name] as $idx => $element) {
if ($element['type'] == $type) {
$found = $idx;
break;
}
}
if (!isset($found)) {
return;
}
unset($data[$name][$found][$key_name]);
// if there's only one element and it's 'type', remove it
if (count($data[$name][$found]) == 1 && isset($data[$name][$found]['type'])) {
unset($data[$name][$found]['type']);
}
if (empty($data[$name][$found])) {
unset($data[$name][$found]);
}
if (empty($data[$name])) {
unset($data[$name]);
}
return;
}
$name_items = explode(':', $name);
$name = $name_items[0];
// simple array (e.g. email)
if (count($name_items) == 2) {
unset($data[$name][$name_items[1]]);
if (empty($data[$name])) {
unset($data[$name]);
}
return;
}
unset($data[$name]);
}
/**
* Setter for Body attribute according to client version
*
* @param string $value Body
* @param array $param Body parameters
*
* @reurn Syncroton_Model_EmailBody Body element
*/
protected function setBody($value, $params = array())
{
if (empty($value) && empty($params)) {
return;
}
// Old protocol version doesn't support AirSyncBase:Body, it's eg. WindowsCE
if ($this->asversion < 12) {
return;
}
if (!empty($value)) {
$params['data'] = $value;
}
if (!isset($params['type'])) {
$params['type'] = Syncroton_Model_EmailBody::TYPE_PLAINTEXT;
}
return new Syncroton_Model_EmailBody($params);
}
/**
* Getter for Body attribute value according to client version
*
* @param mixed $body Body element
* @param int $type Result data type (to which the body will be converted, if specified).
* One of Syncroton_Model_EmailBody constants.
*
* @return string Body value
*/
protected function getBody($body, $type = null)
{
if ($body && $body->data) {
$data = $body->data;
}
// Convert to specified type
if ($data && $type && $body->type != $type) {
$converter = new kolab_sync_body_converter($data, $body->type);
$data = $converter->convert($type);
}
return $data;
}
/**
* Converts PHP DateTime, date (YYYY-MM-DD) or unixtimestamp into PHP DateTime in UTC
*
* @param DateTime|int|string $date Unix timestamp, date (YYYY-MM-DD) or PHP DateTime object
*
* @return DateTime Datetime object
*/
protected static function date_from_kolab($date)
{
if (!empty($date)) {
if (is_numeric($date)) {
$date = new DateTime('@' . $date);
}
else if (is_string($date)) {
$date = new DateTime($date, new DateTimeZone('UTC'));
}
else if ($date instanceof DateTime) {
$date = clone $date;
$tz = $date->getTimezone();
$tz_name = $tz->getName();
// convert to UTC if needed
if ($tz_name != 'UTC') {
$date->setTimezone(new DateTimeZone('UTC'));
}
}
else {
return null; // invalid input
}
return $date;
}
}
/**
* Convert Kolab event/task recurrence into ActiveSync
*/
protected function recurrence_from_kolab($data, $type = 'Event')
{
if (empty($data['recurrence'])) {
return null;
}
$recurrence = array();
$r = $data['recurrence'];
// required fields
switch($r['FREQ']) {
case 'DAILY':
$recurrence['type'] = self::RECUR_TYPE_DAILY;
break;
case 'WEEKLY':
$recurrence['type'] = self::RECUR_TYPE_WEEKLY;
$recurrence['dayOfWeek'] = $this->day2bitmask($r['BYDAY']);
break;
case 'MONTHLY':
if (!empty($r['BYMONTHDAY'])) {
// @TODO: ActiveSync doesn't support multi-valued month days,
// should we replicate the recurrence element for each day of month?
$month_day = array_shift(explode(',', $r['BYMONTHDAY']));
$recurrence['type'] = self::RECUR_TYPE_MONTHLY;
$recurrence['dayOfMonth'] = $month_day;
}
else {
$week = (int) substr($r['BYDAY'], 0, -2);
$week = ($week == -1) ? 5 : $week;
$day = substr($r['BYDAY'], -2);
$recurrence['type'] = self::RECUR_TYPE_MONTHLY_DAYN;
$recurrence['weekOfMonth'] = $week;
$recurrence['dayOfWeek'] = $this->day2bitmask($day);
}
break;
case 'YEARLY':
// @TODO: ActiveSync doesn't support multi-valued months,
// should we replicate the recurrence element for each month?
$month = array_shift(explode(',', $r['BYMONTH']));
if (!empty($r['BYDAY'])) {
$week = (int) substr($r['BYDAY'], 0, -2);
$week = ($week == -1) ? 5 : $week;
$day = substr($r['BYDAY'], -2);
$recurrence['type'] = self::RECUR_TYPE_YEARLY_DAYN;
$recurrence['weekOfMonth'] = $week;
$recurrence['dayOfWeek'] = $this->day2bitmask($day);
$recurrence['monthOfYear'] = $month;
}
else if (!empty($r['BYMONTHDAY'])) {
// @TODO: ActiveSync doesn't support multi-valued month days,
// should we replicate the recurrence element for each day of month?
$month_day = array_shift(explode(',', $r['BYMONTHDAY']));
$recurrence['type'] = self::RECUR_TYPE_YEARLY;
$recurrence['dayOfMonth'] = $month_day;
$recurrence['monthOfYear'] = $month;
}
else {
$recurrence['type'] = self::RECUR_TYPE_YEARLY;
$recurrence['monthOfYear'] = $month;
}
break;
}
// required field
$recurrence['interval'] = $r['INTERVAL'] ? $r['INTERVAL'] : 1;
if (!empty($r['UNTIL'])) {
$recurrence['until'] = $this->date_from_kolab($r['UNTIL']);
}
else if (!empty($r['COUNT'])) {
$recurrence['occurrences'] = $r['COUNT'];
}
$class = 'Syncroton_Model_' . $type . 'Recurrence';
return new $class($recurrence);
}
/**
* Convert ActiveSync event/task recurrence into Kolab
*/
protected function recurrence_to_kolab($recurrence, $timezone = null)
{
if (!($recurrence instanceof Syncroton_Model_EventRecurrence) || !isset($recurrence->type)) {
return null;
}
$type = $recurrence->type;
switch ($type) {
case self::RECUR_TYPE_DAILY:
break;
case self::RECUR_TYPE_WEEKLY:
$rrule['BYDAY'] = $this->bitmask2day($recurrence->dayOfWeek);
break;
case self::RECUR_TYPE_MONTHLY:
$rrule['BYMONTHDAY'] = $recurrence->dayOfMonth;
break;
case self::RECUR_TYPE_MONTHLY_DAYN:
$week = $recurrence->weekOfMonth;
$day = $recurrence->dayOfWeek;
$byDay = $week == 5 ? -1 : $week;
$byDay .= $this->bitmask2day($day);
$rrule['BYDAY'] = $byDay;
break;
case self::RECUR_TYPE_YEARLY:
$rrule['BYMONTH'] = $recurrence->monthOfYear;
$rrule['BYMONTHDAY'] = $recurrence->dayOfMonth;
break;
case self::RECUR_TYPE_YEARLY_DAYN:
$rrule['BYMONTH'] = $recurrence->monthOfYear;
$week = $recurrence->weekOfMonth;
$day = $recurrence->dayOfWeek;
$byDay = $week == 5 ? -1 : $week;
$byDay .= $this->bitmask2day($day);
$rrule['BYDAY'] = $byDay;
break;
}
$rrule['FREQ'] = $this->recurTypeMap[$type];
$rrule['INTERVAL'] = isset($recurrence->interval) ? $recurrence->interval : 1;
if (isset($recurrence->until)) {
if ($timezone) {
$recurrence->until->setTimezone($timezone);
}
$rrule['UNTIL'] = $recurrence->until;
}
else if (!empty($recurrence->occurrences)) {
$rrule['COUNT'] = $recurrence->occurrences;
}
return $rrule;
}
/**
* Convert Kolab event recurrence exceptions into ActiveSync
*/
protected function exceptions_from_kolab($data, $start_date)
{
if (empty($data['recurrence'])) {
return null;
}
$rex = (array) $data['recurrence']['EXDATE'];
$exceptions = array();
foreach ($rex as $ex_date) {
if (!($ex_date instanceof DateTime)) {
continue;
}
$start = clone $ex_date;
$end = clone $ex_date;
$start->setTime(0, 0, 0);
$end->setTime(0, 0, 0);
$end->modify('+1 day');
$ex = array(
'exceptionStartTime' => $start_date,
'startTime' => self::date_from_kolab($start),
'endTime' => self::date_from_kolab($end),
'deleted' => 1,
);
if ($data['allday']) {
$ex['allDayEvent'] = 1;
}
$exceptions[] = new Syncroton_Model_EventException($ex);
}
return $exceptions;
}
/**
* Convert ActiveSync event recurrence exceptions into Kolab
*/
protected function exceptions_to_kolab($exceptions, $timezone = null)
{
$exdates = array();
// handle exceptions from recurrence
if (!empty($exceptions)) {
foreach ($exceptions as $exception) {
if ($exception->deleted && $exception->startTime) {
$date = clone $exception->startTime;
$date->setTimezone($timezone);
$date->setTime(0, 0, 0);
$exdates[] = $date;
}
else {
// @TODO: handle modification exceptions (that doesn't delete) ?
}
}
}
return !empty($exdates) ? $exdates : null;
}
/**
* Converts string of days (TU,TH) to bitmask used by ActiveSync
*
* @param string $days
*
* @return int
*/
protected function day2bitmask($days)
{
$days = explode(',', $days);
$result = 0;
foreach ($days as $day) {
$result = $result + $this->recurDayMap[$day];
}
return $result;
}
/**
* Convert bitmask used by ActiveSync to string of days (TU,TH)
*
* @param int $days
*
* @return string
*/
protected function bitmask2day($days)
{
$days_arr = array();
for ($bitmask = 1; $bitmask <= self::RECUR_DOW_SATURDAY; $bitmask = $bitmask << 1) {
$dayMatch = $days & $bitmask;
if ($dayMatch === $bitmask) {
$days_arr[] = array_search($bitmask, $this->recurDayMap);
}
}
$result = implode(',', $days_arr);
return $result;
}
}

File Metadata

Mime Type
text/x-diff
Expires
Thu, Mar 19, 8:46 AM (21 h, 42 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
457665
Default Alt Text
(226 KB)

Event Timeline