Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117410922
D4686.1774829069.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
51 KB
Referenced Files
None
Subscribers
None
D4686.1774829069.diff
View Options
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
@@ -107,12 +107,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', '2024031100');
+INSERT INTO `system` (`name`, `value`) VALUES ('syncroton-version', '2024040800');
diff --git a/docs/SQL/mysql/2024040800.sql b/docs/SQL/mysql/2024040800.sql
new file mode 100644
--- /dev/null
+++ b/docs/SQL/mysql/2024040800.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/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_email.php b/lib/kolab_sync_data_email.php
--- a/lib/kolab_sync_data_email.php
+++ b/lib/kolab_sync_data_email.php
@@ -972,7 +972,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,12 +51,11 @@
public $syncTimeStamp;
protected $storage;
- protected $folder_meta;
protected $folder_uids;
protected $folders = [];
- protected $root_meta;
protected $relations = [];
protected $relationSupport = true;
+ protected $subscriptions;
protected $tag_rts = [];
private $modseq = [];
@@ -116,6 +115,8 @@
// Disable paging
$this->storage->set_pagesize(999999);
+
+ $this->subscriptions = new kolab_subscriptions();
}
/**
@@ -126,29 +127,6 @@
$this->folders = [];
}
- /**
- * 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 [];
- }
-
/**
* Get list of folders available for sync
*
@@ -160,33 +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 (empty($meta['FOLDER']) || empty($meta['FOLDER'][$deviceid])
- || empty($meta['FOLDER'][$deviceid]['S'])
- ) {
- 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();
+ $folders = $this->subscriptions->list_subscriptions($deviceid, $type);
- if (!is_array($folderdata)) {
- return false;
- }
-
- 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)) {
@@ -1025,8 +723,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);
@@ -1170,7 +868,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;
@@ -1271,10 +969,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.
@@ -1283,7 +982,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'])) {
@@ -1598,12 +1297,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',
@@ -1640,99 +1346,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;
}
/**
@@ -1817,19 +1500,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).
@@ -1854,57 +1524,32 @@
/**
* 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 (empty($meta['FOLDER']) || empty($meta['FOLDER'][$deviceid])
- || empty($meta['FOLDER'][$deviceid]['S'])
- ) {
- 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;
}
}
@@ -1990,34 +1635,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
@@ -77,6 +77,9 @@
// Disable paging
$this->storage->set_pagesize(999999);
+
+ // Folders subscriptions engine
+ $this->subscriptions = new kolab_subscriptions($url);
}
/**
@@ -94,12 +97,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,
@@ -108,13 +105,7 @@
];
// Get the folders "subscribed" for activesync
- foreach ($folderdata as $folder => $meta) {
- if (empty($meta['FOLDER']) || empty($meta['FOLDER'][$deviceid])
- || empty($meta['FOLDER'][$deviceid]['S'])
- ) {
- 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 +129,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 +170,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 +192,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 +220,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 +238,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 +261,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 +278,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 +293,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 +307,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 +317,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 +338,7 @@
}
// Mail folder
- return parent::folder_empty($folderid, $deviceid, $recursive);
+ return parent::folder_empty($folderid, $deviceid, $type, $recursive);
}
/**
@@ -388,20 +389,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,50 +406,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 (empty($meta['FOLDER']) || empty($meta['FOLDER'][$deviceid])
- || empty($meta['FOLDER'][$deviceid]['S'])
- ) {
- 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);
}
/**
@@ -471,7 +428,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)
{
@@ -501,10 +458,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,
];
}
@@ -518,9 +480,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');
@@ -544,6 +508,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));
@@ -553,19 +524,28 @@
|| ($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/SyncTestCase.php b/tests/SyncTestCase.php
--- a/tests/SyncTestCase.php
+++ b/tests/SyncTestCase.php
@@ -46,11 +46,8 @@
self::$deviceId = 'test' . time();
$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');
self::$client = new \GuzzleHttp\Client([
'http_errors' => false,
@@ -75,18 +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_data');
+ $db->query('DELETE FROM syncroton_data_folder');
}
}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Mon, Mar 30, 12:04 AM (4 d, 23 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18769854
Default Alt Text
D4686.1774829069.diff (51 KB)
Attached To
Mode
D4686: Subscriptions engine
Attached
Detach File
Event Timeline