Page MenuHomePhorge

D4686.1775166300.diff
No OneTemporary

Authored By
Unknown
Size
60 KB
Referenced Files
None
Subscribers
None

D4686.1775166300.diff

diff --git a/bin/inspect.php b/bin/inspect.php
--- a/bin/inspect.php
+++ b/bin/inspect.php
@@ -78,7 +78,7 @@
'e' => 'email',
'p' => 'adminpassword',
'd' => 'debug',
- 'k' => 'dump'
+ 'k' => 'dump',
]);
if (empty($opts['email'])) {
diff --git a/config/config.inc.php.dist b/config/config.inc.php.dist
--- a/config/config.inc.php.dist
+++ b/config/config.inc.php.dist
@@ -119,8 +119,3 @@
// Enables adding sender name in the From: header of send email
// when a device uses email address only (e.g. iOS devices)
$config['activesync_fix_from'] = false;
-
-// Force a subscription state per folder
-// The following configuration is recommended for Outlook.
-// States can be: 0 => not subscribed, 1 => subscribed, 2 => subscribed with alarm
-$config['activesync_force_subscription_state'] = ['INBOX' => 1, 'Sent' => 1, 'Trash' => 1, 'Calendar' => 1, 'Contacts' => 1, 'Tasks' => 1];
diff --git a/docs/SQL/mysql.initial.sql b/docs/SQL/mysql.initial.sql
--- a/docs/SQL/mysql.initial.sql
+++ b/docs/SQL/mysql.initial.sql
@@ -109,12 +109,22 @@
CONSTRAINT `syncroton_relations_state::device_id--syncroton_device::id` FOREIGN KEY (`device_id`) REFERENCES `syncroton_device` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
+CREATE TABLE IF NOT EXISTS `syncroton_subscriptions` (
+ `device_id` varchar(40) NOT NULL,
+ `type` varchar(16) NOT NULL,
+ `data` longblob NOT NULL,
+ PRIMARY KEY (`device_id`, `type`),
+ KEY `syncroton_subscriptions::device_id` (`device_id`),
+ CONSTRAINT `syncroton_subscriptions::device_id--syncroton_device::id`
+ FOREIGN KEY (`device_id`) REFERENCES `syncroton_device` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
+) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
+
-- Roundcube core table should exist if we're using the same database
CREATE TABLE IF NOT EXISTS `system` (
- `name` varchar(64) NOT NULL,
- `value` mediumtext,
- PRIMARY KEY(`name`)
+ `name` varchar(64) NOT NULL,
+ `value` mediumtext,
+ PRIMARY KEY (`name`)
) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
-INSERT INTO `system` (`name`, `value`) VALUES ('syncroton-version', '2024102300');
+INSERT INTO `system` (`name`, `value`) VALUES ('syncroton-version', '2025042900');
diff --git a/docs/SQL/mysql/2025042900.sql b/docs/SQL/mysql/2025042900.sql
new file mode 100644
--- /dev/null
+++ b/docs/SQL/mysql/2025042900.sql
@@ -0,0 +1,9 @@
+CREATE TABLE IF NOT EXISTS `syncroton_subscriptions` (
+ `device_id` varchar(40) NOT NULL,
+ `type` varchar(16) NOT NULL,
+ `data` longblob NOT NULL,
+ PRIMARY KEY (`device_id`, `type`),
+ KEY `syncroton_subscriptions::device_id` (`device_id`),
+ CONSTRAINT `syncroton_subscriptions::device_id--syncroton_device::id`
+ FOREIGN KEY (`device_id`) REFERENCES `syncroton_device` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
+) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */;
diff --git a/lib/ext/Syncroton/Command/Ping.php b/lib/ext/Syncroton/Command/Ping.php
--- a/lib/ext/Syncroton/Command/Ping.php
+++ b/lib/ext/Syncroton/Command/Ping.php
@@ -37,7 +37,8 @@
protected $_changesDetected = false;
protected $_foldersWithChanges = [];
- private function goToSleep($sleepInterval) {
+ private function goToSleep($sleepInterval)
+ {
// take a break to save battery lifetime
call_user_func(Syncroton_Registry::getSleepCallback());
sleep($sleepInterval);
diff --git a/lib/kolab_sync_backend_device.php b/lib/kolab_sync_backend_device.php
--- a/lib/kolab_sync_backend_device.php
+++ b/lib/kolab_sync_backend_device.php
@@ -31,23 +31,6 @@
protected $table_name = 'syncroton_device';
protected $interface_name = 'Syncroton_Model_IDevice';
- /**
- * Kolab Sync storage backend
- *
- * @var kolab_sync_storage
- */
- protected $backend;
-
-
- /**
- * Constructor
- */
- public function __construct()
- {
- parent::__construct();
- $this->backend = kolab_sync::storage();
- }
-
/**
* Create (register) a new device
*
@@ -59,33 +42,23 @@
{
$device = parent::create($device);
- // Create device entry in kolab backend
- $created = $this->backend->device_create([
- 'ID' => $device->id,
- 'TYPE' => $device->devicetype,
- 'ALIAS' => $device->friendlyname,
- ], $device->deviceid);
-
- if (!$created) {
- throw new Syncroton_Exception_NotFound('Device creation failed');
+ $sync = kolab_sync::get_instance();
+
+ // 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
+ if ($device->deviceid != 'validate') {
+ $this->db->query(
+ 'DELETE FROM `' . $this->table_name . '` WHERE `deviceid` = ? AND `owner_id` = ?',
+ ['validate', $sync->user->ID]
+ );
}
- return $device;
- }
-
- /**
- * Delete a device
- *
- * @param string|Syncroton_Model_IDevice $device Device object
- *
- * @return bool True on success, False on failure
- */
- public function delete($device)
- {
- // Update IMAP annotation
- $this->backend->device_delete($device->deviceid);
+ // Auto-subscribe a default set of folders
+ $sync->storage()->device_init($device->deviceid);
- return parent::delete($device);
+ return $device;
}
/**
@@ -109,18 +82,7 @@
throw new Syncroton_Exception_NotFound('Device not found');
}
- $device = $this->get_object($device);
-
- // Make sure device exists (could be deleted by the user)
- $dev = $this->backend->device_get($deviceid);
- if (empty($dev)) {
- // Remove the device (and related cached data) from database
- $this->delete($device);
-
- throw new Syncroton_Exception_NotFound('Device not found');
- }
-
- return $device;
+ return $this->get_object($device);
}
/**
diff --git a/lib/kolab_sync_data.php b/lib/kolab_sync_data.php
--- a/lib/kolab_sync_data.php
+++ b/lib/kolab_sync_data.php
@@ -334,7 +334,13 @@
*/
public function updateFolder(Syncroton_Model_IFolder $folder)
{
- $result = $this->backend->folder_rename($folder->serverId, $this->device->deviceid, $folder->displayName, $folder->parentId);
+ $result = $this->backend->folder_rename(
+ $folder->serverId,
+ $this->device->deviceid,
+ $this->modelName,
+ $folder->displayName,
+ $folder->parentId
+ );
if ($result) {
return $folder;
@@ -353,7 +359,7 @@
}
// @TODO: throw exception
- return $this->backend->folder_delete($folder, $this->device->deviceid);
+ return $this->backend->folder_delete($folder, $this->device->deviceid, $this->modelName);
}
/**
@@ -371,7 +377,7 @@
// TODO: Respond with MailboxQuotaExceeded status. Where exactly?
foreach ($this->extractFolders($folderid) as $folderid) {
- if (!$this->backend->folder_empty($folderid, $this->device->deviceid, !empty($options['deleteSubFolders']))) {
+ if (!$this->backend->folder_empty($folderid, $this->device->deviceid, $this->modelName, !empty($options['deleteSubFolders']))) {
throw new Syncroton_Exception_Status_ItemOperations(Syncroton_Exception_Status_ItemOperations::ITEM_SERVER_ERROR);
}
}
@@ -587,10 +593,16 @@
return $this->searchEntries($folderId, $filter, self::RESULT_COUNT, $syncState->extraData);
}
-
+ /**
+ * Get additional metadata for a specified folder
+ *
+ * @param Syncroton_Model_IFolder $folder Folder object
+ *
+ * @return string|null JSON-encoded string
+ */
public function getExtraData(Syncroton_Model_IFolder $folder)
{
- return $this->backend->getExtraData($folder->serverId, $this->device->deviceid);
+ return $this->backend->getExtraData($folder->serverId, $this->device->deviceid, $this->modelName);
}
/**
diff --git a/lib/kolab_sync_data_calendar.php b/lib/kolab_sync_data_calendar.php
--- a/lib/kolab_sync_data_calendar.php
+++ b/lib/kolab_sync_data_calendar.php
@@ -1160,7 +1160,7 @@
$is_organizer = in_array_nocase($event['organizer']['email'], $user_emails);
}
- if ($event['status'] == 'CANCELLED') {
+ if (isset($event['status']) && $event['status'] == 'CANCELLED') {
$status = $is_organizer ? 5 : 7;
} else {
$status = $is_organizer ? 1 : 3;
diff --git a/lib/kolab_sync_data_email.php b/lib/kolab_sync_data_email.php
--- a/lib/kolab_sync_data_email.php
+++ b/lib/kolab_sync_data_email.php
@@ -1017,7 +1017,7 @@
// @TODO: caching with Options->RebuildResults support
foreach ($folders as $folderid) {
- $foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid);
+ $foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid, $this->modelName);
if ($foldername === null) {
continue;
diff --git a/lib/kolab_sync_storage.php b/lib/kolab_sync_storage.php
--- a/lib/kolab_sync_storage.php
+++ b/lib/kolab_sync_storage.php
@@ -51,15 +51,13 @@
public $syncTimeStamp;
protected $storage;
- protected $folder_meta;
protected $folder_uids;
protected $folders = [];
- protected $root_meta;
protected $relations = [];
- protected $relationSupport = [self::MODEL_TASKS, self::MODEL_NOTES, self::MODEL_EMAIL];
+ protected $relationSupport = true;
+ protected $subscriptions;
protected $tag_rts = [];
private $modseq = [];
- private $protectedFolders = null;
protected static $instance;
@@ -117,6 +115,8 @@
// Disable paging
$this->storage->set_pagesize(999999);
+
+ $this->subscriptions = new kolab_subscriptions();
}
/**
@@ -125,30 +125,6 @@
public function reset()
{
$this->folders = [];
- $this->folder_meta = null;
- }
-
- /**
- * List known devices
- *
- * @return array Device list as hash array
- */
- public function devices_list()
- {
- if ($this->root_meta === null) {
- // @TODO: consider server annotation instead of INBOX
- if ($meta = $this->storage->get_metadata(self::ROOT_MAILBOX, self::ASYNC_KEY)) {
- $this->root_meta = $this->unserialize_metadata($meta[self::ROOT_MAILBOX][self::ASYNC_KEY]);
- } else {
- $this->root_meta = [];
- }
- }
-
- if (!empty($this->root_meta['DEVICE']) && is_array($this->root_meta['DEVICE'])) {
- return $this->root_meta['DEVICE'];
- }
-
- return [];
}
/**
@@ -162,31 +138,15 @@
*/
public function folders_list($deviceid, $type, $flat_mode = false)
{
- // get all folders of specified type
- $folders = kolab_storage::list_folders('', '*', $type, false, $typedata);
-
- // get folders activesync config
- $folderdata = $this->folder_meta();
-
- if (!is_array($folders) || !is_array($folderdata)) {
- return false;
- }
+ $typedata = kolab_storage::folders_typedata();
$folders_list = [];
// check if folders are "subscribed" for activesync
- foreach ($folderdata as $folder => $meta) {
- if (!$this->is_subscribed($deviceid, $folder, $meta)) {
- continue;
- }
-
+ foreach ($this->subscriptions->list_subscriptions($deviceid, $type) as $folder => $sub) {
// force numeric folder name to be a string (T1283)
$folder = (string) $folder;
- if (!empty($type) && !in_array($folder, $folders)) {
- continue;
- }
-
// Activesync folder identifier (serverId)
$folder_type = !empty($typedata[$folder]) ? $typedata[$folder] : 'mail';
$folder_id = $this->folder_id($folder, $folder_type);
@@ -256,35 +216,6 @@
return $folders;
}
- /**
- * Getter for folder metadata
- *
- * @return array|bool Hash array with meta data for each folder, False on backend failure
- */
- protected function folder_meta()
- {
- if (!isset($this->folder_meta)) {
- // get folders activesync config
- $folderdata = $this->storage->get_metadata("*", self::ASYNC_KEY);
-
- if (!is_array($folderdata)) {
- return $this->folder_meta = false;
- }
-
- $this->folder_meta = [];
-
- foreach ($folderdata as $folder => $meta) {
- if (isset($meta[self::ASYNC_KEY])) {
- if ($metadata = $this->unserialize_metadata($meta[self::ASYNC_KEY])) {
- $this->folder_meta[$folder] = $metadata;
- }
- }
- }
- }
-
- return $this->folder_meta;
- }
-
/**
* Creates folder and subscribes to the device
*
@@ -299,9 +230,10 @@
{
$parent = null;
$name = rcube_charset::convert($name, kolab_sync::CHARSET, 'UTF7-IMAP');
+ $type = self::type_activesync2kolab($type);
if ($parentid) {
- $parent = $this->folder_id2name($parentid, $deviceid);
+ $parent = $this->folder_id2name($parentid, $deviceid, $type);
if ($parent === null) {
throw new Syncroton_Exception_Status_FolderCreate(Syncroton_Exception_Status_FolderCreate::PARENT_NOT_FOUND);
@@ -317,12 +249,10 @@
throw new Syncroton_Exception_Status_FolderCreate(Syncroton_Exception_Status_FolderCreate::FOLDER_EXISTS);
}
- $type = self::type_activesync2kolab($type);
$created = kolab_storage::folder_create($name, $type, true);
if ($created) {
- // Set ActiveSync subscription flag
- $this->folder_set($name, $deviceid, 1);
+ $this->subscriptions->folder_subscribe($deviceid, $name, 1, $type);
return $this->folder_id($name, $type);
}
@@ -341,17 +271,18 @@
*
* @param string $folderid Folder identifier
* @param string $deviceid Device identifier
+ * @param string $type Activesync model name (folder type)
* @param string $new_name New folder name (UTF8)
* @param ?string $parentid Folder parent identifier
*
* @return bool True on success, False on failure
*/
- public function folder_rename($folderid, $deviceid, $new_name, $parentid)
+ public function folder_rename($folderid, $deviceid, $type, $new_name, $parentid)
{
- $old_name = $this->folder_id2name($folderid, $deviceid);
+ $old_name = $this->folder_id2name($folderid, $deviceid, $type);
if ($parentid) {
- $parent = $this->folder_id2name($parentid, $deviceid);
+ $parent = $this->folder_id2name($parentid, $deviceid, $type);
}
$name = rcube_charset::convert($new_name, kolab_sync::CHARSET, 'UTF7-IMAP');
@@ -366,18 +297,22 @@
return true;
}
- $this->folder_meta = null;
-
// TODO: folder type change?
- $type = kolab_storage::folder_type($old_name);
-
// don't use kolab_storage for moving mail folders
- if (preg_match('/^mail/', $type)) {
- return $this->storage->rename_folder($old_name, $name);
+ if ($type == self::MODEL_EMAIL) {
+ $result = $this->storage->rename_folder($old_name, $name);
} else {
- return kolab_storage::folder_rename($old_name, $name);
+ $result = kolab_storage::folder_rename($old_name, $name);
}
+
+ if ($result) {
+ // Set ActiveSync subscription flag
+ // TODO: Use old subscription flag value
+ $this->subscriptions->folder_subscribe($deviceid, $name, 1, $type);
+ }
+
+ return $result;
}
/**
@@ -385,18 +320,16 @@
*
* @param string $folderid Folder identifier
* @param string $deviceid Device identifier
+ * @param string $type Activesync model name (folder type)
*
* @return bool True on success, False otherwise
*/
- public function folder_delete($folderid, $deviceid)
+ public function folder_delete($folderid, $deviceid, $type)
{
- $name = $this->folder_id2name($folderid, $deviceid);
- $type = kolab_storage::folder_type($name);
-
- unset($this->folder_meta[$name]);
+ $name = $this->folder_id2name($folderid, $deviceid, $type);
// don't use kolab_storage for deleting mail folders
- if (preg_match('/^mail/', $type)) {
+ if ($type == self::MODEL_EMAIL) {
return $this->storage->delete_folder($name);
}
@@ -408,30 +341,27 @@
*
* @param string $folderid Folder identifier
* @param string $deviceid Device identifier
+ * @param string $type Activesync model name (folder type)
* @param bool $recursive Apply to the folder and its subfolders
*
* @return bool True on success, False otherwise
*/
- public function folder_empty($folderid, $deviceid, $recursive = false)
+ public function folder_empty($folderid, $deviceid, $type, $recursive = false)
{
- $foldername = $this->folder_id2name($folderid, $deviceid);
+ $foldername = $this->folder_id2name($folderid, $deviceid, $type);
// Remove all entries
if (!$this->storage->clear_folder($foldername)) {
return false;
}
- // Remove subfolders
+ // Empty subfolders
if ($recursive) {
$delim = $this->storage->get_hierarchy_delimiter();
- $folderdata = $this->folder_meta();
-
- if (!is_array($folderdata)) {
- return false;
- }
+ $folders = $this->subscriptions->list_subscriptions($deviceid, $type);
- foreach ($folderdata as $subfolder => $meta) {
- if (!empty($meta['FOLDER'][$deviceid]['S']) && strpos((string) $subfolder, $foldername . $delim)) {
+ foreach (array_keys($folders) as $subfolder) {
+ if (strpos((string) $subfolder, $foldername . $delim)) {
if (!$this->storage->clear_folder((string) $subfolder)) {
return false;
}
@@ -442,233 +372,6 @@
return true;
}
- /**
- * Sets ActiveSync subscription flag on a folder
- *
- * @param string $name Folder name (UTF7-IMAP)
- * @param string $deviceid Device identifier
- * @param int $flag Flag value (0|1|2)
- *
- * @return bool True on success, False on failure
- */
- protected function folder_set($name, $deviceid, $flag)
- {
- if (empty($deviceid)) {
- return false;
- }
-
- // get folders activesync config
- $metadata = $this->folder_meta();
-
- if (!is_array($metadata)) {
- return false;
- }
-
- $metadata = $metadata[$name] ?? [];
-
- if ($flag) {
- if (empty($metadata)) {
- $metadata = [];
- }
-
- if (empty($metadata['FOLDER'])) {
- $metadata['FOLDER'] = [];
- }
-
- if (empty($metadata['FOLDER'][$deviceid])) {
- $metadata['FOLDER'][$deviceid] = [];
- }
-
- // Z-Push uses:
- // 1 - synchronize, no alarms
- // 2 - synchronize with alarms
- $metadata['FOLDER'][$deviceid]['S'] = $flag;
- } else {
- unset($metadata['FOLDER'][$deviceid]['S']);
-
- if (empty($metadata['FOLDER'][$deviceid])) {
- unset($metadata['FOLDER'][$deviceid]);
- }
-
- if (empty($metadata['FOLDER'])) {
- unset($metadata['FOLDER']);
- }
-
- if (empty($metadata)) {
- $metadata = null;
- }
- }
-
- // Return if nothing's been changed
- if (!self::data_array_diff($this->folder_meta[$name] ?? null, $metadata)) {
- return true;
- }
-
- $this->folder_meta[$name] = $metadata;
-
- return $this->storage->set_metadata($name, [self::ASYNC_KEY => $this->serialize_metadata($metadata)]);
- }
-
- /**
- * Returns device metadata
- *
- * @param string $id Device ID
- *
- * @return array|null Device metadata
- */
- public function device_get($id)
- {
- $devices_list = $this->devices_list();
- return $devices_list[$id] ?? null;
- }
-
- /**
- * Registers new device on server
- *
- * @param array $device Device data
- * @param string $id Device ID
- *
- * @return bool True on success, False on failure
- */
- public function device_create($device, $id)
- {
- // 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
- // ALIAS: user-friendly device name
-
- // Syncroton (kolab_sync_backend_device) uses
- // ID: internal identifier in syncroton database
- // TYPE: device type string
- // ALIAS: user-friendly device name
-
- $metadata = $this->root_meta;
- $metadata['DEVICE'][$id] = $device;
- $metadata = [self::ASYNC_KEY => $this->serialize_metadata($metadata)];
-
- $result = $this->storage->set_metadata(self::ROOT_MAILBOX, $metadata);
-
- if ($result) {
- // Update local cache
- $this->root_meta['DEVICE'][$id] = $device;
-
- // subscribe default set of folders
- $this->device_init_subscriptions($id);
- }
-
- 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();
- $old_device = $devices_list[$id];
-
- if (!$old_device) {
- return false;
- }
-
- // Do nothing if nothing is changed
- if (!self::data_array_diff($old_device, $device)) {
- return true;
- }
-
- $device = array_merge($old_device, $device);
-
- $metadata = $this->root_meta;
- $metadata['DEVICE'][$id] = $device;
- $metadata = [self::ASYNC_KEY => $this->serialize_metadata($metadata)];
-
- $result = $this->storage->set_metadata(self::ROOT_MAILBOX, $metadata);
-
- if ($result) {
- // Update local cache
- $this->root_meta['DEVICE'][$id] = $device;
- }
-
- return $result;
- }
-
- /**
- * Device delete.
- *
- * @param string $id Device ID
- *
- * @return bool True on success, False on failure
- */
- public function device_delete($id)
- {
- $device = $this->device_get($id);
-
- if (!$device) {
- return false;
- }
-
- unset($this->root_meta['DEVICE'][$id], $this->root_meta['FOLDER'][$id]);
-
- if (empty($this->root_meta['DEVICE'])) {
- unset($this->root_meta['DEVICE']);
- }
- if (empty($this->root_meta['FOLDER'])) {
- unset($this->root_meta['FOLDER']);
- }
-
- $metadata = $this->serialize_metadata($this->root_meta);
- $metadata = [self::ASYNC_KEY => $metadata];
-
- // update meta data
- $result = $this->storage->set_metadata(self::ROOT_MAILBOX, $metadata);
-
- if ($result) {
- // remove device annotation for every folder
- foreach ($this->folder_meta() as $folder => $meta) {
- // skip root folder (already handled above)
- if ($folder == self::ROOT_MAILBOX) {
- continue;
- }
-
- if (!empty($meta['FOLDER']) && isset($meta['FOLDER'][$id])) {
- unset($meta['FOLDER'][$id]);
-
- if (empty($meta['FOLDER'])) {
- unset($this->folder_meta[$folder]['FOLDER']);
- unset($meta['FOLDER']);
- }
- if (empty($meta)) {
- unset($this->folder_meta[$folder]);
- $meta = null;
- }
-
- $metadata = [self::ASYNC_KEY => $this->serialize_metadata($meta)];
- $res = $this->storage->set_metadata($folder, $metadata);
-
- if ($res && $meta) {
- $this->folder_meta[$folder] = $meta;
- }
- }
- }
- }
-
- return $result;
- }
-
/**
* Creates an item in a folder.
*
@@ -683,7 +386,7 @@
public function createItem($folderid, $deviceid, $type, $data, $params = [])
{
if ($type == self::MODEL_EMAIL) {
- $foldername = $this->folder_id2name($folderid, $deviceid);
+ $foldername = $this->folder_id2name($folderid, $deviceid, $type);
$uid = $this->storage->save_message($foldername, $data, '', false, $params['flags'] ?? []);
@@ -733,7 +436,7 @@
public function deleteItem($folderid, $deviceid, $type, $uid, $moveToTrash = false)
{
if ($type == self::MODEL_EMAIL) {
- $foldername = $this->folder_id2name($folderid, $deviceid);
+ $foldername = $this->folder_id2name($folderid, $deviceid, $type);
$trash = kolab_sync::get_instance()->config->get('trash_mbox');
// move message to the Trash folder
@@ -786,7 +489,7 @@
public function updateItem($folderid, $deviceid, $type, $uid, $data, $params = [])
{
if ($type == self::MODEL_EMAIL) {
- $foldername = $this->folder_id2name($folderid, $deviceid);
+ $foldername = $this->folder_id2name($folderid, $deviceid, $type);
// Note: We do not support a message body update, as it's not needed
@@ -905,7 +608,7 @@
return $this->folders[$unique_key];
}
- $foldername = $this->folder_id2name($folderid, $deviceid);
+ $foldername = $this->folder_id2name($folderid, $deviceid, $type);
return $this->folders[$unique_key] = kolab_storage::get_folder($foldername, $type);
}
@@ -921,17 +624,12 @@
*/
public function getFolderConfig($folderid, $deviceid, $type)
{
- $foldername = $this->folder_id2name($folderid, $deviceid);
+ $foldername = $this->folder_id2name($folderid, $deviceid, $type);
- $metadata = $this->folder_meta();
- $config = [];
-
- if (!empty($metadata[$foldername]['FOLDER'][$deviceid])) {
- $config = $metadata[$foldername]['FOLDER'][$deviceid];
- }
+ $subs = $this->subscriptions->folder_subscriptions($foldername, $type);
return [
- 'ALARMS' => ($config['S'] ?? 0) == 2,
+ 'ALARMS' => ($subs[$deviceid] ?? 0) == 2,
];
}
@@ -948,7 +646,7 @@
public function getItem($folderid, $deviceid, $type, $uid)
{
if ($type == self::MODEL_EMAIL) {
- $foldername = $this->folder_id2name($folderid, $deviceid);
+ $foldername = $this->folder_id2name($folderid, $deviceid, $type);
$message = new rcube_message($uid, $foldername);
if (!empty($message->headers)) {
@@ -1027,8 +725,8 @@
public function moveItem($srcFolderId, $deviceid, $type, $uid, $dstFolderId)
{
if ($type === self::MODEL_EMAIL) {
- $src_name = $this->folder_id2name($srcFolderId, $deviceid);
- $dst_name = $this->folder_id2name($dstFolderId, $deviceid);
+ $src_name = $this->folder_id2name($srcFolderId, $deviceid, $type);
+ $dst_name = $this->folder_id2name($dstFolderId, $deviceid, $type);
if ($dst_name === null) {
throw new Syncroton_Exception_Status_MoveItems(Syncroton_Exception_Status_MoveItems::INVALID_DESTINATION);
@@ -1174,7 +872,7 @@
$result = $result_type == kolab_sync_data::RESULT_COUNT ? 0 : [];
- $foldername = $this->folder_id2name($folderid, $deviceid);
+ $foldername = $this->folder_id2name($folderid, $deviceid, $type);
if ($foldername === null) {
return $result;
@@ -1280,10 +978,11 @@
*
* @param string $folderid Folder identifier
* @param string $deviceid Device identifier
+ * @param string $type Activesync model name (folder type)
*
* @return string|null Extra data (JSON-encoded)
*/
- public function getExtraData($folderid, $deviceid)
+ public function getExtraData($folderid, $deviceid, $type)
{
//We explicitly return a cached value that was used during the search.
//Otherwise we'd risk storing a higher modseq value and missing an update.
@@ -1292,7 +991,7 @@
}
//If we didn't fetch modseq in the first place we have to fetch it now.
- $foldername = $this->folder_id2name($folderid, $deviceid);
+ $foldername = $this->folder_id2name($folderid, $deviceid, $type);
if ($foldername !== null) {
$folder_data = $this->storage->folder_data($foldername);
if (!empty($folder_data['HIGHESTMODSEQ'])) {
@@ -1618,12 +1317,19 @@
}
/**
- * Subscribe default set of folders on device registration
+ * Subscribes to a default set of folder on a new device registration
+ *
+ * @param string $deviceid Device ID
*/
- protected function device_init_subscriptions($deviceid)
+ public function device_init($deviceid)
{
- // INBOX always exists
- $this->folder_set('INBOX', $deviceid, 1);
+ $subscribed = [
+ 'mail' => ['INBOX' => 1], // INBOX always exists
+ 'event' => [],
+ 'contact' => [],
+ 'task' => [],
+ 'note' => [],
+ ];
$supported_types = [
'mail.drafts',
@@ -1660,99 +1366,76 @@
// only personal folders
if ($this->storage->folder_namespace($folder) == 'personal') {
$flag = preg_match('/^(event|task)/', $type) ? 2 : 1;
- $this->folder_set($folder, $deviceid, $flag);
+ [$type, ] = explode('.', $type);
+ $subscribed[$type][$folder] = $flag;
$folders[] = $folder;
}
}
}
- // we're in default mode, exit
- if (!$mode) {
- return;
- }
-
- // below we support additionally all mail folders
- $supported_types[] = 'mail';
- $supported_types[] = 'mail.junkemail';
+ if ($mode) {
+ // below we support additionally all mail folders
+ $supported_types[] = 'mail';
+ $supported_types[] = 'mail.junkemail';
- // get configured special folders
- $special_folders = [];
- $map = [
- 'drafts' => 'mail.drafts',
- 'junk' => 'mail.junkemail',
- 'sent' => 'mail.sentitems',
- 'trash' => 'mail.wastebasket',
- ];
+ // get configured special folders
+ $special_folders = [];
+ $map = [
+ 'drafts' => 'mail.drafts',
+ 'junk' => 'mail.junkemail',
+ 'sent' => 'mail.sentitems',
+ 'trash' => 'mail.wastebasket',
+ ];
- foreach ($map as $folder => $type) {
- if ($folder = $config->get($folder . '_mbox')) {
- $special_folders[$folder] = $type;
+ foreach ($map as $folder => $type) {
+ if ($folder = $config->get($folder . '_mbox')) {
+ $special_folders[$folder] = $type;
+ }
}
- }
- // get folders list(s)
- if (($mode & self::INIT_ALL_PERSONAL) || ($mode & self::INIT_ALL_OTHER) || ($mode & self::INIT_ALL_SHARED)) {
- $all_folders = $this->storage->list_folders();
- if (($mode & self::INIT_SUB_PERSONAL) || ($mode & self::INIT_SUB_OTHER) || ($mode & self::INIT_SUB_SHARED)) {
- $subscribed_folders = $this->storage->list_folders_subscribed();
+ // get folders list(s)
+ if (($mode & self::INIT_ALL_PERSONAL) || ($mode & self::INIT_ALL_OTHER) || ($mode & self::INIT_ALL_SHARED)) {
+ $all_folders = $this->storage->list_folders();
+ if (($mode & self::INIT_SUB_PERSONAL) || ($mode & self::INIT_SUB_OTHER) || ($mode & self::INIT_SUB_SHARED)) {
+ $subscribed_folders = $this->storage->list_folders_subscribed();
+ }
+ } else {
+ $all_folders = $this->storage->list_folders_subscribed();
}
- } else {
- $all_folders = $this->storage->list_folders_subscribed();
- }
- foreach ($all_folders as $folder) {
- // folder already subscribed
- if (in_array($folder, $folders)) {
- continue;
- }
+ foreach ($all_folders as $folder) {
+ // folder already subscribed
+ if (in_array($folder, $folders)) {
+ continue;
+ }
- $type = ($foldertypes[$folder] ?? null) ?: 'mail';
- if ($type == 'mail' && isset($special_folders[$folder])) {
- $type = $special_folders[$folder];
- }
+ $type = ($foldertypes[$folder] ?? null) ?: 'mail';
+ if ($type == 'mail' && isset($special_folders[$folder])) {
+ $type = $special_folders[$folder];
+ }
- if (!in_array($type, $supported_types)) {
- continue;
- }
+ if (!in_array($type, $supported_types)) {
+ continue;
+ }
- $ns = strtoupper($this->storage->folder_namespace($folder));
+ $ns = strtoupper($this->storage->folder_namespace($folder));
- // subscribe the folder according to configured mode
- // and folder namespace/subscription status
- if (($mode & constant("self::INIT_ALL_{$ns}"))
- || (($mode & constant("self::INIT_SUB_{$ns}"))
- && (!isset($subscribed_folders) || in_array($folder, $subscribed_folders)))
- ) {
- $flag = preg_match('/^(event|task)/', $type) ? 2 : 1;
- $this->folder_set($folder, $deviceid, $flag);
+ // subscribe the folder according to configured mode
+ // and folder namespace/subscription status
+ if (($mode & constant("self::INIT_ALL_{$ns}"))
+ || (($mode & constant("self::INIT_SUB_{$ns}"))
+ && (!isset($subscribed_folders) || in_array($folder, $subscribed_folders)))
+ ) {
+ $flag = preg_match('/^(event|task)/', $type) ? 2 : 1;
+ [$type, ] = explode('.', $type);
+ $subscribed[$type][$folder] = $flag;
+ }
}
}
- }
-
- /**
- * Helper method to decode saved IMAP metadata
- */
- protected function unserialize_metadata($str)
- {
- if (!empty($str)) {
- $data = json_decode($str, true);
- return $data;
- }
-
- return null;
- }
- /**
- * Helper method to encode IMAP metadata for saving
- */
- protected function serialize_metadata($data)
- {
- if (!empty($data) && is_array($data)) {
- $data = json_encode($data);
- return $data;
+ foreach ($subscribed as $type => $list) {
+ $this->subscriptions->set_subscriptions($deviceid, $type, $list);
}
-
- return null;
}
/**
@@ -1837,19 +1520,6 @@
return $this->folder_uids[$name];
}
- /*
- @TODO: For now uniqueid annotation doesn't work, we will create UIDs by ourselves.
- There's one inconvenience of this solution: folder name/type change
- would be handled in ActiveSync as delete + create.
-
- // get folders unique identifier
- $folderdata = $this->storage->get_metadata($name, self::UID_KEY);
-
- if ($folderdata && !empty($folderdata[$name])) {
- $uid = $folderdata[$name][self::UID_KEY];
- return $this->folder_uids[$name] = $uid;
- }
- */
if (strcasecmp($name, 'INBOX') === 0) {
// INBOX is always inbox, prevent from issues related with a change of
// folder type annotation (it can be initially unset).
@@ -1871,79 +1541,35 @@
return $this->folder_uids[$name] = $uid;
}
- private function is_protected($folder)
- {
- if ($this->protectedFolders === null) {
- $this->protectedFolders = rcube::get_instance()->config->get('activesync_force_subscription_state', []);
- }
- return array_key_exists($folder, $this->protectedFolders);
- }
-
- protected function is_subscribed($deviceid, $folder, $meta)
- {
- if ($this->is_protected($folder)) {
- return true;
- }
- if (empty($meta['FOLDER']) || empty($meta['FOLDER'][$deviceid])
- || empty($meta['FOLDER'][$deviceid]['S'])
- ) {
- return false;
- }
- return true;
- }
-
/**
* Returns IMAP folder name
*
- * @param string $id Folder identifier
- * @param string $deviceid Device dentifier
+ * @param string $id Folder identifier
+ * @param string $deviceid Device dentifier
+ * @param string $type Folder type
*
* @return string|null Folder name (UTF7-IMAP)
*/
- public function folder_id2name($id, $deviceid)
+ public function folder_id2name($id, $deviceid, $type)
{
+ // TODO: This method should become protected
+
// check in cache first
if (!empty($this->folder_uids)) {
if (($name = array_search($id, $this->folder_uids)) !== false) {
return $name;
}
}
- /*
- @TODO: see folder_id()
-
- // get folders unique identifier
- $folderdata = $this->storage->get_metadata('*', self::UID_KEY);
-
- foreach ((array)$folderdata as $folder => $data) {
- if (!empty($data[self::UID_KEY])) {
- $uid = $data[self::UID_KEY];
- $this->folder_uids[$folder] = $uid;
- if ($uid == $id) {
- $name = $folder;
- }
- }
- }
- */
- // get all folders of specified type
- $folderdata = $this->folder_meta();
-
- if (!is_array($folderdata) || empty($id)) {
- return null;
- }
$name = null;
- // check if folders are "subscribed" for activesync
- foreach ($folderdata as $folder => $meta) {
- if (!$this->is_subscribed($deviceid, $folder, $meta)) {
- continue;
- }
-
- if ($uid = $this->folder_id($folder)) {
- $this->folder_uids[$folder] = $uid;
- }
+ if (strpos($type, '.')) {
+ [$type, ] = explode('.', $type);
+ }
- if ($uid === $id) {
+ // Get the uids of all folders subscribed for activesync
+ foreach ($this->subscriptions->list_subscriptions($deviceid, $type) as $folder => $props) {
+ if ($this->folder_id($folder) === $id) {
$name = $folder;
}
}
@@ -2065,34 +1691,4 @@
{
return kolab_storage::$last_error;
}
-
- /**
- * Compares two arrays
- *
- * @param array $array1
- * @param array $array2
- *
- * @return bool True if arrays differs, False otherwise
- */
- protected static function data_array_diff($array1, $array2)
- {
- if (!is_array($array1) || !is_array($array2)) {
- return $array1 != $array2;
- }
-
- if (count($array1) != count($array2)) {
- return true;
- }
-
- foreach ($array1 as $key => $val) {
- if (!array_key_exists($key, $array2)) {
- return true;
- }
- if ($val !== $array2[$key]) {
- return true;
- }
- }
-
- return false;
- }
}
diff --git a/lib/kolab_sync_storage_kolab4.php b/lib/kolab_sync_storage_kolab4.php
--- a/lib/kolab_sync_storage_kolab4.php
+++ b/lib/kolab_sync_storage_kolab4.php
@@ -79,6 +79,9 @@
// Disable paging
$this->storage->set_pagesize(999999);
+
+ // Folders subscriptions engine
+ $this->subscriptions = new kolab_subscriptions($url);
}
/**
@@ -96,12 +99,6 @@
// get mail folders subscribed for sync
if ($type === self::MODEL_EMAIL) {
- $folderdata = $this->folder_meta();
-
- if (!is_array($folderdata)) {
- return false;
- }
-
$special_folders = $this->storage->get_special_folders(true);
$type_map = [
'drafts' => 3,
@@ -110,11 +107,7 @@
];
// Get the folders "subscribed" for activesync
- foreach ($folderdata as $folder => $meta) {
- if (!$this->is_subscribed($deviceid, $folder, $meta)) {
- continue;
- }
-
+ foreach ($this->subscriptions->list_subscriptions($deviceid, self::MODEL_EMAIL) as $folder => $meta) {
// Force numeric folder name to be a string (T1283)
$folder = (string) $folder;
@@ -138,10 +131,10 @@
}
}
- // TODO: For now all DAV folders are subscribed
-
if (empty($list)) {
- foreach ($this->davStorage->get_folders($type) as $folder) {
+ foreach ($this->subscriptions->list_subscriptions($deviceid, $type) as $folder) {
+ /** @var kolab_storage_dav_folder $folder */
+ $folder = $folder[2];
$folder_data = $this->folder_data($folder, $type);
$list[$folder_data['serverId']] = $folder_data;
@@ -179,7 +172,7 @@
$name = rcube_charset::convert($name, kolab_sync::CHARSET, 'UTF7-IMAP');
if ($parentid) {
- $parent = $this->folder_id2name($parentid, $deviceid);
+ $parent = $this->folder_id2name($parentid, $deviceid, self::MODEL_EMAIL);
if ($parent === null) {
throw new Syncroton_Exception_Status_FolderCreate(Syncroton_Exception_Status_FolderCreate::PARENT_NOT_FOUND);
@@ -201,7 +194,7 @@
if ($created) {
// Set ActiveSync subscription flag
- $this->folder_set($name, $deviceid, 1);
+ $this->subscriptions->folder_subscribe($deviceid, $name, 1, self::MODEL_EMAIL);
return $this->folder_id($name, 'mail');
}
@@ -229,6 +222,10 @@
$props = ['name' => $name, 'type' => $type];
if ($id = $this->davStorage->folder_update($props)) {
+ // Set ActiveSync subscription flag
+ $this->subscriptions->folder_subscribe($deviceid, $this->davStorage->new_location, 1, $type);
+ $this->folders = [];
+
return "DAV:{$type}:{$id}";
}
@@ -243,12 +240,13 @@
*
* @param string $folderid Folder identifier
* @param string $deviceid Device identifier
+ * @param string $type Activesync model name (folder type)
* @param string $new_name New folder name (UTF8)
* @param ?string $parentid Folder parent identifier
*
* @return bool True on success, False on failure
*/
- public function folder_rename($folderid, $deviceid, $new_name, $parentid)
+ public function folder_rename($folderid, $deviceid, $type, $new_name, $parentid)
{
// DAV folder
if (strpos($folderid, 'DAV:') === 0) {
@@ -265,10 +263,10 @@
}
// Mail folder
- $old_name = $this->folder_id2name($folderid, $deviceid);
+ $old_name = $this->folder_id2name($folderid, $deviceid, $type);
if ($parentid) {
- $parent = $this->folder_id2name($parentid, $deviceid);
+ $parent = $this->folder_id2name($parentid, $deviceid, $type);
}
$name = rcube_charset::convert($new_name, kolab_sync::CHARSET, 'UTF7-IMAP');
@@ -282,9 +280,14 @@
return true;
}
- $this->folder_meta = null;
+ $result = $this->storage->rename_folder($old_name, $name);
+
+ if ($result) {
+ // Set ActiveSync subscription flag
+ $this->subscriptions->folder_subscribe($deviceid, $name, 1, self::MODEL_EMAIL);
+ }
- return $this->storage->rename_folder($old_name, $name);
+ return $result;
}
/**
@@ -292,10 +295,11 @@
*
* @param string $folderid Folder identifier
* @param string $deviceid Device identifier
+ * @param string $type Activesync model name (folder type)
*
* @return bool True on success, False otherwise
*/
- public function folder_delete($folderid, $deviceid)
+ public function folder_delete($folderid, $deviceid, $type)
{
// DAV folder
if (strpos($folderid, 'DAV:') === 0) {
@@ -305,9 +309,7 @@
}
// Mail folder
- $name = $this->folder_id2name($folderid, $deviceid);
-
- unset($this->folder_meta[$name]);
+ $name = $this->folder_id2name($folderid, $deviceid, $type);
return $this->storage->delete_folder($name);
}
@@ -317,11 +319,12 @@
*
* @param string $folderid Folder identifier
* @param string $deviceid Device identifier
+ * @param string $type Activesync model name (folder type)
* @param bool $recursive Apply to the folder and its subfolders
*
* @return bool True on success, False otherwise
*/
- public function folder_empty($folderid, $deviceid, $recursive = false)
+ public function folder_empty($folderid, $deviceid, $type, $recursive = false)
{
// DAV folder
if (strpos($folderid, 'DAV:') === 0) {
@@ -337,7 +340,7 @@
}
// Mail folder
- return parent::folder_empty($folderid, $deviceid, $recursive);
+ return parent::folder_empty($folderid, $deviceid, $type, $recursive);
}
/**
@@ -388,20 +391,7 @@
if (isset($this->folder_uids[$name])) {
return $this->folder_uids[$name];
}
- /*
- @TODO: For now uniqueid annotation doesn't work, we will create UIDs by ourselves.
- There's one inconvenience of this solution: folder name/type change
- would be handled in ActiveSync as delete + create.
- @TODO: Consider using MAILBOXID (RFC8474) that Cyrus v3 supports
- // get folders unique identifier
- $folderdata = $this->storage->get_metadata($name, self::UID_KEY);
-
- if ($folderdata && !empty($folderdata[$name])) {
- $uid = $folderdata[$name][self::UID_KEY];
- return $this->folder_uids[$name] = $uid;
- }
- */
if (strcasecmp($name, 'INBOX') === 0) {
// INBOX is always inbox, prevent from issues related with a change of
// folder type annotation (it can be initially unset).
@@ -418,48 +408,19 @@
/**
* Returns IMAP folder name
*
- * @param string $id Folder identifier
- * @param string $deviceid Device dentifier
+ * @param string $id Folder identifier
+ * @param string $deviceid Device dentifier
+ * @param string $type Folder type
*
* @return null|string Folder name (UTF7-IMAP)
*/
- public function folder_id2name($id, $deviceid)
+ public function folder_id2name($id, $deviceid, $type)
{
- // TODO: This method should become protected and be used for mail folders only
if (strpos($id, 'DAV:') === 0) {
throw new Exception("Unsupported folder_id2name() call on a DAV folder");
}
- // check in cache first
- if (!empty($this->folder_uids)) {
- if (($name = array_search($id, $this->folder_uids)) !== false) {
- return $name;
- }
- }
-
- // get all folders of specified type
- $folderdata = $this->folder_meta();
-
- if (!is_array($folderdata) || empty($id)) {
- return null;
- }
-
- // check if folders are "subscribed" for activesync
- foreach ($folderdata as $folder => $meta) {
- if (!$this->is_subscribed($deviceid, $folder, $meta)) {
- continue;
- }
-
- if ($uid = $this->folder_id($folder, 'mail')) {
- $this->folder_uids[$folder] = $uid;
- }
-
- if ($uid === $id) {
- $name = $folder;
- }
- }
-
- return $name ?? null;
+ return parent::folder_id2name($id, $deviceid, $type);
}
/**
@@ -498,7 +459,7 @@
* @param string $deviceid Device identifier
* @param string $type Activesync model name (folder type)
*
- * @return ?kolab_storage_folder
+ * @return ?kolab_storage_dav_folder
*/
public function getFolder($folderid, $deviceid, $type)
{
@@ -528,10 +489,15 @@
*/
public function getFolderConfig($folderid, $deviceid, $type)
{
- // TODO: Get "alarms" from the DAV folder props, or implement
- // a storage for folder properties
+ $alarms = 0;
+
+ if ($folder = $this->getFolder($folderid, $deviceid, $type)) {
+ $subs = $this->subscriptions->folder_subscriptions($folder->href, $type);
+ $alarms = $subs[$deviceid] ?? 0;
+ }
+
return [
- 'ALARMS' => true,
+ 'ALARMS' => $alarms == 2,
];
}
@@ -545,9 +511,11 @@
}
/**
- * Subscribe default set of folders on device registration
+ * Subscribes to a default set of folder on a new device registration
+ *
+ * @param string $deviceid Device ID
*/
- protected function device_init_subscriptions($deviceid)
+ public function device_init($deviceid)
{
$config = rcube::get_instance()->config;
$mode = (int) $config->get('activesync_init_subscriptions');
@@ -571,6 +539,13 @@
$all_folders = $this->storage->list_folders_subscribed();
}
+ $subscribe = [
+ 'mail' => ['INBOX' => 1],
+ 'contact' => [],
+ 'event' => [],
+ 'task' => [],
+ ];
+
foreach ($all_folders as $folder) {
$ns = strtoupper($this->storage->folder_namespace($folder));
@@ -580,20 +555,29 @@
|| ($mode & constant("self::INIT_ALL_{$ns}"))
|| (($mode & constant("self::INIT_SUB_{$ns}")) && ($subscribed_folders === null || in_array($folder, $subscribed_folders)))
) {
- $this->folder_set($folder, $deviceid, 1);
+ $subscribe['mail'][$folder] = 1;
}
}
- // TODO: Subscribe personal DAV folders, for now we assume all are subscribed
- // TODO: Subscribe shared DAV folders
+ foreach ($subscribe as $type => $list) {
+ if ($type != 'mail') {
+ foreach ($this->subscriptions->list_folders($type) as $folder) {
+ // TODO: Subscribe personal DAV folders, for now we assume all are subscribed
+ $list[$folder[0]] = ($type == 'event' || $type == 'task') ? 2 : 1;
+ }
+ }
+
+ $this->subscriptions->set_subscriptions($deviceid, $type, $list);
+ }
}
- public function getExtraData($folderid, $deviceid)
+ public function getExtraData($folderid, $deviceid, $type)
{
if (strpos($folderid, 'DAV:') === 0) {
return null;
}
- return parent::getExtraData($folderid, $deviceid);
+
+ return parent::getExtraData($folderid, $deviceid, $type);
}
/**
diff --git a/tests/Sync/FoldersTest.php b/tests/Sync/FoldersTest.php
--- a/tests/Sync/FoldersTest.php
+++ b/tests/Sync/FoldersTest.php
@@ -138,6 +138,7 @@
$dom = $this->fromWbxml($response->getBody());
$xpath = $this->xpath($dom);
$this->assertSame('1', $xpath->query("//ns:FolderSync/ns:Status")->item(0)->nodeValue);
+ $this->assertSame('1', $xpath->query("//ns:FolderSync/ns:SyncKey")->item(0)->nodeValue);
//Now change something
$this->createTestFolder("NewFolder", "mail");
diff --git a/tests/Sync/PingTest.php b/tests/Sync/PingTest.php
--- a/tests/Sync/PingTest.php
+++ b/tests/Sync/PingTest.php
@@ -204,7 +204,7 @@
$response = $this->request($request, 'FolderSync');
$this->assertEquals(200, $response->getStatusCode());
- $this->setSubscriptionState("NewFolder", null);
+ $this->setSubscriptionState("NewFolder", 'mail', null);
$request = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/">
@@ -265,7 +265,10 @@
$deviceId = self::$deviceId;
$username = self::$username;
$password = self::$password;
- exec("php setmetadata.php $deviceId NewFolder $username $password > /dev/null 2>&1 &");
+ $script = TESTS_DIR . 'bootstrap.php';
+ exec("php {$script} subscribe --folder=NewFolder --type=mail --delay=5"
+ . " --device={$deviceId} --user={$username} --password={$password} &", $output);
+ $this->assertSame([], $output);
$request = <<<EOF
<?xml version="1.0" encoding="utf-8"?>
diff --git a/tests/SyncTestCase.php b/tests/SyncTestCase.php
--- a/tests/SyncTestCase.php
+++ b/tests/SyncTestCase.php
@@ -46,12 +46,8 @@
self::$deviceId = 'test' . str_replace('.', '', microtime(true));
$db->query('DELETE FROM syncroton_device');
- $db->query('DELETE FROM syncroton_synckey');
- $db->query('DELETE FROM syncroton_folder');
$db->query('DELETE FROM syncroton_data');
$db->query('DELETE FROM syncroton_data_folder');
- $db->query('DELETE FROM syncroton_content');
- $db->query('DELETE FROM syncroton_relations_state');
self::$client = new \GuzzleHttp\Client([
'http_errors' => false,
@@ -76,19 +72,16 @@
{
if (self::$deviceId) {
$sync = \kolab_sync::get_instance();
-
+ /*
if (self::$authenticated || $sync->authenticate(self::$username, self::$password)) {
$sync->password = self::$password;
-
- $storage = $sync->storage();
- $storage->device_delete(self::$deviceId);
}
+ */
$db = $sync->get_dbh();
$db->query('DELETE FROM syncroton_device');
- $db->query('DELETE FROM syncroton_synckey');
- $db->query('DELETE FROM syncroton_folder');
- $db->query('DELETE FROM syncroton_relations_state');
+ $db->query('DELETE FROM syncroton_data');
+ $db->query('DELETE FROM syncroton_data_folder');
}
}
@@ -230,27 +223,67 @@
if ($type == 'mail' || $this->isStorageDriver('kolab')) {
$imap = $this->getImapStorage();
$imap->create_folder($name, true);
- if ($type != "mail") {
+ if ($type != 'mail' && $this->isStorageDriver('kolab')) {
$imap->set_metadata($name, ['/private/vendor/kolab/folder-type' => $type]);
}
- $this->setSubscriptionState($name, $subscriptionState);
+ $this->setSubscriptionState($name, $type, $subscriptionState);
}
}
/**
- * Subscribe test folder
+ * Subscribe a test folder
*/
- protected function setSubscriptionState($name, $subscriptionState)
+ public static function setSubscriptionState($name, $type, $subscriptionState, $deviceid = null)
{
- $metadata = null;
+ $sync = \kolab_sync::get_instance();
+ $db = $sync->get_dbh();
+ $query = $db->query("SELECT `id` FROM `syncroton_device` WHERE `deviceid` = ?", $deviceid ?: self::$deviceId);
+ $arr = $db->fetch_array($query);
+ $device_id = $arr[0] ?? null;
+
+ if (!$device_id) {
+ throw new \Exception('Device ID not found');
+ }
+
+ $query = $db->query(
+ "SELECT `data` FROM `syncroton_subscriptions` WHERE `device_id` = ? AND `type` = ?",
+ $device_id,
+ $type
+ );
+
+ if ($record = $db->fetch_assoc($query)) {
+ $data = json_decode($record['data'], true);
+ } else {
+ if (!$subscriptionState) {
+ return;
+ }
+ }
+
if ($subscriptionState) {
- $metadata = [];
- $metadata['FOLDER'] = [];
- $metadata['FOLDER'][self::$deviceId] = [];
- $metadata['FOLDER'][self::$deviceId]['S'] = $subscriptionState;
- $metadata = json_encode($metadata);
+ $data[$name] = (int) $subscriptionState;
+ } elseif (!isset($data[$name])) {
+ return;
+ } else {
+ unset($data[$name]);
+ }
+
+ $data = json_encode($data);
+
+ if ($record) {
+ $db->query(
+ 'UPDATE syncroton_subscriptions SET `data` = ? WHERE `device_id` = ? AND `type` = ?',
+ $data,
+ $device_id,
+ $type
+ );
+ } else {
+ $db->query(
+ 'INSERT INTO `syncroton_subscriptions` (`data`, `device_id`, `type`) VALUES (?, ?, ?)',
+ $data,
+ $device_id,
+ $type
+ );
}
- $this->getImapStorage()->set_metadata($name, ['/private/vendor/kolab/activesync' => $metadata]);
}
/**
@@ -394,11 +427,6 @@
{
$sync = \kolab_sync::get_instance();
- if (self::$deviceId) {
- $storage = $sync->storage();
- $storage->device_delete(self::$deviceId);
- }
-
$db = $sync->get_dbh();
$db->query('DELETE FROM syncroton_device');
}
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
--- a/tests/bootstrap.php
+++ b/tests/bootstrap.php
@@ -13,3 +13,16 @@
require_once(TESTS_DIR . '/SyncTestCase.php');
rcube::get_instance()->config->set('devel_mode', false);
+
+// CLI (background) commands for Ping tests
+if (!empty($argv) && $argv[1] == 'subscribe') {
+ $args = rcube_utils::get_opt();
+
+ if (!empty($args['delay'])) {
+ sleep($args['delay']);
+ }
+
+ kolab_sync::get_instance()->authenticate($args['user'], $args['password']);
+
+ Tests\SyncTestCase::setSubscriptionState($args['folder'], $args['type'], 1, $args['device']);
+}
diff --git a/tests/setmetadata.php b/tests/setmetadata.php
deleted file mode 100644
--- a/tests/setmetadata.php
+++ /dev/null
@@ -1,21 +0,0 @@
-<?php
-
-define('TESTS_DIR', dirname(__FILE__) . '/');
-require_once(TESTS_DIR . '/../lib/init.php');
-
-sleep(5);
-
-$deviceid=$argv[1] ;
-$folderName=$argv[2] ;
-
-$metadata = [];
-$metadata['FOLDER'] = [];
-$metadata['FOLDER'][$deviceid] = [];
-$metadata['FOLDER'][$deviceid]['S'] = '1';
-$metadata = json_encode($metadata);
-\kolab_sync::get_instance()->authenticate($argv[3], $argv[4]);
-if (\kolab_sync::get_instance()->get_storage()->set_metadata($folderName, ['/private/vendor/kolab/activesync' => $metadata])) {
- print("Set metdata on $deviceid on $folderName");
-} else {
- print("Failed to set metdata on $deviceid on $folderName");
-}

File Metadata

Mime Type
text/plain
Expires
Thu, Apr 2, 9:45 PM (1 d, 1 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18821036
Default Alt Text
D4686.1775166300.diff (60 KB)

Event Timeline