2 commits - docs/SQL lib/ext lib/kolab_sync_backend.php lib/kolab_sync_data_email.php lib/kolab_sync_data.php
Aleksander Machniak
machniak at kolabsys.com
Sun Apr 7 14:08:23 CEST 2013
docs/SQL/mysql.initial.sql | 45 --
docs/SQL/mysql/2013040700.sql | 9
lib/ext/Syncroton/Backend/ABackend.php | 39 ++
lib/ext/Syncroton/Backend/Policy.php | 46 ++
lib/ext/Syncroton/Command/FolderSync.php | 31 +
lib/ext/Syncroton/Command/GetItemEstimate.php | 2
lib/ext/Syncroton/Command/ItemOperations.php | 167 ++++++---
lib/ext/Syncroton/Command/Ping.php | 7
lib/ext/Syncroton/Command/Provision.php | 10
lib/ext/Syncroton/Command/Settings.php | 6
lib/ext/Syncroton/Command/Sync.php | 322 +++++++++++++-----
lib/ext/Syncroton/Command/Wbxml.php | 2
lib/ext/Syncroton/Data/AData.php | 133 ++++++-
lib/ext/Syncroton/Data/IData.php | 23 +
lib/ext/Syncroton/Exception/MemoryExhausted.php | 20 +
lib/ext/Syncroton/Model/AXMLEntry.php | 31 +
lib/ext/Syncroton/Model/Content.php | 13
lib/ext/Syncroton/Model/Event.php | 90 ++---
lib/ext/Syncroton/Model/EventException.php | 6
lib/ext/Syncroton/Model/IContent.php | 1
lib/ext/Syncroton/Model/ISyncState.php | 1
lib/ext/Syncroton/Model/SyncCollection.php | 5
lib/ext/Syncroton/Model/SyncState.php | 13
lib/ext/Syncroton/Server.php | 20 -
lib/ext/Syncroton/Wbxml/Dtd/ActiveSync/CodePage14.php | 31 +
lib/ext/Syncroton/Wbxml/Dtd/ActiveSync/CodePage9.php | 6
lib/kolab_sync_backend.php | 15
lib/kolab_sync_data.php | 145 +++++++-
lib/kolab_sync_data_email.php | 6
29 files changed, 937 insertions(+), 308 deletions(-)
New commits:
commit f045cc4a793d1b08fb849f9f8923ad03bddd394b
Author: Aleksander Machniak <alec at alec.pl>
Date: Sun Apr 7 14:01:39 2013 +0200
Fix redundant "validate" device on device registration (Bug #1109)
diff --git a/lib/kolab_sync_backend.php b/lib/kolab_sync_backend.php
index 0498759..99a1257 100644
--- a/lib/kolab_sync_backend.php
+++ b/lib/kolab_sync_backend.php
@@ -371,6 +371,12 @@ class kolab_sync_backend
// Fill local cache
$this->devices_list();
+ // Some devices create dummy devices with name "validate" (#1109)
+ // This device entry is used in two initial requests, but later
+ // the device registers a real name. We can remove this dummy entry
+ // on new device creation
+ $this->device_delete('validate');
+
// Old Kolab_ZPush device parameters
// MODE: -1 | 0 | 1 (not set | flatmode | foldermode)
// TYPE: device type string
@@ -423,7 +429,14 @@ class kolab_sync_backend
return $result;
}
-
+ /**
+ * Device update.
+ *
+ * @param array $device Device data
+ * @param string $id Device ID
+ *
+ * @return bool True on success, False on failure
+ */
public function device_update($device, $id)
{
$devices_list = $this->devices_list();
commit ac10637a95996bf6dbd717218a4b01b8a84f9e89
Author: Aleksander Machniak <alec at alec.pl>
Date: Sun Apr 7 13:28:34 2013 +0200
Update Syncroton
diff --git a/docs/SQL/mysql.initial.sql b/docs/SQL/mysql.initial.sql
index b918f12..4f6ebcf 100644
--- a/docs/SQL/mysql.initial.sql
+++ b/docs/SQL/mysql.initial.sql
@@ -3,50 +3,9 @@ CREATE TABLE IF NOT EXISTS `syncroton_policy` (
`name` varchar(255) NOT NULL,
`description` varchar(255) DEFAULT NULL,
`policy_key` varchar(64) NOT NULL,
- `allow_bluetooth` int(11) DEFAULT NULL,
- `allow_browser` int(11) DEFAULT NULL,
- `allow_camera` int(11) DEFAULT NULL,
- `allow_consumer_email` int(11) DEFAULT NULL,
- `allow_desktop_sync` int(11) DEFAULT NULL,
- `allow_h_t_m_l_email` int(11) DEFAULT NULL,
- `allow_internet_sharing` int(11) DEFAULT NULL,
- `allow_ir_d_a` int(11) DEFAULT NULL,
- `allow_p_o_p_i_m_a_p_email` int(11) DEFAULT NULL,
- `allow_remote_desktop` int(11) DEFAULT NULL,
- `allow_simple_device_password` int(11) DEFAULT NULL,
- `allow_s_m_i_m_e_encryption_algorithm_negotiation` int(11) DEFAULT NULL,
- `allow_s_m_i_m_e_soft_certs` int(11) DEFAULT NULL,
- `allow_storage_card` int(11) DEFAULT NULL,
- `allow_text_messaging` int(11) DEFAULT NULL,
- `allow_unsigned_applications` int(11) DEFAULT NULL,
- `allow_unsigned_installation_packages` int(11) DEFAULT NULL,
- `allow_wifi` int(11) DEFAULT NULL,
- `alphanumeric_device_password_required` int(11) DEFAULT NULL,
- `approved_application_list` varchar(255) DEFAULT NULL,
- `attachments_enabled` int(11) DEFAULT NULL,
- `device_password_enabled` int(11) DEFAULT NULL,
- `device_password_expiration` int(11) DEFAULT NULL,
- `device_password_history` int(11) DEFAULT NULL,
- `max_attachment_size` int(11) DEFAULT NULL,
- `max_calendar_age_filter` int(11) DEFAULT NULL,
- `max_device_password_failed_attempts` int(11) DEFAULT NULL,
- `max_email_age_filter` int(11) DEFAULT NULL,
- `max_email_body_truncation_size` int(11) DEFAULT NULL,
- `max_email_h_t_m_l_body_truncation_size` int(11) DEFAULT NULL,
- `max_inactivity_time_device_lock` int(11) DEFAULT NULL,
- `min_device_password_complex_characters` int(11) DEFAULT NULL,
- `min_device_password_length` int(11) DEFAULT NULL,
- `password_recovery_enabled` int(11) DEFAULT NULL,
- `require_device_encryption` int(11) DEFAULT NULL,
- `require_encrypted_s_m_i_m_e_messages` int(11) DEFAULT NULL,
- `require_encryption_s_m_i_m_e_algorithm` int(11) DEFAULT NULL,
- `require_manual_sync_when_roaming` int(11) DEFAULT NULL,
- `require_signed_s_m_i_m_e_algorithm` int(11) DEFAULT NULL,
- `require_signed_s_m_i_m_e_messages` int(11) DEFAULT NULL,
- `require_storage_card_encryption` int(11) DEFAULT NULL,
- `unapproved_in_r_o_m_application_list` varchar(255) DEFAULT NULL,
+ `json_policy` blob NOT NULL,
PRIMARY KEY (`id`)
-) ENGINE=InnoDB;
+);
CREATE TABLE IF NOT EXISTS `syncroton_device` (
`id` varchar(40) NOT NULL,
diff --git a/docs/SQL/mysql/2013040700.sql b/docs/SQL/mysql/2013040700.sql
new file mode 100644
index 0000000..501d3d9
--- /dev/null
+++ b/docs/SQL/mysql/2013040700.sql
@@ -0,0 +1,9 @@
+DROP TABLE IF EXISTS `syncroton_policy`;
+CREATE TABLE IF NOT EXISTS `syncroton_policy` (
+ `id` varchar(40) NOT NULL,
+ `name` varchar(255) NOT NULL,
+ `description` varchar(255) DEFAULT NULL,
+ `policy_key` varchar(64) NOT NULL,
+ `json_policy` blob NOT NULL,
+ PRIMARY KEY (`id`)
+);
diff --git a/lib/ext/Syncroton/Backend/ABackend.php b/lib/ext/Syncroton/Backend/ABackend.php
index d345d6d..6c50e1c 100644
--- a/lib/ext/Syncroton/Backend/ABackend.php
+++ b/lib/ext/Syncroton/Backend/ABackend.php
@@ -13,7 +13,6 @@
*
* @package Backend
*/
-
abstract class Syncroton_Backend_ABackend implements Syncroton_Backend_IBackend
{
/**
@@ -31,6 +30,12 @@ abstract class Syncroton_Backend_ABackend implements Syncroton_Backend_IBackend
protected $_modelInterfaceName;
+ /**
+ * the constructor
+ *
+ * @param Zend_Db_Adapter_Abstract $_db
+ * @param string $_tablePrefix
+ */
public function __construct(Zend_Db_Adapter_Abstract $_db, $_tablePrefix = 'Syncroton_')
{
$this->_db = $_db;
@@ -58,6 +63,12 @@ abstract class Syncroton_Backend_ABackend implements Syncroton_Backend_IBackend
return $this->get($data['id']);
}
+ /**
+ * convert iteratable object to array
+ *
+ * @param unknown $model
+ * @return array
+ */
protected function _convertModelToArray($model)
{
$data = array();
@@ -99,6 +110,12 @@ abstract class Syncroton_Backend_ABackend implements Syncroton_Backend_IBackend
return $this->_getObject($data);
}
+ /**
+ * convert array to object
+ *
+ * @param array $data
+ * @return object
+ */
protected function _getObject($data)
{
foreach ($data as $key => $value) {
@@ -114,6 +131,10 @@ abstract class Syncroton_Backend_ABackend implements Syncroton_Backend_IBackend
return new $this->_modelClassName($data);
}
+ /**
+ * (non-PHPdoc)
+ * @see Syncroton_Backend_IBackend::delete()
+ */
public function delete($id)
{
$id = $id instanceof $this->_modelInterfaceName ? $id->id : $id;
@@ -123,6 +144,10 @@ abstract class Syncroton_Backend_ABackend implements Syncroton_Backend_IBackend
return (bool) $result;
}
+ /**
+ * (non-PHPdoc)
+ * @see Syncroton_Backend_IBackend::update()
+ */
public function update($model)
{
if (! $model instanceof $this->_modelInterfaceName) {
@@ -138,6 +163,11 @@ abstract class Syncroton_Backend_ABackend implements Syncroton_Backend_IBackend
return $this->get($model->id);
}
+ /**
+ * convert from camelCase to camel_case
+ * @param string $string
+ * @return string
+ */
protected function _fromCamelCase($string)
{
$string = lcfirst($string);
@@ -145,6 +175,13 @@ abstract class Syncroton_Backend_ABackend implements Syncroton_Backend_IBackend
return preg_replace_callback('/([A-Z])/', function ($string) {return '_' . strtolower($string[0]);}, $string);
}
+ /**
+ * convert from camel_case to camelCase
+ *
+ * @param string $string
+ * @param bool $ucFirst
+ * @return string
+ */
protected function _toCamelCase($string, $ucFirst = true)
{
if ($ucFirst === true) {
diff --git a/lib/ext/Syncroton/Backend/Policy.php b/lib/ext/Syncroton/Backend/Policy.php
index 5163359..bb62e6d 100644
--- a/lib/ext/Syncroton/Backend/Policy.php
+++ b/lib/ext/Syncroton/Backend/Policy.php
@@ -21,4 +21,50 @@ class Syncroton_Backend_Policy extends Syncroton_Backend_ABackend #implements Sy
protected $_modelClassName = 'Syncroton_Model_Policy';
protected $_modelInterfaceName = 'Syncroton_Model_IPolicy';
+
+ /**
+ * convert iteratable object to array
+ *
+ * @param unknown $model
+ * @return array
+ */
+ protected function _convertModelToArray($model)
+ {
+ $policyValues = $model->getProperties('Provision');
+
+ $policy = array();
+
+ foreach ($policyValues as $policyName) {
+ if ($model->$policyName !== NULL) {
+ $policy[$policyName] = $model->$policyName;
+ }
+
+ unset($model->$policyName);
+ }
+
+ $data = parent::_convertModelToArray($model);
+
+ $data['json_policy'] = Zend_Json::encode($policy);
+
+ return $data;
+ }
+
+ /**
+ * convert array to object
+ *
+ * @param array $data
+ * @return object
+ */
+ protected function _getObject($data)
+ {
+ $policy = Zend_Json::decode($data['json_policy']);
+
+ foreach ($policy as $policyKey => $policyValue) {
+ $data[$policyKey] = $policyValue;
+ }
+
+ unset($data['json_policy']);
+
+ return parent::_getObject($data);
+ }
}
diff --git a/lib/ext/Syncroton/Command/FolderSync.php b/lib/ext/Syncroton/Command/FolderSync.php
index 84e761e..87d9d3f 100644
--- a/lib/ext/Syncroton/Command/FolderSync.php
+++ b/lib/ext/Syncroton/Command/FolderSync.php
@@ -121,7 +121,8 @@ class Syncroton_Command_FolderSync extends Syncroton_Command_Wbxml
$this->_headers = array_merge($this->_headers, $optionsCommand->getHeaders());
}
- $adds = array();
+ $adds = array();
+ $updates = array();
$deletes = array();
foreach($this->_classes as $class) {
@@ -138,6 +139,13 @@ class Syncroton_Command_FolderSync extends Syncroton_Command_Wbxml
// retrieve all folders available in data backend
$serverFolders = $dataController->getAllFolders();
+ if ($this->_syncState->counter > 0) {
+ // retrieve all folders changed since last sync
+ $changedFolders = $dataController->getChangedFolders($this->_syncState->lastsync, $this->_syncTimeStamp);
+ } else {
+ $changedFolders = array();
+ }
+
// retrieve all folders sent to client
$clientFolders = $this->_folderBackend->getFolderState($this->_device, $class);
@@ -186,6 +194,17 @@ class Syncroton_Command_FolderSync extends Syncroton_Command_Wbxml
$adds[] = $add;
}
+ // calculate changed entries
+ foreach ($changedFolders as $changedFolder) {
+ $change = $clientFolders[$changedFolder->serverId];
+
+ $change->displayName = $changedFolder->displayName;
+ $change->parentId = $changedFolder->parentId;
+ $change->type = $changedFolder->type;
+
+ $updates[] = $change;
+ }
+
// calculate deleted entries
$serverDiff = array_diff($clientFoldersIds, $serverFoldersIds);
foreach ($serverDiff as $serverFolderId) {
@@ -195,7 +214,7 @@ class Syncroton_Command_FolderSync extends Syncroton_Command_Wbxml
$folderSync->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Status', self::STATUS_SUCCESS));
- $count = count($adds) + /*count($changes) + */count($deletes);
+ $count = count($adds) + count($updates) + count($deletes);
if($count > 0) {
$this->_syncState->counter++;
$this->_syncState->lastsync = $this->_syncTimeStamp;
@@ -218,6 +237,14 @@ class Syncroton_Command_FolderSync extends Syncroton_Command_Wbxml
}
}
+ foreach($updates as $folder) {
+ $update = $changes->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Update'));
+
+ $folder->appendXML($update, $this->_device);
+
+ $this->_folderBackend->update($folder);
+ }
+
foreach($deletes as $folder) {
$delete = $changes->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'Delete'));
$delete->appendChild($this->_outputDom->createElementNS('uri:FolderHierarchy', 'ServerId', $folder->serverId));
diff --git a/lib/ext/Syncroton/Command/GetItemEstimate.php b/lib/ext/Syncroton/Command/GetItemEstimate.php
index 5eb0f4c..c1ff330 100644
--- a/lib/ext/Syncroton/Command/GetItemEstimate.php
+++ b/lib/ext/Syncroton/Command/GetItemEstimate.php
@@ -137,7 +137,7 @@ class Syncroton_Command_GetItemEstimate extends Syncroton_Command_Wbxml
}
// folderState can be NULL in case of not existing folder
- if (isset($collectionData['folder'])) {
+ if (isset($collectionData['folder']) && $collectionData['folder']->isDirty()) {
$this->_folderBackend->update($collectionData['folder']);
}
}
diff --git a/lib/ext/Syncroton/Command/ItemOperations.php b/lib/ext/Syncroton/Command/ItemOperations.php
index c3ee287..93aa722 100644
--- a/lib/ext/Syncroton/Command/ItemOperations.php
+++ b/lib/ext/Syncroton/Command/ItemOperations.php
@@ -34,6 +34,13 @@ class Syncroton_Command_ItemOperations extends Syncroton_Command_Wbxml
protected $_fetches = array();
/**
+ * list of folder to empty
+ *
+ * @var array
+ */
+ protected $_emptyFolderContents = array();
+
+ /**
* parse MoveItems request
*
*/
@@ -43,59 +50,13 @@ class Syncroton_Command_ItemOperations extends Syncroton_Command_Wbxml
if (isset($xml->Fetch)) {
foreach ($xml->Fetch as $fetch) {
- $fetchArray = array(
- 'store' => (string)$fetch->Store,
- 'options' => array()
- );
-
- // try to fetch element from namespace AirSync
- $airSync = $fetch->children('uri:AirSync');
-
- if (isset($airSync->CollectionId)) {
- $fetchArray['collectionId'] = (string)$airSync->CollectionId;
- $fetchArray['serverId'] = (string)$airSync->ServerId;
- }
-
- // try to fetch element from namespace Search
- $search = $fetch->children('uri:Search');
-
- if (isset($search->LongId)) {
- $fetchArray['longId'] = (string)$search->LongId;
- }
-
- // try to fetch element from namespace AirSyncBase
- $airSyncBase = $fetch->children('uri:AirSyncBase');
-
- if (isset($airSyncBase->FileReference)) {
- $fetchArray['fileReference'] = (string)$airSyncBase->FileReference;
- }
-
- if (isset($fetch->Options)) {
- // try to fetch element from namespace AirSyncBase
- $airSyncBase = $fetch->Options->children('uri:AirSyncBase');
-
- if (isset($airSyncBase->BodyPreference)) {
- foreach ($airSyncBase->BodyPreference as $bodyPreference) {
- $type = (int) $bodyPreference->Type;
- $fetchArray['options']['bodyPreferences'][$type] = array(
- 'type' => $type
- );
-
- // optional
- if (isset($bodyPreference->TruncationSize)) {
- $fetchArray['options']['bodyPreferences'][$type]['truncationSize'] = (int) $bodyPreference->TruncationSize;
- }
-
- // optional
- if (isset($bodyPreference->AllOrNone)) {
- $fetchArray['options']['bodyPreferences'][$type]['allOrNone'] = (int) $bodyPreference->AllOrNone;
- }
- }
- }
-
-
- }
- $this->_fetches[] = $fetchArray;
+ $this->_fetches[] = $this->_handleFetch($fetch);
+ }
+ }
+
+ if (isset($xml->EmptyFolderContents)) {
+ foreach ($xml->EmptyFolderContents as $emptyFolderContents) {
+ $this->_emptyFolderContents[] = $this->_handleEmptyFolderContents($emptyFolderContents);
}
}
@@ -187,6 +148,106 @@ class Syncroton_Command_ItemOperations extends Syncroton_Command_Wbxml
}
}
+ foreach ($this->_emptyFolderContents as $emptyFolderContents) {
+
+ try {
+ $folder = $this->_folderBackend->getFolder($this->_device, $emptyFolderContents['collectionId']);
+
+ $dataController = Syncroton_Data_Factory::factory($folder->class, $this->_device, $this->_syncTimeStamp);
+ $dataController->emptyFolderContents($emptyFolderContents['collectionId'], $emptyFolderContents['options']);
+
+ $status = Syncroton_Command_ItemOperations::STATUS_SUCCESS;
+ }
+ catch (Syncroton_Exception_Status_ItemOperations $e) {
+ $status = $e->getCode();
+ }
+ catch (Exception $e) {
+ $status = Syncroton_Exception_Status_ItemOperations::ITEM_SERVER_ERROR;
+ }
+
+ $emptyFolderContentsTag = $this->_outputDom->createElementNS('uri:ItemOperations', 'EmptyFolderContents');
+
+ $emptyFolderContentsTag->appendChild($this->_outputDom->createElementNS('uri:ItemOperations', 'Status', $status));
+ $emptyFolderContentsTag->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'CollectionId', $emptyFolderContents['collectionId']));
+
+ $response->appendChild($emptyFolderContentsTag);
+ }
+
return $this->_outputDom;
}
+
+ protected function _handleFetch(SimpleXMLElement $fetch)
+ {
+ $fetchArray = array(
+ 'store' => (string)$fetch->Store,
+ 'options' => array()
+ );
+
+ // try to fetch element from namespace AirSync
+ $airSync = $fetch->children('uri:AirSync');
+
+ if (isset($airSync->CollectionId)) {
+ $fetchArray['collectionId'] = (string)$airSync->CollectionId;
+ $fetchArray['serverId'] = (string)$airSync->ServerId;
+ }
+
+ // try to fetch element from namespace Search
+ $search = $fetch->children('uri:Search');
+
+ if (isset($search->LongId)) {
+ $fetchArray['longId'] = (string)$search->LongId;
+ }
+
+ // try to fetch element from namespace AirSyncBase
+ $airSyncBase = $fetch->children('uri:AirSyncBase');
+
+ if (isset($airSyncBase->FileReference)) {
+ $fetchArray['fileReference'] = (string)$airSyncBase->FileReference;
+ }
+
+ if (isset($fetch->Options)) {
+ // try to fetch element from namespace AirSyncBase
+ $airSyncBase = $fetch->Options->children('uri:AirSyncBase');
+
+ if (isset($airSyncBase->BodyPreference)) {
+ foreach ($airSyncBase->BodyPreference as $bodyPreference) {
+ $type = (int) $bodyPreference->Type;
+ $fetchArray['options']['bodyPreferences'][$type] = array(
+ 'type' => $type
+ );
+
+ // optional
+ if (isset($bodyPreference->TruncationSize)) {
+ $fetchArray['options']['bodyPreferences'][$type]['truncationSize'] = (int) $bodyPreference->TruncationSize;
+ }
+
+ // optional
+ if (isset($bodyPreference->AllOrNone)) {
+ $fetchArray['options']['bodyPreferences'][$type]['allOrNone'] = (int) $bodyPreference->AllOrNone;
+ }
+ }
+ }
+ }
+
+ return $fetchArray;
+ }
+
+ protected function _handleEmptyFolderContents(SimpleXMLElement $emptyFolderContent)
+ {
+ $folderArray = array(
+ 'collectiondId' => null,
+ 'options' => array('deleteSubFolders' => FALSE)
+ );
+
+ // try to fetch element from namespace AirSync
+ $airSync = $emptyFolderContent->children('uri:AirSync');
+
+ $folderArray['collectionId'] = (string)$airSync->CollectionId;
+
+ if (isset($emptyFolderContent->Options)) {
+ $folderArray['options']['deleteSubFolders'] = isset($emptyFolderContent->Options->DeleteSubFolders);
+ }
+
+ return $folderArray;
+ }
}
diff --git a/lib/ext/Syncroton/Command/Ping.php b/lib/ext/Syncroton/Command/Ping.php
index 9ac1b35..dc06215 100644
--- a/lib/ext/Syncroton/Command/Ping.php
+++ b/lib/ext/Syncroton/Command/Ping.php
@@ -89,8 +89,13 @@ class Syncroton_Command_Ping extends Syncroton_Command_Wbxml
$intervalEnd = $intervalStart + $lifeTime;
$secondsLeft = $intervalEnd;
+
$folders = unserialize($this->_device->pingfolder);
+ if ($status === self::STATUS_NO_CHANGES_FOUND && (!is_array($folders) || count($folders) == 0)) {
+ $status = self::STATUS_MISSING_PARAMETERS;
+ }
+
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " Folders to monitor($lifeTime / $intervalStart / $intervalEnd / $status): " . print_r($folders, true));
@@ -104,7 +109,7 @@ class Syncroton_Command_Ping extends Syncroton_Command_Wbxml
$now = new DateTime('now', new DateTimeZone('utc'));
- foreach ((array) $folders as $folderId) {
+ foreach ($folders as $folderId) {
try {
$folder = $this->_folderBackend->get($folderId);
$dataController = Syncroton_Data_Factory::factory($folder->class, $this->_device, $this->_syncTimeStamp);
diff --git a/lib/ext/Syncroton/Command/Provision.php b/lib/ext/Syncroton/Command/Provision.php
index a3e7328..3163d5f 100644
--- a/lib/ext/Syncroton/Command/Provision.php
+++ b/lib/ext/Syncroton/Command/Provision.php
@@ -60,7 +60,6 @@ class Syncroton_Command_Provision extends Syncroton_Command_Wbxml
if ($this->_device->remotewipe == self::REMOTEWIPE_REQUESTED && isset($xml->RemoteWipe->Status) && (int)$xml->RemoteWipe->Status == self::STATUS_SUCCESS) {
$this->_device->remotewipe = self::REMOTEWIPE_CONFIRMED;
- $this->_device = $this->_deviceBackend->update($this->_device);
}
// try to fetch element from Settings namespace
@@ -74,9 +73,10 @@ class Syncroton_Command_Provision extends Syncroton_Command_Wbxml
$this->_device->os = $this->_deviceInformation->oS;
$this->_device->oslanguage = $this->_deviceInformation->oSLanguage;
$this->_device->phonenumber = $this->_deviceInformation->phoneNumber;
-
- $this->_device = $this->_deviceBackend->update($this->_device);
-
+ }
+
+ if ($this->_device->isDirty()) {
+ $this->_device = $this->_deviceBackend->update($this->_device);
}
}
@@ -194,6 +194,6 @@ class Syncroton_Command_Provision extends Syncroton_Command_Wbxml
*/
public static function generatePolicyKey()
{
- return sha1(mt_rand(). microtime());
+ return mt_rand(1, 2147483647);
}
}
diff --git a/lib/ext/Syncroton/Command/Settings.php b/lib/ext/Syncroton/Command/Settings.php
index e322355..11a36a3 100644
--- a/lib/ext/Syncroton/Command/Settings.php
+++ b/lib/ext/Syncroton/Command/Settings.php
@@ -54,8 +54,10 @@ class Syncroton_Command_Settings extends Syncroton_Command_Wbxml
$this->_device->os = $this->_deviceInformation->oS;
$this->_device->oslanguage = $this->_deviceInformation->oSLanguage;
$this->_device->phonenumber = $this->_deviceInformation->phoneNumber;
-
- $this->_device = $this->_deviceBackend->update($this->_device);
+
+ if ($this->_device->isDirty()) {
+ $this->_device = $this->_deviceBackend->update($this->_device);
+ }
}
if(isset($xml->UserInformation->Get)) {
diff --git a/lib/ext/Syncroton/Command/Sync.php b/lib/ext/Syncroton/Command/Sync.php
index 3308bed..40ff6df 100644
--- a/lib/ext/Syncroton/Command/Sync.php
+++ b/lib/ext/Syncroton/Command/Sync.php
@@ -113,60 +113,60 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
public function handle()
{
// input xml
- $xml = simplexml_import_dom($this->_requestBody);
+ $requestXML = simplexml_import_dom($this->_mergeSyncRequest($this->_requestBody, $this->_device));
- if (isset($xml->HeartbeatInterval)) {
- $this->_heartbeatInterval = (int)$xml->HeartbeatInterval;
- } elseif (isset($xml->Wait)) {
- $this->_heartbeatInterval = (int)$xml->Wait * 60;
+ if (! isset($requestXML->Collections)) {
+ $this->_outputDom->documentElement->appendChild(
+ $this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_RESEND_FULL_XML)
+ );
+
+ return $this->_outputDom;
}
- $this->_globalWindowSize = isset($xml->WindowSize) ? (int)$xml->WindowSize : 100;
+ if (isset($requestXML->HeartbeatInterval)) {
+ $this->_heartbeatInterval = (int)$requestXML->HeartbeatInterval;
+ } elseif (isset($requestXML->Wait)) {
+ $this->_heartbeatInterval = (int)$requestXML->Wait * 60;
+ }
+
+ $this->_globalWindowSize = isset($requestXML->WindowSize) ? (int)$requestXML->WindowSize : 100;
if ($this->_globalWindowSize > $this->_maxWindowSize) {
$this->_globalWindowSize = $this->_maxWindowSize;
}
+
+ // load options from lastsynccollection
+ $lastSyncCollection = array('options' => array());
+ if (!empty($this->_device->lastsynccollection)) {
+ $lastSyncCollection = Zend_Json::decode($this->_device->lastsynccollection);
+ if (!array_key_exists('options', $lastSyncCollection) || !is_array($lastSyncCollection['options'])) {
+ $lastSyncCollection['options'] = array();
+ }
+ }
$collections = array();
- $isPartialRequest = isset($xml->Partial);
- // try to restore collections from previous request
- if ($isPartialRequest) {
- $decodedCollections = Zend_Json::decode($this->_device->lastsynccollection);
+ foreach ($requestXML->Collections->Collection as $xmlCollection) {
+ $collectionId = (string)$xmlCollection->CollectionId;
- 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);
- }
- }
+ $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();
+ // do we have to reuse the options from the previous request?
+ if (!isset($xmlCollection->Options) && array_key_exists($collectionId, $lastSyncCollection['options'])) {
+ $collections[$collectionId]->options = $lastSyncCollection['options'][$collectionId];
+ if ($this->_logger instanceof Zend_Log)
+ $this->_logger->debug(__METHOD__ . '::' . __LINE__ . " restored options to " . print_r($collections[$collectionId]->options, TRUE));
}
+
+ // store current options for next Sync command request (sticky options)
+ $lastSyncCollection['options'][$collectionId] = $collections[$collectionId]->options;
+ }
- $this->_device->lastsynccollection = Zend_Json::encode($collectionsToSave);
+ $this->_device->lastsynccollection = Zend_Json::encode($lastSyncCollection);
- if ($this->_device->isDirty()) {
- Syncroton_Registry::getDeviceBackend()->update($this->_device);
- }
+ if ($this->_device->isDirty()) {
+ Syncroton_Registry::getDeviceBackend()->update($this->_device);
}
foreach ($collections as $collectionData) {
@@ -412,13 +412,7 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
{
$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'));
+ $collections = $this->_outputDom->createElementNS('uri:AirSync', 'Collections');
$totalChanges = 0;
@@ -433,7 +427,7 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
$now = new DateTime(null, new DateTimeZone('utc'));
foreach($this->_collections as $collectionData) {
- // countinue immediately if folder does not exist
+ // continue immediately if folder does not exist
if (! ($collectionData->folder instanceof Syncroton_Model_IFolder)) {
break 2;
@@ -488,23 +482,20 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
// 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()) {
+ if ( ! $collectionData->syncState instanceof Syncroton_Model_SyncState ||
+ ($now->getTimestamp() - $collectionData->syncState->lastsync->getTimestamp()) < Syncroton_Registry::getQuietTime()) {
continue;
}
$dataController = Syncroton_Data_Factory::factory($collectionData->folder->class , $this->_device, $this->_syncTimeStamp);
- $hasChanges = $dataController->hasChanges($this->_contentStateBackend, $collectionData->folder, $collectionData->syncState);
-
// countinue immediately if there are any changes available
- if ($hasChanges) {
+ if($dataController->hasChanges($this->_contentStateBackend, $collectionData->folder, $collectionData->syncState)) {
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
@@ -574,11 +565,21 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
$serverModifications = $collectionData->syncState->pendingdata;
- } else {
+ } elseif ($dataController->hasChanges($this->_contentStateBackend, $collectionData->folder, $collectionData->syncState)) {
+
+ // update _syncTimeStamp as $dataController->hasChanges might have spent some time
+ $this->_syncTimeStamp = new DateTime(null, new DateTimeZone('utc'));
+
try {
// fetch entries added since last sync
- $allClientEntries = $this->_contentStateBackend->getFolderState($this->_device, $collectionData->folder);
- $allServerEntries = $dataController->getServerEntries($collectionData->collectionId, $collectionData->options['filterType']);
+ $allClientEntries = $this->_contentStateBackend->getFolderState(
+ $this->_device,
+ $collectionData->folder
+ );
+ $allServerEntries = $dataController->getServerEntries(
+ $collectionData->collectionId,
+ $collectionData->options['filterType']
+ );
// add entries
$serverDiff = array_diff($allServerEntries, $allClientEntries);
@@ -601,7 +602,12 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
$serverModifications['deleted'] = array_diff($allClientEntries, $allServerEntries);
// fetch entries changed since last sync
- $serverModifications['changed'] = $dataController->getChangedEntries($collectionData->collectionId, $collectionData->syncState->lastsync, $this->_syncTimeStamp, $collectionData->options['filterType']);
+ $serverModifications['changed'] = $dataController->getChangedEntries(
+ $collectionData->collectionId,
+ $collectionData->syncState->lastsync,
+ $this->_syncTimeStamp,
+ $collectionData->options['filterType']
+ );
$serverModifications['changed'] = array_merge($serverModifications['changed'], $clientModifications['forceChange']);
foreach($serverModifications['changed'] as $id => $serverId) {
@@ -643,7 +649,7 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
}
// collection header
- $collection = $collections->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Collection'));
+ $collection = $this->_outputDom->createElementNS('uri:AirSync', 'Collection');
if (!empty($collectionData->folder->class)) {
$collection->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Class', $collectionData->folder->class));
}
@@ -681,6 +687,17 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
// send response for to be fetched entries
if(!empty($collectionData->toBeFetched)) {
+ // unset all truncation settings as entries are not allowed to be truncated during fetch
+ $fetchCollectionData = clone $collectionData;
+
+ // unset truncationSize
+ if (isset($fetchCollectionData->options['bodyPreferences']) && is_array($fetchCollectionData->options['bodyPreferences'])) {
+ foreach($fetchCollectionData->options['bodyPreferences'] as $key => $bodyPreference) {
+ unset($fetchCollectionData->options['bodyPreferences'][$key]['truncationSize']);
+ }
+ }
+ $fetchCollectionData->options['mimeTruncation'] = Syncroton_Command_Sync::TRUNCATE_NOTHING;
+
foreach($collectionData->toBeFetched as $serverId) {
$fetch = $responses->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Fetch'));
$fetch->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'ServerId', $serverId));
@@ -689,7 +706,7 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
$applicationData = $this->_outputDom->createElementNS('uri:AirSync', 'ApplicationData');
$dataController
- ->getEntry($collectionData, $serverId)
+ ->getEntry($fetchCollectionData, $serverId)
->appendXML($applicationData, $this->_device);
$fetch->appendChild($this->_outputDom->createElementNS('uri:AirSync', 'Status', self::STATUS_SUCCESS));
@@ -741,7 +758,7 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
$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);
@@ -749,12 +766,20 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
$commands->appendChild($add);
$collectionChanges++;
+ } catch (Syncroton_Exception_MemoryExhausted $seme) {
+ // continue to next entry, as there is not enough memory left for the current entry
+ // this will lead to MoreAvailable at the end and the entry will be synced during the next Sync command
+ if ($this->_logger instanceof Zend_Log)
+ $this->_logger->warn(__METHOD__ . '::' . __LINE__ . " memory exhausted for entry: " . $serverId);
+
+ continue;
+
} catch (Exception $e) {
- if ($this->_logger instanceof Zend_Log)
+ 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
+ // mark as sent to the client, even the conversion to xml might have failed
$newContentStates[] = new Syncroton_Model_Content(array(
'device_id' => $this->_device,
'folder_id' => $collectionData->folder,
@@ -787,6 +812,14 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
$commands->appendChild($change);
$collectionChanges++;
+ } catch (Syncroton_Exception_MemoryExhausted $seme) {
+ // continue to next entry, as there is not enough memory left for the current entry
+ // this will lead to MoreAvailable at the end and the entry will be synced during the next Sync command
+ if ($this->_logger instanceof Zend_Log)
+ $this->_logger->warn(__METHOD__ . '::' . __LINE__ . " memory exhausted for entry: " . $serverId);
+
+ continue;
+
} catch (Exception $e) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->warn(__METHOD__ . '::' . __LINE__ . " unable to convert entry to xml: " . $e->getMessage());
@@ -835,29 +868,35 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
// increase SyncKey if needed
if ((
- // sent the clients updates?
+ // sent the clients updates... ?
!empty($clientModifications['added']) ||
!empty($clientModifications['changed']) ||
!empty($clientModifications['deleted'])
) || (
- // sends the server updates to the client?
+ // is the server sending updates to the client... ?
$commands->hasChildNodes() === true
) || (
- // changed the pending data?
+ // changed the pending data... ?
$collectionData->syncState->pendingdata != $serverModifications
)
) {
- // then increase SyncKey
+ // ...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 ($this->_logger instanceof Zend_Log)
+ $this->_logger->info(__METHOD__ . '::' . __LINE__ . " current synckey is ". $collectionData->syncState->counter);
+
+ if ($collection->childNodes->length > 4 || $collectionData->syncState->counter != $collectionData->syncKey) {
+ $collections->appendChild($collection);
+ }
}
- if (isset($collectionData->syncState) && $collectionData->syncState instanceof Syncroton_Model_ISyncState &&
- $collectionData->syncState->counter != $collectionData->syncKey) {
+ if (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);
@@ -918,21 +957,144 @@ class Syncroton_Command_Sync extends Syncroton_Command_Wbxml
throw $zdse;
}
-
-
- // store current filter type
- try {
- $folderState = $this->_folderBackend->getFolder($this->_device, $collectionData->collectionId);
- $folderState->lastfiltertype = $collectionData->options['filterType'];
+ }
+
+ // store current filter type
+ try {
+ $folderState = $this->_folderBackend->get($collectionData->folder);
+ $folderState->lastfiltertype = $collectionData->options['filterType'];
+ if ($folderState->isDirty()) {
$this->_folderBackend->update($folderState);
- } catch (Syncroton_Exception_NotFound $senf) {
- // failed to get folderstate => should not happen but is also no problem in this state
- if ($this->_logger instanceof Zend_Log)
- $this->_logger->crit(__METHOD__ . '::' . __LINE__ . ' failed to get content state for: ' . $collectionData->collectionId);
}
+ } catch (Syncroton_Exception_NotFound $senf) {
+ // failed to get folderstate => should not happen but is also no problem in this state
+ if ($this->_logger instanceof Zend_Log)
+ $this->_logger->warn(__METHOD__ . '::' . __LINE__ . ' failed to get folder state for: ' . $collectionData->collectionId);
+ }
+ }
+
+ if ($collections->hasChildNodes() === true) {
+ $sync->appendChild($collections);
+ }
+
+ if ($sync->hasChildNodes()) {
+ return $this->_outputDom;
+ }
+
+ return null;
+ }
+
+ /**
+ * remove Commands and Supported from collections XML tree
+ *
+ * @param DOMDocument $document
+ * @return DOMDocument
+ */
+ protected function _cleanUpXML(DOMDocument $document)
+ {
+ $cleanedDocument = clone $document;
+
+ $xpath = new DomXPath($cleanedDocument);
+ $xpath->registerNamespace('AirSync', 'uri:AirSync');
+
+ $collections = $xpath->query("//AirSync:Sync/AirSync:Collections/AirSync:Collection");
+
+ // remove Commands and Supported elements
+ foreach ($collections as $collection) {
+ foreach (array('Commands', 'Supported') as $element) {
+ $childrenToRemove = $collection->getElementsByTagName($element);
+
+ foreach ($childrenToRemove as $childToRemove) {
+ $collection->removeChild($childToRemove);
+ }
+ }
+ }
+
+ return $cleanedDocument;
+ }
+
+ /**
+ * merge a partial XML document with the XML document from the previous request
+ *
+ * @param DOMDocument|null $requestBody
+ * @return SimpleXMLElement
+ */
+ protected function _mergeSyncRequest($requestBody, Syncroton_Model_Device $device)
+ {
+ $lastSyncCollection = array();
+
+ if (!empty($device->lastsynccollection)) {
+ $lastSyncCollection = Zend_Json::decode($device->lastsynccollection);
+ if (!empty($lastSyncCollection['lastXML'])) {
+ $lastXML = new DOMDocument();
+ $lastXML->loadXML($lastSyncCollection['lastXML']);
}
}
- return $this->_outputDom;
+ if (! $requestBody instanceof DOMDocument && isset($lastXML) && $lastXML instanceof DOMDocument) {
+ $requestBody = $lastXML;
+ } elseif (! $requestBody instanceof DOMDocument) {
+ throw new Syncroton_Exception_UnexpectedValue('no xml body found');
+ }
+
+ if ($requestBody->getElementsByTagName('Partial')->length > 0) {
+ $partialBody = clone $requestBody;
+ $requestBody = $lastXML;
+
+ $xpath = new DomXPath($requestBody);
+ $xpath->registerNamespace('AirSync', 'uri:AirSync');
+
+ foreach ($partialBody->documentElement->childNodes as $child) {
+ if (! $child instanceof DOMElement) {
+ continue;
+ }
+
+ if ($child->tagName == 'Partial') {
+ continue;
+ }
+
+ if ($child->tagName == 'Collections') {
+ foreach ($child->getElementsByTagName('Collection') as $updatedCollection) {
+ $collectionId = $updatedCollection->getElementsByTagName('CollectionId')->item(0)->nodeValue;
+
+ $existingCollections = $xpath->query("//AirSync:Sync/AirSync:Collections/AirSync:Collection[AirSync:CollectionId='$collectionId']");
+
+ if ($existingCollections->length > 0) {
+ $existingCollection = $existingCollections->item(0);
+ foreach ($updatedCollection->childNodes as $updatedCollectionChild) {
+ if (! $updatedCollectionChild instanceof DOMElement) {
+ continue;
+ }
+
+ $duplicateChild = $existingCollection->getElementsByTagName($updatedCollectionChild->tagName);
+
+ if ($duplicateChild->length > 0) {
+ $existingCollection->replaceChild($requestBody->importNode($updatedCollectionChild, TRUE), $duplicateChild->item(0));
+ } else {
+ $existingCollection->appendChild($requestBody->importNode($updatedCollectionChild, TRUE));
+ }
+ }
+ } else {
+ $importedCollection = $requestBody->importNode($updatedCollection, TRUE);
+ }
+ }
+
+ } else {
+ $duplicateChild = $xpath->query("//AirSync:Sync/AirSync:{$child->tagName}");
+
+ if ($duplicateChild->length > 0) {
+ $requestBody->documentElement->replaceChild($requestBody->importNode($child, TRUE), $duplicateChild->item(0));
+ } else {
+ $requestBody->documentElement->appendChild($requestBody->importNode($child, TRUE));
+ }
+ }
+ }
+ }
+
+ $lastSyncCollection['lastXML'] = $this->_cleanUpXML($requestBody)->saveXML();
+
+ $device->lastsynccollection = Zend_Json::encode($lastSyncCollection);
+
+ return $requestBody;
}
}
diff --git a/lib/ext/Syncroton/Command/Wbxml.php b/lib/ext/Syncroton/Command/Wbxml.php
index e927cc2..a252d25 100644
--- a/lib/ext/Syncroton/Command/Wbxml.php
+++ b/lib/ext/Syncroton/Command/Wbxml.php
@@ -180,7 +180,7 @@ abstract class Syncroton_Command_Wbxml implements Syncroton_Command_ICommand
if (!empty($this->_device->policyId)) {
$policy = $this->_policyBackend->get($this->_device->policyId);
- if($policy->policyKey != $this->_policyKey) {
+ if((int)$policy->policyKey != (int)$this->_policyKey) {
$this->_outputDom->documentElement->appendChild($this->_outputDom->createElementNS($this->_defaultNameSpace, 'Status', 142));
$sepn = new Syncroton_Exception_ProvisioningNeeded();
diff --git a/lib/ext/Syncroton/Data/AData.php b/lib/ext/Syncroton/Data/AData.php
index e99e0b9..1a76021 100644
--- a/lib/ext/Syncroton/Data/AData.php
+++ b/lib/ext/Syncroton/Data/AData.php
@@ -23,6 +23,17 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
*/
public static $changedEntries = array();
+ /**
+ * used by unit tests only to simulated exhausted memory
+ */
+ public static $exhaustedEntries = array();
+
+ /**
+ * the constructor
+ *
+ * @param Syncroton_Model_IDevice $_device
+ * @param DateTime $_timeStamp
+ */
public function __construct(Syncroton_Model_IDevice $_device, DateTime $_timeStamp)
{
$this->_device = $_device;
@@ -32,6 +43,13 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
$this->_ownerId = '1234';
}
+ /**
+ * return one folder identified by id
+ *
+ * @param string $id
+ * @throws Syncroton_Exception_NotFound
+ * @return Syncroton_Model_Folder
+ */
public function getFolder($id)
{
$select = $this->_db->select()
@@ -55,6 +73,10 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
));
}
+ /**
+ * (non-PHPdoc)
+ * @see Syncroton_Data_IData::createFolder()
+ */
public function createFolder(Syncroton_Model_IFolder $folder)
{
if (!in_array($folder->type, $this->_supportedFolderTypes)) {
@@ -64,16 +86,21 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
$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
+ 'id' => $id,
+ 'type' => $folder->type,
+ 'name' => $folder->displayName,
+ 'owner_id' => $this->_ownerId,
+ 'parent_id' => $folder->parentId,
+ 'creation_time' => $this->_timestamp->format('Y-m-d H:i:s')
));
return $this->getFolder($id);
}
+ /**
+ * (non-PHPdoc)
+ * @see Syncroton_Data_IData::createEntry()
+ */
public function createEntry($_folderId, Syncroton_Model_IEntry $_entry)
{
$id = sha1(mt_rand(). microtime());
@@ -88,6 +115,10 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
return $id;
}
+ /**
+ * (non-PHPdoc)
+ * @see Syncroton_Data_IData::deleteEntry()
+ */
public function deleteEntry($_folderId, $_serverId, $_collectionData)
{
$folderId = $_folderId instanceof Syncroton_Model_IFolder ? $_folderId->serverId : $_folderId;
@@ -97,6 +128,10 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
return (bool) $result;
}
+ /**
+ * (non-PHPdoc)
+ * @see Syncroton_Data_IData::deleteFolder()
+ */
public function deleteFolder($_folderId)
{
$folderId = $_folderId instanceof Syncroton_Model_IFolder ? $_folderId->serverId : $_folderId;
@@ -107,6 +142,19 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
return (bool) $result;
}
+ /**
+ * (non-PHPdoc)
+ * @see Syncroton_Data_IData::emptyFolderContents()
+ */
+ public function emptyFolderContents($folderId, $options)
+ {
+ return true;
+ }
+
+ /**
+ * (non-PHPdoc)
+ * @see Syncroton_Data_IData::getAllFolders()
+ */
public function getAllFolders()
{
$select = $this->_db->select()
@@ -132,6 +180,10 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
return $result;
}
+ /**
+ * (non-PHPdoc)
+ * @see Syncroton_Data_IData::getChangedEntries()
+ */
public function getChangedEntries($_folderId, DateTime $_startTimeStamp, DateTime $_endTimeStamp = NULL, $filterType = NULL)
{
if (!isset(Syncroton_Data_AData::$changedEntries[get_class($this)])) {
@@ -142,6 +194,40 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
}
/**
+ * retrieve folders which were modified since last sync
+ *
+ * @param DateTime $startTimeStamp
+ * @param DateTime $endTimeStamp
+ * @return array list of Syncroton_Model_Folder
+ */
+ public function getChangedFolders(DateTime $startTimeStamp, DateTime $endTimeStamp)
+ {
+ $select = $this->_db->select()
+ ->from($this->_tablePrefix . 'data_folder')
+ ->where('type IN (?)', $this->_supportedFolderTypes)
+ ->where('owner_id = ?', $this->_ownerId)
+ ->where('last_modified_time > ?', $startTimeStamp->format('Y-m-d H:i:s'))
+ ->where('last_modified_time <= ?', $endTimeStamp->format('Y-m-d H:i:s'));
+
+ $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;
+ }
+
+ /**
* @param Syncroton_Model_IFolder|string $_folderId
* @param string $_filter
* @return array
@@ -164,6 +250,10 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
return $ids;
}
+ /**
+ * (non-PHPdoc)
+ * @see Syncroton_Data_IData::getCountOfChanges()
+ */
public function getCountOfChanges(Syncroton_Backend_IContent $contentBackend, Syncroton_Model_IFolder $folder, Syncroton_Model_ISyncState $syncState)
{
$allClientEntries = $contentBackend->getFolderState($this->_device, $folder);
@@ -176,6 +266,10 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
return count($addedEntries) + count($deletedEntries) + count($changedEntries);
}
+ /**
+ * (non-PHPdoc)
+ * @see Syncroton_Data_IData::getFileReference()
+ */
public function getFileReference($fileReference)
{
throw new Syncroton_Exception_NotFound('filereference not found');
@@ -186,7 +280,10 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
* @see Syncroton_Data_IData::getEntry()
*/
public function getEntry(Syncroton_Model_SyncCollection $collection, $serverId)
- {
+ {
+ if (isset(self::$exhaustedEntries[get_class($this)]) && is_array(self::$exhaustedEntries[get_class($this)]) && in_array($serverId, self::$exhaustedEntries[get_class($this)])) {
+ throw new Syncroton_Exception_MemoryExhausted('memory exchausted for ' . $serverId);
+ }
$select = $this->_db->select()
->from($this->_tablePrefix . 'data', array('data'))
->where('id = ?', $serverId);
@@ -210,6 +307,10 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
return !!$this->getCountOfChanges($contentBackend, $folder, $syncState);
}
+ /**
+ * (non-PHPdoc)
+ * @see Syncroton_Data_IData::moveItem()
+ */
public function moveItem($_srcFolderId, $_serverId, $_dstFolderId)
{
$this->_db->update($this->_tablePrefix . 'data', array(
@@ -221,6 +322,10 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
return $_serverId;
}
+ /**
+ * (non-PHPdoc)
+ * @see Syncroton_Data_IData::updateEntry()
+ */
public function updateEntry($_folderId, $_serverId, Syncroton_Model_IEntry $_entry)
{
$this->_db->update($this->_tablePrefix . 'data', array(
@@ -230,15 +335,23 @@ abstract class Syncroton_Data_AData implements Syncroton_Data_IData
'id = ?' => $_serverId
));
}
-
+
+ /**
+ * (non-PHPdoc)
+ * @see Syncroton_Data_IData::updateFolder()
+ */
public function updateFolder(Syncroton_Model_IFolder $folder)
{
$this->_db->update($this->_tablePrefix . 'data_folder', array(
- 'name' => $folder->displayName,
- 'parent_id' => $folder->parentId
+ 'name' => $folder->displayName,
+ 'parent_id' => $folder->parentId,
+ 'last_modified_time' => $this->_timestamp->format('Y-m-d H:i:s')
), array(
- 'id = ?' => $folder->serverId
+ 'id = ?' => $folder->serverId,
+ 'owner_id = ?' => $this->_ownerId
));
+
+ return $this->getFolder($folder->serverId);
}
}
diff --git a/lib/ext/Syncroton/Data/IData.php b/lib/ext/Syncroton/Data/IData.php
index a21dfd5..2d26335 100644
--- a/lib/ext/Syncroton/Data/IData.php
+++ b/lib/ext/Syncroton/Data/IData.php
@@ -43,7 +43,20 @@ interface Syncroton_Data_IData
*/
public function deleteEntry($_folderId, $_serverId, $_collectionData);
- public function deleteFolder($_folderId);
+ /**
+ * delete folder
+ *
+ * @param string $folderId
+ */
+ public function deleteFolder($folderId);
+
+ /**
+ * empty folder
+ *
+ * @param string $folderId
+ * @param array $options
+ */
+ public function emptyFolderContents($folderId, $options);
/**
* return list off all folders
@@ -53,6 +66,14 @@ interface Syncroton_Data_IData
public function getChangedEntries($folderId, DateTime $startTimeStamp, DateTime $endTimeStamp = NULL, $filterType = NULL);
+ /**
+ * retrieve folders which were modified since last sync
+ *
+ * @param DateTime $startTimeStamp
+ * @param DateTime $endTimeStamp
+ */
+ public function getChangedFolders(DateTime $startTimeStamp, DateTime $endTimeStamp);
+
public function getCountOfChanges(Syncroton_Backend_IContent $contentBackend, Syncroton_Model_IFolder $folder, Syncroton_Model_ISyncState $syncState);
/**
diff --git a/lib/ext/Syncroton/Exception/MemoryExhausted.php b/lib/ext/Syncroton/Exception/MemoryExhausted.php
new file mode 100644
index 0000000..ee346cc
--- /dev/null
+++ b/lib/ext/Syncroton/Exception/MemoryExhausted.php
@@ -0,0 +1,20 @@
+<?php
+/**
+ * Syncroton
+ *
+ * @package Syncroton
+ * @subpackage Exception
+ * @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
+ * @copyright Copyright (c) 2013-2013 Metaways Infosystems GmbH (http://www.metaways.de)
+ * @author Lars Kneschke <l.kneschke at metaways.de>
+ */
+
+/**
+ * exception for memory exhausted
+ *
+ * @package Syncroton
+ * @subpackage Exception
+ */
+class Syncroton_Exception_MemoryExhausted extends Syncroton_Exception
+{
+}
diff --git a/lib/ext/Syncroton/Model/AXMLEntry.php b/lib/ext/Syncroton/Model/AXMLEntry.php
index 6dc1e9f..faecc88 100644
--- a/lib/ext/Syncroton/Model/AXMLEntry.php
+++ b/lib/ext/Syncroton/Model/AXMLEntry.php
@@ -93,11 +93,14 @@ abstract class Syncroton_Model_AXMLEntry extends Syncroton_Model_AEntry implemen
* (non-PHPdoc)
* @see Syncroton_Model_IEntry::getProperties()
*/
- public function getProperties()
+ public function getProperties($selectedNamespace = null)
{
$properties = array();
foreach($this->_properties as $namespace => $namespaceProperties) {
+ if ($selectedNamespace !== null && $namespace != $selectedNamespace) {
+ continue;
+ }
$properties = array_merge($properties, array_keys($namespaceProperties));
}
@@ -169,7 +172,8 @@ abstract class Syncroton_Model_AXMLEntry extends Syncroton_Model_AEntry implemen
if (!ctype_print($value)) {
$value = $this->_removeControlChars($value);
}
- $element->appendChild($element->ownerDocument->createTextNode($value));
+
+ $element->appendChild($element->ownerDocument->createTextNode($this->_enforeUTF8($value)));
}
}
}
@@ -186,6 +190,29 @@ abstract class Syncroton_Model_AXMLEntry extends Syncroton_Model_AEntry implemen
}
/**
+ * enforce >valid< utf-8 encoding
+ *
+ * @param string $dirty the string with maybe invalid utf-8 data
+ * @return string string with valid utf-8
+ */
+ protected function _enforeUTF8($dirty)
+ {
+ if (function_exists('iconv')) {
+ if (($clean = @iconv('UTF-8', 'UTF-8//IGNORE', $dirty)) !== false) {
+ return $clean;
+ }
+ }
+
+ if (function_exists('mb_convert_encoding')) {
+ if (($clean = mb_convert_encoding($dirty, 'UTF-8', 'UTF-8')) !== false) {
+ return $clean;
+ }
+ }
+
+ return $dirty;
+ }
+
+ /**
*
* @param unknown_type $element
* @throws InvalidArgumentException
diff --git a/lib/ext/Syncroton/Model/Content.php b/lib/ext/Syncroton/Model/Content.php
index 27075f2..5df9826 100644
--- a/lib/ext/Syncroton/Model/Content.php
+++ b/lib/ext/Syncroton/Model/Content.php
@@ -15,18 +15,7 @@
* @package Syncroton
* @subpackage Model
*/
-class Syncroton_Model_Content implements Syncroton_Model_IContent
+class Syncroton_Model_Content extends Syncroton_Model_AEntry implements Syncroton_Model_IContent
{
- public function __construct(array $_data = array())
- {
- $this->setFromArray($_data);
- }
-
- public function setFromArray(array $_data)
- {
- foreach($_data as $key => $value) {
- $this->$key = $value;
- }
- }
}
diff --git a/lib/ext/Syncroton/Model/Event.php b/lib/ext/Syncroton/Model/Event.php
index 08dc9c5..0fccd2a 100644
--- a/lib/ext/Syncroton/Model/Event.php
+++ b/lib/ext/Syncroton/Model/Event.php
@@ -65,32 +65,45 @@ class Syncroton_Model_Event extends Syncroton_Model_AXMLEntry
)
);
- 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)
+ /**
+ * (non-PHPdoc)
+ * @see Syncroton_Model_IEntry::appendXML()
+ * @todo handle Attendees element
+ */
+ public function appendXML(DOMElement $domParrent, Syncroton_Model_IDevice $device)
{
- parent::setFromSimpleXMLElement($properties);
-
- $this->_copyFieldsFromParent();
+ parent::appendXML($domParrent, $device);
+
+ $exceptionElements = $domParrent->getElementsByTagName('Exception');
+ $parentFields = array('AllDayEvent'/*, 'Attendees'*/, 'Body', 'BusyStatus'/*, 'Categories'*/, 'DtStamp', 'EndTime', 'Location', 'MeetingStatus', 'Reminder', 'ResponseType', 'Sensitivity', 'StartTime', 'Subject');
+
+ if ($exceptionElements->length > 0) {
+ $mainEventElement = $exceptionElements->item(0)->parentNode->parentNode;
+
+ foreach ($mainEventElement->childNodes as $childNode) {
+ if (in_array($childNode->localName, $parentFields)) {
+ foreach ($exceptionElements as $exception) {
+ $elementsToLeftOut = $exception->getElementsByTagName($childNode->localName);
+
+ foreach ($elementsToLeftOut as $elementToLeftOut) {
+ if ($elementToLeftOut->nodeValue == $childNode->nodeValue) {
+ $exception->removeChild($elementToLeftOut);
+ }
+ }
+ }
+ }
+ }
+ }
}
/**
- * 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
+ * some elements of an exception can be left out, if they have the same value
+ * like the main event
+ *
+ * this function copies these elements to the exception for backends which need
+ * this elements in the exceptions too. Tine 2.0 needs this for example.
*/
- protected function _copyFieldsFromParent()
+ public function copyFieldsFromParent()
{
if (isset($this->_elements['exceptions']) && is_array($this->_elements['exceptions'])) {
foreach ($this->_elements['exceptions'] as $exception) {
@@ -98,30 +111,15 @@ class Syncroton_Model_Event extends Syncroton_Model_AXMLEntry
if ($exception->deleted == 1) {
continue;
}
-
- $parentFields = array(
- 'allDayEvent',
- 'attendees',
- 'busyStatus',
- 'meetingStatus',
- 'sensitivity',
- 'subject',
- 'body',
- 'location',
- 'reminder'
- );
-
- foreach ($parentFields as $field) {
- if (isset($this->_elements[$field])) {
- if (!isset($exception->$field)) {
- $exception->$field = $this->_elements[$field];
- }
- else if ($exception->$field == $this->_elements[$field]) {
- unset($exception->$field);
- }
- }
- }
- }
- }
+
+ $parentFields = array('allDayEvent', 'attendees', 'body', 'busyStatus', 'categories', 'dtStamp', 'endTime', 'location', 'meetingStatus', 'reminder', 'responseType', 'sensitivity', 'startTime', '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/EventException.php b/lib/ext/Syncroton/Model/EventException.php
index 4c6361c..63ae15e 100644
--- a/lib/ext/Syncroton/Model/EventException.php
+++ b/lib/ext/Syncroton/Model/EventException.php
@@ -27,9 +27,9 @@ class Syncroton_Model_EventException extends Syncroton_Model_AXMLEntry
protected $_dateTimeFormat = "Ymd\THis\Z";
protected $_properties = array(
- 'AirSyncBase' => array(
- 'body' => array('type' => 'container', 'class' => 'Syncroton_Model_EmailBody')
- ),
+ 'AirSyncBase' => array(
+ 'body' => array('type' => 'container', 'class' => 'Syncroton_Model_EmailBody')
+ ),
'Calendar' => array(
'allDayEvent' => array('type' => 'number'),
'appointmentReplyTime' => array('type' => 'datetime'),
diff --git a/lib/ext/Syncroton/Model/IContent.php b/lib/ext/Syncroton/Model/IContent.php
index 0ec4e2a..2ce315a 100644
--- a/lib/ext/Syncroton/Model/IContent.php
+++ b/lib/ext/Syncroton/Model/IContent.php
@@ -24,6 +24,5 @@
interface Syncroton_Model_IContent
{
-
}
diff --git a/lib/ext/Syncroton/Model/ISyncState.php b/lib/ext/Syncroton/Model/ISyncState.php
index 8e73069..cd1fc2a 100644
--- a/lib/ext/Syncroton/Model/ISyncState.php
+++ b/lib/ext/Syncroton/Model/ISyncState.php
@@ -21,6 +21,5 @@
interface Syncroton_Model_ISyncState
{
-
}
diff --git a/lib/ext/Syncroton/Model/SyncCollection.php b/lib/ext/Syncroton/Model/SyncCollection.php
index b6370dc..5976b02 100644
--- a/lib/ext/Syncroton/Model/SyncCollection.php
+++ b/lib/ext/Syncroton/Model/SyncCollection.php
@@ -273,6 +273,11 @@ class Syncroton_Model_SyncCollection extends Syncroton_Model_AXMLEntry
// optional
if (isset($bodyPreference->TruncationSize)) {
$this->_elements['options']['bodyPreferences'][$type]['truncationSize'] = (int) $bodyPreference->TruncationSize;
+ }
+
+ // optional
+ if (isset($bodyPreference->Preview)) {
+ $this->_elements['options']['bodyPreferences'][$type]['preview'] = (int) $bodyPreference->Preview;
}
}
}
diff --git a/lib/ext/Syncroton/Model/SyncState.php b/lib/ext/Syncroton/Model/SyncState.php
index b44b4c5..398f3a7 100644
--- a/lib/ext/Syncroton/Model/SyncState.php
+++ b/lib/ext/Syncroton/Model/SyncState.php
@@ -15,18 +15,7 @@
* @package Model
*/
-class Syncroton_Model_SyncState implements Syncroton_Model_ISyncState
+class Syncroton_Model_SyncState extends Syncroton_Model_AEntry implements Syncroton_Model_ISyncState
{
- public function __construct(array $_data = array())
- {
- $this->setFromArray($_data);
- }
-
- public function setFromArray(array $_data)
- {
- foreach($_data as $key => $value) {
- $this->$key = $value;
- }
- }
}
diff --git a/lib/ext/Syncroton/Server.php b/lib/ext/Syncroton/Server.php
index 8c3ccea..0e57ddd 100644
--- a/lib/ext/Syncroton/Server.php
+++ b/lib/ext/Syncroton/Server.php
@@ -81,7 +81,7 @@ class Syncroton_Server
protected function _handleOptions()
{
$command = new Syncroton_Command_Options();
-
+
$this->_sendHeaders($command->getHeaders());
}
@@ -145,12 +145,12 @@ class Syncroton_Server
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " provisioning needed");
+ header("HTTP/1.1 449 Retry after sending a PROVISION command");
+
if (version_compare($device->acsversion, '14.0', '>=')) {
$response = $sepn->domDocument;
} else {
// pre 14.0 method
- header("HTTP/1.1 449 Retry after sending a PROVISION command");
-
return;
}
@@ -181,7 +181,19 @@ class Syncroton_Server
$outputStream = fopen("php://temp", 'r+');
$encoder = new Syncroton_Wbxml_Encoder($outputStream, 'UTF-8', 3);
- $encoder->encode($response);
+
+ try {
+ $encoder->encode($response);
+ } catch (Syncroton_Wbxml_Exception $swe) {
+ if ($this->_logger instanceof Zend_Log) {
+ $this->_logger->err(__METHOD__ . '::' . __LINE__ . " Could not encode output: " . $swe);
+ $this->_logger->err(__METHOD__ . '::' . __LINE__ . " xml response:\n" . $response->saveXML());
+ }
+
+ header("HTTP/1.1 500 Internal server error");
+
+ return;
+ }
if ($requestParameters['acceptMultipart'] == true) {
$parts = $command->getParts();
diff --git a/lib/ext/Syncroton/Wbxml/Dtd/ActiveSync/CodePage14.php b/lib/ext/Syncroton/Wbxml/Dtd/ActiveSync/CodePage14.php
index ee5635e..f34442b 100644
--- a/lib/ext/Syncroton/Wbxml/Dtd/ActiveSync/CodePage14.php
+++ b/lib/ext/Syncroton/Wbxml/Dtd/ActiveSync/CodePage14.php
@@ -49,5 +49,34 @@ class Syncroton_Wbxml_Dtd_ActiveSync_CodePage14 extends Syncroton_Wbxml_Dtd_Acti
'AllowStorageCard' => 0x1b,
'AllowCamera' => 0x1c,
'RequireDeviceEncryption' => 0x1d,
+ 'AllowUnsignedApplications' => 0x1e,
+ 'AllowUnsignedInstallationPackages' => 0x1f,
+ 'MinDevicePasswordComplexCharacters' => 0x20,
+ 'AllowWiFi' => 0x21,
+ 'AllowTextMessaging' => 0x22,
+ 'AllowPOPIMAPEmail' => 0x23,
+ 'AllowBluetooth' => 0x24,
+ 'AllowIrDA' => 0x25,
+ 'RequireManualSyncWhenRoaming' => 0x26,
+ 'AllowDesktopSync' => 0x27,
+ 'MaxCalendarAgeFilter' => 0x28,
+ 'AllowHTMLEmail' => 0x29,
+ 'MaxEmailAgeFilter' => 0x2a,
+ 'MaxEmailBodyTruncationSize' => 0x2b,
+ 'MaxEmailHTMLBodyTruncationSize' => 0x2c,
+ 'RequireSignedSMIMEMessages' => 0x2d,
+ 'RequireEncryptedSMIMEMessages' => 0x2e,
+ 'RequireSignedSMIMEAlgorithm' => 0x2F,
+ 'RequireEncryptionSMIMEAlgorithm' => 0x30,
+ 'AllowSMIMEEncryptionAlgorithmNegotiation' => 0x31,
+ 'AllowSMIMESoftCerts' => 0x32,
+ 'AllowBrowser' => 0x33,
+ 'AllowConsumerEmail' => 0x34,
+ 'AllowRemoteDesktop' => 0x35,
+ 'AllowInternetSharing' => 0x36,
+ 'UnapprovedInROMApplicationList' => 0x37,
+ 'ApplicationName' => 0x38,
+ 'ApprovedApplicationList' => 0x39,
+ 'Hash' => 0x3a,
);
-}
\ No newline at end of file
+}
diff --git a/lib/ext/Syncroton/Wbxml/Dtd/ActiveSync/CodePage9.php b/lib/ext/Syncroton/Wbxml/Dtd/ActiveSync/CodePage9.php
index 033f156..7d33bc6 100644
--- a/lib/ext/Syncroton/Wbxml/Dtd/ActiveSync/CodePage9.php
+++ b/lib/ext/Syncroton/Wbxml/Dtd/ActiveSync/CodePage9.php
@@ -54,8 +54,8 @@ class Syncroton_Wbxml_Dtd_ActiveSync_CodePage9 extends Syncroton_Wbxml_Dtd_Activ
'Rtf' => 0x21,
'OrdinalDate' => 0x22,
'SubOrdinalDate' => 0x23,
- 'CalendarType' => 0x23,
- 'IsLeapMonth' => 0x23,
- 'FirstDayOfWeek' => 0x23
+ 'CalendarType' => 0x24,
+ 'IsLeapMonth' => 0x25,
+ 'FirstDayOfWeek' => 0x26,
);
}
\ No newline at end of file
diff --git a/lib/kolab_sync_data.php b/lib/kolab_sync_data.php
index 0c6d0ef..bdfcbb7 100644
--- a/lib/kolab_sync_data.php
+++ b/lib/kolab_sync_data.php
@@ -85,6 +85,13 @@ abstract class kolab_sync_data implements Syncroton_Data_IData
protected $folders = array();
/**
+ * Internal cache for IMAP folders list
+ *
+ * @var array
+ */
+ protected $imap_folders = array();
+
+ /**
* Timezone
*
* @var string
@@ -101,6 +108,7 @@ abstract class kolab_sync_data implements Syncroton_Data_IData
'ipad',
'thundertine',
'windowsphone',
+ 'playbook',
);
const RESULT_OBJECT = 0;
@@ -200,7 +208,7 @@ abstract class kolab_sync_data implements Syncroton_Data_IData
// device supports multiple folders ?
if (in_array(strtolower($this->device->devicetype), $this->ext_devices)) {
// get the folders the user has access to
- $list = $this->backend->folders_list($this->device->deviceid, $this->modelName);
+ $list = $this->listFolders();
}
else if ($default = $this->getDefaultFolder()) {
$list = array($default['serverId'] => $default);
@@ -220,12 +228,25 @@ abstract class kolab_sync_data implements Syncroton_Data_IData
}
/**
+ * Retrieve folders which were modified since last sync
+ *
+ * @param DateTime $startTimeStamp
+ * @param DateTime $endTimeStamp
+ *
+ * @return array List of folders
+ */
+ public function getChangedFolders(DateTime $startTimeStamp, DateTime $endTimeStamp)
+ {
+ return array();
+ }
+
+ /**
* 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);
+ $folders = $this->listFolders();
if (empty($folders)) {
return $folders;
@@ -343,6 +364,47 @@ abstract class kolab_sync_data implements Syncroton_Data_IData
}
/**
+ * Empty folder (remove all entries and optionally subfolders)
+ *
+ * @param string $folderId Folder identifier
+ * @param array $options Options
+ */
+ public function emptyFolderContents($folderid, $options)
+ {
+ $folders = $this->extractFolders($folderid);
+
+ foreach ($folders as $folderid) {
+ $foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid);
+
+ if ($foldername === null) {
+ continue;
+ }
+
+ $folder = $this->getFolderObject($foldername);
+
+ // Remove all entries
+ $folder->delete_all();
+
+ // Remove subfolders
+ if (!empty($options['deleteSubFolders'])) {
+ $list = $this->listFolders($folderid);
+ foreach ($list as $folderid => $folder) {
+ $foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid);
+
+ if ($foldername === null) {
+ continue;
+ }
+
+ $folder = $this->getFolderObject($foldername);
+
+ // Remove all entries
+ $folder->delete_all();
+ }
+ }
+ }
+ }
+
+ /**
* Moves object into another location (folder)
*
* @param string $srcFolderId Source folder identifier
@@ -456,7 +518,7 @@ abstract class kolab_sync_data implements Syncroton_Data_IData
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);
+ $folders = $this->listFolders();
if (!is_array($folders)) {
throw new Syncroton_Exception_Status(Syncroton_Exception_Status::SERVER_ERROR);
@@ -656,22 +718,7 @@ abstract class kolab_sync_data implements Syncroton_Data_IData
*/
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);
- }
+ $folders = $this->extractFolders($folderid);
foreach ($folders as $folderid) {
$foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid);
@@ -746,6 +793,66 @@ abstract class kolab_sync_data implements Syncroton_Data_IData
}
/**
+ * Returns internal folder IDs
+ *
+ * @param string $folderid Folder identifier
+ *
+ * @return array List of folder identifiers
+ */
+ protected function extractFolders($folderid)
+ {
+ if ($folderid instanceof Syncroton_Model_IFolder) {
+ $folderid = $folderid->serverId;
+ }
+
+ if ($folderid == $this->defaultRootFolder) {
+ $folders = $this->listFolders();
+
+ if (!is_array($folders)) {
+ return null;
+ }
+
+ $folders = array_keys($folders);
+ }
+ else {
+ $folders = array($folderid);
+ }
+
+ return $folders;
+ }
+
+ /**
+ * List of all IMAP folders (or subtree)
+ *
+ * @param string $parentid Parent folder identifier
+ *
+ * @return array List of folder identifiers
+ */
+ protected function listFolders($parentid = null)
+ {
+ if (empty($this->imap_folders)) {
+ $this->imap_folders = $this->backend->folders_list($this->device->deviceid, $this->modelName);
+ }
+
+ if ($parentid === null) {
+ return $this->imap_folders;
+ }
+
+ $folders = array();
+ $parents = array($parentid);
+
+ foreach ($this->imap_folders as $folder_id => $folder) {
+ if ($folder['parentId'] && in_array($folder['parentId'], $parents)) {
+ $folders[$folder_id] = $folder;
+ $parents[] = $folder_id;
+ }
+ }
+
+
+ return $folders;
+ }
+
+ /**
* Returns Folder object (uses internal cache)
*
* @param string $name Folder name (UTF7-IMAP)
diff --git a/lib/kolab_sync_data_email.php b/lib/kolab_sync_data_email.php
index 6ad2ed8..6822f54 100644
--- a/lib/kolab_sync_data_email.php
+++ b/lib/kolab_sync_data_email.php
@@ -416,7 +416,7 @@ class kolab_sync_data_email extends kolab_sync_data implements Syncroton_Data_ID
*/
public function getAllFolders()
{
- $list = $this->backend->folders_list($this->device->deviceid, $this->modelName);
+ $list = $this->listFolders();
if (!is_array($list)) {
throw new Syncroton_Exception_Status_FolderSync(Syncroton_Exception_Status_FolderSync::FOLDER_SERVER_ERROR);
@@ -457,7 +457,7 @@ class kolab_sync_data_email extends kolab_sync_data implements Syncroton_Data_ID
*/
protected function extractFolders($folder_id)
{
- $list = $this->backend->folders_list($this->device->deviceid, $this->modelName);
+ $list = $this->listFolders();
$result = array();
if (!is_array($list)) {
@@ -1002,7 +1002,7 @@ class kolab_sync_data_email extends kolab_sync_data implements Syncroton_Data_ID
// @TODO: DeepTraversal
if (empty($folders)) {
- $folders = $this->backend->folders_list($this->device->deviceid, $this->modelName);
+ $folders = $this->listFolders();
if (is_array($folders)) {
$folders = array_keys($folders);
More information about the commits
mailing list