Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2571597
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
226 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
Mime Type
text/x-diff
Expires
Thu, Mar 19, 8:46 AM (18 h, 30 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
457665
Default Alt Text
(226 KB)
Attached To
Mode
R4 syncroton
Attached
Detach File
Event Timeline
Log In to Comment