Projects
Kolab:16:Testing
kolab-syncroton
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
Expand all
Collapse all
Changes of Revision 69
View file
kolab-syncroton.spec
Changed
@@ -43,7 +43,7 @@ %global upstream_version 2.4.2 Name: kolab-syncroton -Version: 2.4.2.42 +Version: 2.4.2.43 Release: 1%{?dist} Summary: ActiveSync for Kolab Groupware
View file
debian.changelog
Changed
@@ -1,4 +1,4 @@ -kolab-syncroton (2.4.2.42-0~kolab1) unstable; urgency=low +kolab-syncroton (2.4.2.43-0~kolab1) unstable; urgency=low * Release version 2.4.2
View file
kolab-syncroton-2.4.2.tar.gz/bin/inspect.php
Changed
@@ -78,7 +78,7 @@ 'e' => 'email', 'p' => 'adminpassword', 'd' => 'debug', - 'k' => 'dump' + 'k' => 'dump', ); if (empty($opts'email')) {
View file
kolab-syncroton-2.4.2.tar.gz/config/config.inc.php.dist
Changed
@@ -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;
View file
kolab-syncroton-2.4.2.tar.gz/docs/SQL/mysql.initial.sql
Changed
@@ -48,6 +48,7 @@ `lastfiltertype` int(11) DEFAULT NULL, `supportedfields` longblob DEFAULT NULL, `resync` tinyint(1) DEFAULT NULL, + `is_deleted` tinyint(1) DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `device_id--class--folderid` (`device_id`(40),`class`(40),`folderid`(40)), KEY `folderstates::device_id--devices::id` (`device_id`), @@ -109,12 +110,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', '2025043000');
View file
kolab-syncroton-2.4.2.tar.gz/docs/SQL/mysql/2025042900.sql
Added
@@ -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 */;
View file
kolab-syncroton-2.4.2.tar.gz/docs/SQL/mysql/2025043000.sql
Added
@@ -0,0 +1,1 @@ +ALTER TABLE `syncroton_folder` ADD `is_deleted` tinyint(1) NOT NULL DEFAULT '0';
View file
kolab-syncroton-2.4.2.tar.gz/lib/ext/Syncroton/Command/Ping.php
Changed
@@ -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);
View file
kolab-syncroton-2.4.2.tar.gz/lib/ext/Syncroton/Model/AXMLEntry.php
Changed
@@ -50,7 +50,7 @@ foreach ($this->_elements as $elementName => $value) { // skip empty values - if ($value === null || $value === '' || (is_array($value) && empty($value))) { + if ($value === null || (is_array($value) && empty($value))) { continue; }
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_backend_device.php
Changed
@@ -32,23 +32,6 @@ 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 * * @param Syncroton_Model_IDevice $device Device object @@ -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); } /**
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_backend_folder.php
Changed
@@ -46,6 +46,21 @@ } /** + * mark folder as deleted. The state gets removed finally, + * when the synckey gets validated during next sync. + * + * @param Syncroton_Model_IFolder|string $id + */ + public function delete($id) + { + $id = $id instanceof Syncroton_Model_IFolder ? $id->id : $id; + + $result = $this->db->query("UPDATE `{$this->table_name}` SET `is_deleted` = 1 WHERE `id` = ?", $id); + + return $result; + } + + /** * Get array of ids which got send to the client for a given class * * @param Syncroton_Model_Device|string $deviceid Device object or identifier
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_backend_state.php
Changed
@@ -146,6 +146,28 @@ } /** + * Run a query and retry on deadlock + */ + private function runAndRetry($query) + { + $retryCounter = 0; + while (true) { + $result = $this->db->query($query); + if ($this->db->is_error($result)) { + $retryCounter++; + // Retry on deadlock + if ($this->db->error_info()0 != '40001' || $retryCounter > 60) { + throw new Exception("Failed to run query ($retryCounter retries): " . $query); + } + } else { + break; + } + // Give the other transactions some time before we try again + sleep(1); + } + } + + /** * Validates specified sync state by checking for existance of newer keys * * @param Syncroton_Model_IDevice|string $deviceid Device object or identifier @@ -184,32 +206,24 @@ $state = $states$sync_key; $next = max(array_keys($states)); - $where = ; - $where'device_id' = $this->db->quote_identifier('device_id') . ' = ' . $this->db->quote($device_id); - $where'folder_id' = $this->db->quote_identifier('folder_id') . ' = ' . $this->db->quote($folder_id); - $where'is_deleted' = $this->db->quote_identifier('is_deleted') . ' = 1'; - // found more recent synckey => the last sync response was not received by the client if ($next > $sync_key) { // We store the clientIdMap with the "next" sync state, so we need to copy it back. $state->clientIdMap = $states$next->clientIdMap; $state->counterNext = $next; } else { - // finally delete all entries marked for removal in syncroton_content table - $retryCounter = 0; - while (true) { - $result = $this->db->query("DELETE FROM `syncroton_content` WHERE " . implode(' AND ', $where)); - if ($this->db->is_error($result)) { - $retryCounter++; - // Retry on deadlock - if ($this->db->error_info()0 != '40001' || $retryCounter > 60) { - throw new Exception('Failed to delete entries in sync_key check'); - } - } else { - break; - } - //Give the other transactions some time before we try again - sleep(1); + // Cleanup deleted entries + if ($folderid == 'FolderSync') { + $where = ; + $where'device_id' = $this->db->quote_identifier('device_id') . ' = ' . $this->db->quote($device_id); + $where'is_deleted' = $this->db->quote_identifier('is_deleted') . ' = 1'; + $this->runAndRetry("DELETE FROM `syncroton_folder` WHERE " . implode(' AND ', $where)); + } else { + $where = ; + $where'device_id' = $this->db->quote_identifier('device_id') . ' = ' . $this->db->quote($device_id); + $where'folder_id' = $this->db->quote_identifier('folder_id') . ' = ' . $this->db->quote($folder_id); + $where'is_deleted' = $this->db->quote_identifier('is_deleted') . ' = 1'; + $this->runAndRetry("DELETE FROM `syncroton_content` WHERE " . implode(' AND ', $where)); } }
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_data.php
Changed
@@ -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); } /**
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_data_calendar.php
Changed
@@ -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;
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_data_contacts.php
Changed
@@ -192,19 +192,21 @@ $result$key = $value; } + $emails = $this->getKolabContactEmails($data); + // email address(es): email1Address, email2Address, email3Address for ($x = 0; $x < 3; $x++) { - if (!empty($data'email'$x)) { - $email = $data'email'$x; - if (is_array($email)) { - $email = $email'address'; - } - if ($email) { - $result'email' . ($x + 1) . 'Address' = $email; - } + if (!empty($emails$x)) { + $result'email' . ($x + 1) . 'Address' = $emails$x; } } + // Outlook's synchronization will break with an empty contact (no nodes under ApplicationData), + // so we make sure to always set a property (an empty name is enough as long as there is an XML node). + if (empty($result)) { + $result'firstName' = ''; + } + return new Syncroton_Model_Contact($result); } @@ -269,31 +271,7 @@ } // email address(es): email1Address, email2Address, email3Address - $emails = ; - for ($x = 0; $x < 3; $x++) { - $key = 'email' . ($x + 1) . 'Address'; - $value = $data->$key ?? null; - if ($value) { - // Android sends email address as: Lars Kneschke <l.kneschke@metaways.de> - if (preg_match('/(.*)<(.+@^@+)>/', $value, $matches)) { - $value = trim($matches2); - } - - // sanitize email address, it can contain broken (non-unicode) characters (#3287) - $value = rcube_charset::clean($value); - - // try to find address type, at least we can do this if - // address wasn't changed - $type = ''; - foreach ((array)$contact'email' as $email) { - if ($email'address' == $value) { - $type = $email'type'; - } - } - $emails = 'address' => $value, 'type' => $type; - } - } - $contact'email' = $emails; + $this->setKolabContactEmails($contact, $data); return $contact; } @@ -638,4 +616,99 @@ return ; } + + /** + * Extract list of email addresses from a Kolab contact + */ + protected function getKolabContactEmails($contact) + { + // Contacts from XML (Kolab3) contain 'email' item set with an array that contains address and type + // or is just an address. + // Contacts from DAV (Kolab4) contain 'email:<type>' items with an array of email addresses. + + $emails = ; + foreach ('email', 'email:work', 'email:other', 'email:home' as $key) { + foreach ($contact$key ?? as $item) { + if (is_string($item) && strpos($item, '@')) { + $emails = $item; + } elseif (is_array($item) && !empty($item)) { + if (isset($item'address')) { + $emails = $item'address'; + } else { + $emails = array_merge($emails, $item); + } + } + } + } + + // Remove duplicates and empty values + return array_values(array_filter(array_unique($emails))); + } + + /** + * Set Kolab contact email addresses + */ + protected function setKolabContactEmails(&$contact, $data) + { + // On Kolab3 (XML) contacts have 'email' item that is a list of arrays that contain address and type + // or is just an address. No types. + // On Kolab4 (DAV) contacts have 'email:home', 'email:other' and 'email:home' items with + // an array of email addresses each. + + // Get addresses from ActiveSync properties (email1Address, email2Address, email3Address) + $emails = ; + for ($x = 0; $x < 3; $x++) { + $key = 'email' . ($x + 1) . 'Address'; + $email = $data->$key ?? null; + if ($email) { + // sanitize email address, it can contain broken (non-unicode) characters (#3287) + $email = rcube_charset::clean($email); + + // Android sends email address as: Lars Kneschke <l.kneschke@metaways.de> + if (preg_match('/(.*)<(.+@^@+)>/', $email, $matches)) { + $email = trim($matches2); + } + + $emails = $email; + } + } + + // Warning: If contact has more than 3 addresses in Kolab they will be removed + + if ($this->backend instanceof kolab_sync_storage_kolab4) { + // Remove addresses that do not exist anymore + $existing = ; + foreach ('email:work', 'email:other', 'email:home' as $key) { + if (!empty($contact$key)) { + $contact$key = array_values(array_intersect($contact$key, $emails)); + $existing = array_merge($existing, $contact$key); + if (empty($contact$key)) { + unset($contact$key); + } + } + } + + // Add new addresses + foreach (array_diff($emails, $existing) as $email) { + if (!isset($contact'email:other')) { + $contact'email:other' = ; + } + $contact'email:other' = $email; + } + } else { + foreach ($emails as $idx => $email) { + // try to find address type, at least we can do this if address wasn't changed + $type = ''; + foreach ($contact'email' ?? as $existing) { + if (isset($existing'address') && $existing'address' == $email) { + $type = $existing'type' ?? ''; + } + } + + $emails$idx = 'address' => $email, 'type' => $type; + } + + $contact'email' = $emails; + } + } }
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_data_email.php
Changed
@@ -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;
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_storage.php
Changed
@@ -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 $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($metaself::ROOT_MAILBOXself::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); @@ -257,35 +217,6 @@ } /** - * 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($metaself::ASYNC_KEY)) { - if ($metadata = $this->unserialize_metadata($metaself::ASYNC_KEY)) { - $this->folder_meta$folder = $metadata; - } - } - } - } - - return $this->folder_meta; - } - - /** * Creates folder and subscribes to the device * * @param string $name Folder name (UTF8) @@ -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; } @@ -443,233 +373,6 @@ } /** - * 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. * * @param string $folderid Folder identifier @@ -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$nameself::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($dataself::UID_KEY)) { - $uid = $dataself::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; - } }
View file
kolab-syncroton-2.4.2.tar.gz/lib/kolab_sync_storage_kolab4.php
Changed
@@ -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 = $folder2; $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$nameself::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$folder0 = ($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); } /**
View file
kolab-syncroton-2.4.2.tar.gz/tests/Sync/FoldersTest.php
Changed
@@ -17,10 +17,10 @@ $this->deleteTestFolder('Test Contacts New', 'contact'); // Make sure the default folders exist if (!$this->isStorageDriver('kolab4')) { - $this->createTestFolder("Calendar", "event.default"); - $this->createTestFolder("Contacts", "contact.default"); - $this->createTestFolder("Tasks", "task.default"); - $this->createTestFolder("Notes", "note.default"); + $this->createTestFolder('Calendar', 'event.default'); + $this->createTestFolder('Contacts', 'contact.default'); + $this->createTestFolder('Tasks', 'task.default'); + $this->createTestFolder('Notes', 'note.default'); } //TODO: handle kolab4 case? } @@ -30,6 +30,7 @@ */ public function testFolderSyncBasic() { + $this->registerDevice(); $this->foldersCleanup(); $request = <<<EOF @@ -126,6 +127,7 @@ */ public function testSyncKeyResend() { + $this->createTestFolder("NewFolderToRemove", "mail"); $request = <<<EOF <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> @@ -138,9 +140,12 @@ $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"); + $this->deleteTestFolder('NewFolderToRemove', 'mail'); + $request = <<<EOF <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE AirSync PUBLIC "-//AIRSYNC//DTD AirSync//EN" "http://www.microsoft.com/"> @@ -155,7 +160,7 @@ $xpath = $this->xpath($dom); $this->assertSame('1', $xpath->query("//ns:FolderSync/ns:Status")->item(0)->nodeValue); $this->assertSame('2', $xpath->query("//ns:FolderSync/ns:SyncKey")->item(0)->nodeValue); - $this->assertSame(strval(1), $xpath->query("//ns:FolderSync/ns:Changes/ns:Count")->item(0)->nodeValue); + $this->assertSame(strval(2), $xpath->query("//ns:FolderSync/ns:Changes/ns:Count")->item(0)->nodeValue); //Resend the same synckey $request = <<<EOF @@ -172,7 +177,7 @@ $xpath = $this->xpath($dom); $this->assertSame('1', $xpath->query("//ns:FolderSync/ns:Status")->item(0)->nodeValue); $this->assertSame('2', $xpath->query("//ns:FolderSync/ns:SyncKey")->item(0)->nodeValue); - $this->assertSame(strval(1), $xpath->query("//ns:FolderSync/ns:Changes/ns:Count")->item(0)->nodeValue); + $this->assertSame(strval(2), $xpath->query("//ns:FolderSync/ns:Changes/ns:Count")->item(0)->nodeValue); //And now make sure we can still move on $request = <<<EOF
View file
kolab-syncroton-2.4.2.tar.gz/tests/Sync/PingTest.php
Changed
@@ -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"?>
View file
kolab-syncroton-2.4.2.tar.gz/tests/Sync/Sync/CalendarTest.php
Changed
@@ -44,6 +44,10 @@ $this->assertSame($folderId, $xpath->query("{$root}/ns:CollectionId")->item(0)->nodeValue); $this->assertSame(0, $xpath->query("{$root}/ns:Commands/ns:Add")->count()); + if ($this->isStorageDriver('kolab')) { + $this->markTestSkipped("Appending test objects does not work with 'kolab' storage yet."); + } + // Append two event objects and sync them $this->appendObject($davFolder, 'event.ics1', 'event'); $this->appendObject($davFolder, 'event.ics2', 'event');
View file
kolab-syncroton-2.4.2.tar.gz/tests/Sync/Sync/ContactsTest.php
Changed
@@ -53,6 +53,10 @@ $this->assertSame($folderId, $xpath->query("{$root}/ns:CollectionId")->item(0)->nodeValue); $this->assertSame(0, $xpath->query("{$root}/ns:Commands/ns:Add")->count()); + if ($this->isStorageDriver('kolab')) { + $this->markTestSkipped("Appending test objects does not work with 'kolab' storage yet."); + } + // Append two contact objects and sync them // TODO: Test a folder with contact groups inside $this->appendObject($davFolder, 'contact.vcard1', 'contact'); @@ -75,10 +79,19 @@ $root .= "/ns:Commands/ns:Add"; $this->assertStringMatchesFormat("CRC%s", $xpath->query("{$root}/ns:ServerId")->item(0)->nodeValue); - $this->assertSame('Jack', $xpath->query("{$root}/ns:ApplicationData/Contacts:FirstName")->item(0)->nodeValue); - $this->assertSame('Strong', $xpath->query("{$root}/ns:ApplicationData/Contacts:LastName")->item(0)->nodeValue); - $this->assertSame('Jane', $xpath->query("{$root}/ns:ApplicationData/Contacts:FirstName")->item(1)->nodeValue); - $this->assertSame('Doe', $xpath->query("{$root}/ns:ApplicationData/Contacts:LastName")->item(1)->nodeValue); + $r = "{$root}1/ns:ApplicationData"; + $this->assertSame('Jack', $xpath->query("{$r}/Contacts:FirstName")->item(0)->nodeValue); + $this->assertSame('Strong', $xpath->query("{$r}/Contacts:LastName")->item(0)->nodeValue); + $this->assertSame('jack@kolab.org', $xpath->query("{$r}/Contacts:Email1Address")->item(0)->nodeValue); + $this->assertNull($xpath->query("{$r}/Contacts:Email2Address")->item(0)); + $this->assertNull($xpath->query("{$r}/Contacts:Email3Address")->item(0)); + $r = "{$root}2/ns:ApplicationData"; + $this->assertSame('Jane', $xpath->query("{$r}/Contacts:FirstName")->item(0)->nodeValue); + $this->assertSame('J.', $xpath->query("{$r}/Contacts:MiddleName")->item(0)->nodeValue); + $this->assertSame('Doe', $xpath->query("{$r}/Contacts:LastName")->item(0)->nodeValue); + $this->assertNull($xpath->query("{$r}/Contacts:Email1Address")->item(0)); + $this->assertNull($xpath->query("{$r}/Contacts:Email2Address")->item(0)); + $this->assertNull($xpath->query("{$r}/Contacts:Email3Address")->item(0)); return $syncKey; } @@ -113,6 +126,10 @@ <ClientId>42</ClientId> <ApplicationData> <Contacts:FirstName>Lars</Contacts:FirstName> + <Contacts:LastName>Ulrich</Contacts:LastName> + <Contacts:Email1Address>lars@kolab.org</Contacts:Email1Address> + <Contacts:Email2Address>lars.tw@kolab.org</Contacts:Email2Address> + <Contacts:Email3Address>lars.th@kolab.org</Contacts:Email3Address> </ApplicationData> </Add> </Commands> @@ -137,7 +154,13 @@ $serverId = $xpath->query("ns:ServerId", $root)->item(0)->nodeValue; $this->assertStringMatchesFormat("CRC%s", $serverId); - // TODO: Test the content on the server + // Assert the content on the server + $contacts = $this->getDavObjects('Contacts', 'contact'); + $this->assertCount(3, $contacts); + usort($contacts, function ($c1, $c2) { return $c1'surname' <=> $c2'surname'; }); + $this->assertSame('Lars', $contacts2'firstname'); + $this->assertSame('Ulrich', $contacts2'surname'); + $this->assertSame('lars@kolab.org', 'lars.tw@kolab.org', 'lars.th@kolab.org', $contacts2'email:other'); return $syncKey, $serverId; }
View file
kolab-syncroton-2.4.2.tar.gz/tests/SyncTestCase.php
Changed
@@ -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,69 @@ 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 = $arr0 ?? null; + + if (!$device_id) { + throw new \Exception('Device ID not found'); + } + + $type, = explode('.', $type); + + $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); } /** @@ -289,6 +324,26 @@ } /** + * Get objects from a DAV folder + */ + protected function getDavObjects($foldername, $type, $query = ) + { + $dav = $this->getDavStorage(); + + foreach ($dav->get_folders($type) as $folder) { + if ($folder->get_name() === $foldername) { + $result = ; + foreach ($folder->select($query) as $object) { + $result = $object; + } + return $result; + } + } + + throw new \Exception("Folder not found"); + } + + /** * Initialize DAV storage */ protected function getDavStorage() @@ -394,11 +449,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'); }
View file
kolab-syncroton-2.4.2.tar.gz/tests/bootstrap.php
Changed
@@ -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) && $argv1 == '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'); +}
View file
kolab-syncroton-2.4.2.tar.gz/tests/setmetadata.php
Deleted
@@ -1,21 +0,0 @@ -<?php - -define('TESTS_DIR', dirname(__FILE__) . '/'); -require_once(TESTS_DIR . '/../lib/init.php'); - -sleep(5); - -$deviceid=$argv1 ; -$folderName=$argv2 ; - -$metadata = ; -$metadata'FOLDER' = ; -$metadata'FOLDER'$deviceid = ; -$metadata'FOLDER'$deviceid'S' = '1'; -$metadata = json_encode($metadata); -\kolab_sync::get_instance()->authenticate($argv3, $argv4); -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"); -}
View file
kolab-syncroton-2.4.2.tar.gz/tests/src/contact.vcard1
Changed
@@ -1,6 +1,6 @@ BEGIN:VCARD VERSION:3.0 -UID:urn:uuid:abcdef-0123-4567-89ab-abcdefabcdef +UID:abcdef-0123-4567-89ab-abcdefabcdef FN:Jane Doe N:Doe;Jane;J.;; END:VCARD
View file
kolab-syncroton-2.4.2.tar.gz/tests/src/contact.vcard2
Changed
@@ -1,6 +1,7 @@ BEGIN:VCARD VERSION:3.0 -UID:urn:uuid:abcdef-0123-4567-89ab-abcdefabc123 +UID:abcdef-0123-4567-89ab-abcdefabc123 FN:Jack Strong N:Strong;Jack;;; +EMAIL;TYPE=work:jack@kolab.org END:VCARD
View file
kolab-syncroton.dsc
Changed
@@ -2,7 +2,7 @@ Source: kolab-syncroton Binary: kolab-syncroton Architecture: all -Version: 1:2.4.2.42-1~kolab1 +Version: 1:2.4.2.43-1~kolab1 Maintainer: Jeroen van Meeuwen <vanmeeuwen@kolabsys.com> Uploaders: Jeroen van Meeuwen <vanmeeuwen@kolabsys.com> Homepage: http://www.kolab.org/
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.