Projects
Kolab:16:Enterprise
kolab-syncroton
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
Expand all
Collapse all
Changes of Revision 10
View file
kolab-syncroton.spec
Changed
@@ -47,6 +47,9 @@ Source0: https://mirror.kolabenterprise.com/pub/releases/%{name}-%{version}.tar.gz Source1: kolab-syncroton.logrotate +Patch0001: 0001-T2477-GAL-for-Outlook.patch +Patch0002: 0002-Fix-MeetingStatus-value-Bifrost-T34257.patch + BuildArch: noarch # Use this build requirement to make sure we are using @@ -222,6 +225,10 @@ %attr(0770,%{httpd_user},%{httpd_group}) %{_var}/log/%{name} %changelog +* Sun Jun 18 2017 Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen@kolabsys.com> - 2.3.5-2 +- Implement a GAL virtual folder with LDAP backend for Outlook over Activesync +- Fix organizer / ownership for events + * Thu Jun 8 2017 Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen@kolabsys.com> - 2.3.5-1 - Release 2.3.5
View file
0001-T2477-GAL-for-Outlook.patch
Added
@@ -0,0 +1,626 @@ +From 3695940a9949322c6ea9b6b71d8319ada8d84d9e Mon Sep 17 00:00:00 2001 +From: Aleksander Machniak <machniak@kolabsys.com> +Date: Fri, 16 Jun 2017 08:42:27 +0000 +Subject: PATCH 1/2 T2477: GAL for Outlook + +--- + config/config.inc.php.dist | 24 +++ + lib/kolab_sync_data.php | 44 +++-- + lib/kolab_sync_data_contacts.php | 360 ++++++++++++++++++++++++++++++++++++++- + lib/kolab_sync_data_gal.php | 35 ++-- + 4 files changed, 421 insertions(+), 42 deletions(-) + +diff --git a/config/config.inc.php.dist b/config/config.inc.php.dist +index 6864820..2433475 100644 +--- a/config/config.inc.php.dist ++++ b/config/config.inc.php.dist +@@ -52,6 +52,20 @@ $config'activesync_addressbooks' = array(); + */ + $config'activesync_gal_fieldmap' = null; + ++// List of device types that will sync the LDAP addressbook(s) as a normal folder. ++// For devices that do not support GAL searching, e.g. Outlook. ++// Examples: ++// array('windowsoutlook') # enable for Oultook only ++// true # enable for all ++$config'activesync_gal_sync' = false; ++ ++// GAL cache. As reading all contacts from LDAP may be slow, caching is recommended. ++$config'activesync_gal_cache' = 'db'; ++ ++// TTL of GAL cache entries. Technically this causes that synchronized ++// contacts will not be updated (queried) often than the specified interval. ++$config'activesync_gal_cache_ttl' = '1d'; ++ + // List of Roundcube plugins + // WARNING: Not all plugins used in Roundcube can be listed here + $config'activesync_plugins' = array(); +@@ -89,6 +103,16 @@ $config'activesync_init_subscriptions' = 0; + // action and enable folder hierarchies only on device types known to support it. + $config'activesync_multifolder_blacklist' = null; + ++// Blacklist overwrites for specified object type. If set to an array ++// it will have a precedence over 'activesync_multifolder_blacklist' list only for that type. ++// Note: Outlook does not support multiple folders for contacts, ++// in that case use $config'activesync_multifolder_blacklist_contact' = array('windowsoutlook'); ++$config'activesync_multifolder_blacklist_mail' = null; ++$config'activesync_multifolder_blacklist_event' = null; ++$config'activesync_multifolder_blacklist_contact' = null; ++$config'activesync_multifolder_blacklist_note' = null; ++$config'activesync_multifolder_blacklist_task' = null; ++ + // 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; +diff --git a/lib/kolab_sync_data.php b/lib/kolab_sync_data.php +index a32e738..b03d04b 100644 +--- a/lib/kolab_sync_data.php ++++ b/lib/kolab_sync_data.php +@@ -247,16 +247,18 @@ abstract class kolab_sync_data implements Syncroton_Data_IData + */ + protected function isMultiFolder() + { +- $blacklist = rcube::get_instance()->config->get('activesync_multifolder_blacklist'); ++ $config = rcube::get_instance()->config; ++ $blacklist = $config->get('activesync_multifolder_blacklist_' . $this->modelName); + +- if (is_array($blacklist)) { +- $is_multifolder = !in_array_nocase($this->device->devicetype, $blacklist); ++ if (!is_array($blacklist)) { ++ $blacklist = $config->get('activesync_multifolder_blacklist'); + } +- else { +- $is_multifolder = in_array_nocase($this->device->devicetype, $this->ext_devices); ++ ++ if (is_array($blacklist)) { ++ return !$this->deviceTypeFilter($blacklist); + } + +- return $is_multifolder; ++ return in_array_nocase($this->device->devicetype, $this->ext_devices); + } + + /** +@@ -908,17 +910,6 @@ abstract class kolab_sync_data implements Syncroton_Data_IData + */ + public function hasChanges(Syncroton_Backend_IContent $contentBackend, Syncroton_Model_IFolder $folder, Syncroton_Model_ISyncState $syncState) + { +- // Try to detect change in multi-folder mode and throw exception +- // so device will re-sync folders hierarchy +- // @TODO: this is a temp solution until we have real hierarchy +- // changes detection fort Ping/Hartbeat +- $is_multifolder = $this->isMultiFolder(); +- if (($is_multifolder && $folder->serverId == $this->defaultRootFolder) +- || (!$is_multifolder && $folder->type >= 12) +- ) { +- throw new Syncroton_Exception_NotFound('Folder not found'); +- } +- + try { + if ($this->getChangedEntriesCount($folder->serverId, $syncState->lastsync, null, $folder->lastfiltertype)) { + return true; +@@ -1819,4 +1810,23 @@ abstract class kolab_sync_data implements Syncroton_Data_IData + + return $result; + } ++ ++ /** ++ * Check if current device type string matches any of options ++ */ ++ protected function deviceTypeFilter($options) ++ { ++ foreach ($options as $option) { ++ if ($option0 == '/') { ++ if (preg_match($option, $this->device->devicetype)) { ++ return true; ++ } ++ } ++ else if (stripos($this->device->devicetype, $option) !== false) { ++ return true; ++ } ++ } ++ ++ return false; ++ } + } +diff --git a/lib/kolab_sync_data_contacts.php b/lib/kolab_sync_data_contacts.php +index e348474..0562377 100644 +--- a/lib/kolab_sync_data_contacts.php ++++ b/lib/kolab_sync_data_contacts.php +@@ -4,7 +4,7 @@ + +--------------------------------------------------------------------------+ + | Kolab Sync (ActiveSync for Kolab) | + | | +- | Copyright (C) 2011-2012, Kolab Systems AG <contact@kolabsys.com> | ++ | Copyright (C) 2011-2017, Kolab Systems AG <contact@kolabsys.com> | + | | + | This program is free software: you can redistribute it and/or modify | + | it under the terms of the GNU Affero General Public License as published | +@@ -124,6 +124,25 @@ class kolab_sync_data_contacts extends kolab_sync_data + */ + protected $folderType = Syncroton_Command_FolderSync::FOLDERTYPE_CONTACT_USER_CREATED; + ++ /** ++ * Identifier of special Global Address List folder ++ * ++ * @var string ++ */ ++ protected $galFolder = 'GAL'; ++ ++ /** ++ * Name of special Global Address List folder ++ * ++ * @var string ++ */ ++ protected $galFolderName = 'Global Address Book'; ++ ++ protected $galPrefix = 'GAL:'; ++ protected $galSources; ++ protected $galResult; ++ protected $galCache; ++ + + /** + * Creates model object +@@ -136,6 +155,10 @@ class kolab_sync_data_contacts extends kolab_sync_data + $data = is_array($serverId) ? $serverId : $this->getObject($collection->collectionId, $serverId); + $result = array(); + ++ if (empty($data)) { ++ throw new Syncroton_Exception_NotFound("Contact $serverId not found"); ++ } ++ + // Contacts namespace fields + foreach ($this->mapping as $key => $name) { + $value = $this->getKolabDataItem($data, $name); +@@ -169,8 +192,13 @@ class kolab_sync_data_contacts extends kolab_sync_data + + // email address(es): email1Address, email2Address, email3Address + for ($x=0; $x<3; $x++) { +- if (!empty($data'email'$x) && !empty($data'email'$x'address')) { +- $result'email' . ($x+1) . 'Address' = $data'email'$x'address'; ++ if ($email = $data'email'$x) { ++ if (is_array($email)) { ++ $email = $email'address'; ++ } ++ if ($email) { ++ $result'email' . ($x+1) . 'Address' = $email; ++ } + } + } + +@@ -267,6 +295,148 @@ class kolab_sync_data_contacts extends kolab_sync_data + } + + /** ++ * Return list of supported folders for this backend ++ * ++ * @return array ++ */ ++ public function getAllFolders() ++ { ++ $list = parent::getAllFolders(); ++ ++ if ($this->isMultiFolder() && $this->hasGAL()) { ++ $list$this->galFolder = new Syncroton_Model_Folder(array( ++ 'displayName' => $this->galFolderName, // @TODO: localization? ++ 'serverId' => $this->galFolder, ++ 'parentId' => 0, ++ 'type' => 14, ++ )); ++ } ++ ++ return $list; ++ } ++ ++ /** ++ * Updates a folder ++ */ ++ public function updateFolder(Syncroton_Model_IFolder $folder) ++ { ++ if ($folder->serverId === $this->galFolder && $this->hasGAL()) { ++ throw new Syncroton_Exception_AccessDenied("Updating GAL folder is not possible"); ++ } ++ ++ return parent::updateFolder($folder); ++ } ++ ++ /** ++ * Deletes a folder ++ */ ++ public function deleteFolder($folder) ++ { ++ if ($folder instanceof Syncroton_Model_IFolder) { ++ $folder = $folder->serverId; ++ } ++ ++ if ($folder === $this->galFolder && $this->hasGAL()) { ++ throw new Syncroton_Exception_AccessDenied("Deleting GAL folder is not possible"); ++ } ++ ++ return parent::deleteFolder($folder); ++ } ++ ++ /** ++ * Empty folder (remove all entries and optionally subfolders) ++ * ++ * @param string $folderId Folder identifier ++ * @param array $options Options ++ */ ++ public function emptyFolderContents($folderid, $options) ++ { ++ if ($folderid === $this->galFolder && $this->hasGAL()) { ++ throw new Syncroton_Exception_AccessDenied("Emptying GAL folder is not possible"); ++ } ++ ++ return parent::emptyFolderContents($folderid, $options); ++ } ++ ++ /** ++ * Moves object into another location (folder) ++ * ++ * @param string $srcFolderId Source folder identifier ++ * @param string $serverId Object identifier ++ * @param string $dstFolderId Destination folder identifier ++ * ++ * @throws Syncroton_Exception_Status ++ * @return string New object identifier ++ */ ++ public function moveItem($srcFolderId, $serverId, $dstFolderId) ++ { ++ if (strpos($serverId, $this->galPrefix) === 0 && $this->hasGAL()) { ++ throw new Syncroton_Exception_AccessDenied("Moving GAL entries is not possible"); ++ } ++ ++ if ($srcFolderId === $this->galFolder && $this->hasGAL()) { ++ throw new Syncroton_Exception_AccessDenied("Moving/Deleting GAL entries is not possible"); ++ } ++ ++ if ($dstFolderId === $this->galFolder && $this->hasGAL()) { ++ throw new Syncroton_Exception_AccessDenied("Creating GAL entries is not possible"); ++ } ++ ++ return parent::moveItem($srcFolderId, $serverId, $dstFolderId); ++ } ++ ++ /** ++ * Add entry ++ * ++ * @param string $folderId Folder identifier ++ * @param Syncroton_Model_IEntry $entry Entry object ++ * ++ * @return string ID of the created entry ++ */ ++ public function createEntry($folderId, Syncroton_Model_IEntry $entry) ++ { ++ if ($folderId === $this->galFolder && $this->hasGAL()) { ++ throw new Syncroton_Exception_AccessDenied("Creating GAL entries is not possible"); ++ } ++ ++ return parent::createEntry($folderId, $entry); ++ } ++ ++ /** ++ * update existing entry ++ * ++ * @param string $folderId ++ * @param string $serverId ++ * @param SimpleXMLElement $entry ++ * ++ * @return string ID of the updated entry ++ */ ++ public function updateEntry($folderId, $serverId, Syncroton_Model_IEntry $entry) ++ { ++ if (strpos($serverId, $this->galPrefix) === 0 && $this->hasGAL()) { ++ throw new Syncroton_Exception_AccessDenied("Updating GAL entries is not possible"); ++ } ++ ++ return parent::updateEntry($folderId, $serverId, $entry); ++ } ++ ++ /** ++ * delete entry ++ * ++ * @param string $folderId ++ * @param string $serverId ++ * @param array $collectionData ++ */ ++ public function deleteEntry($folderId, $serverId, $collectionData) ++ { ++ if (strpos($serverId, $this->galPrefix) === 0 && $this->hasGAL()) { ++ throw new Syncroton_Exception_AccessDenied("Deleting GAL entries is not possible"); ++ } ++ ++ return parent::deleteEntry($folderId, $serverId, $collectionData); ++ } ++ ++ /** + * Returns filter query array according to specified ActiveSync FilterType + * + * @param int $filter_type Filter type +@@ -280,4 +450,188 @@ class kolab_sync_data_contacts extends kolab_sync_data + return array(array('type', '=', $this->modelName)); + } + ++ /** ++ * Check if GAL synchronization is enabled for current device ++ */ ++ protected function hasGAL() ++ { ++ return count($this->getGALSources()); ++ } ++ ++ /** ++ * Search for existing entries ++ * ++ * @param string $folderid Folder identifier ++ * @param array $filter Search filter ++ * @param int $result_type Type of the result (see RESULT_* constants) ++ * ++ * @return array|int Search result as count or array of uids/objects ++ */ ++ protected function searchEntries($folderid, $filter = array(), $result_type = self::RESULT_UID) ++ { ++ // GAL Folder exists, return result from LDAP only ++ if ($folderid === $this->galFolder && $this->hasGAL()) { ++ return $this->searchGALEntries($filter, $result_type); ++ } ++ ++ $result = parent::searchEntries($folderid, $filter, $result_type); ++ ++ // Merge results from LDAP ++ if ($this->hasGAL() && !$this->isMultiFolder()) { ++ $gal_result = $this->searchGALEntries($filter, $result_type); ++ ++ if ($result_type == self::RESULT_COUNT) { ++ $result += $gal_result; ++ } ++ else { ++ $result = array_merge($result, $gal_result); ++ } ++ } ++ ++ return $result; ++ } ++ ++ /** ++ * Fetches the entry from the backend ++ */ ++ protected function getObject($folderid, $entryid, &$folder = null) ++ { ++ if (strpos($entryid, $this->galPrefix) === 0 && $this->hasGAL()) { ++ return $this->getGALEntry($entryid); ++ } ++ ++ return parent::getObject($folderid, $entryid, $folder); ++ } ++ ++ /** ++ * Search for existing LDAP entries ++ * ++ * @param array $filter Search filter ++ * @param int $result_type Type of the result (see RESULT_* constants) ++ * ++ * @return array|int Search result as count or array of uids/objects ++ */ ++ protected function searchGALEntries($filter, $result_type) ++ { ++ // For GAL we don't check for changes. ++ // When something changed a new UID will be generated so the update ++ // will be done as delete + create ++ foreach ($filter as $f) { ++ if ($f0 == 'changed') { ++ return $result_type == self::RESULT_COUNT ? 0 : array(); ++ } ++ } ++ ++ if ($this->galCache && ($result = $this->galCache->get('index')) !== null) { ++ $result = explode("\n", $result); ++ return $result_type == self::RESULT_COUNT ? count($result) : $result; ++ } ++ ++ $result = array(); ++ ++ foreach ($this->getGALSources() as $source) { ++ if ($book = kolab_sync_data_gal::get_address_book($source'id')) { ++ $book->reset(); ++ $book->set_page(1); ++ $book->set_pagesize(10000); ++ ++ $set = $book->list_records(); ++ while ($contact = $set->next()) { ++ $result = $this->createGALEntryUID($contact, $source'id'); ++ } ++ } ++ } ++ ++ if ($this->galCache) { ++ $this->galCache->set('index', implode("\n", $result)); ++ } ++ ++ return $result_type == self::RESULT_COUNT ? count($result) : $result; ++ } ++ ++ /** ++ * Return specified LDAP entry ++ * ++ * @param string $serverId Entry identifier ++ * ++ * @return array Contact data ++ */ ++ protected function getGALEntry($serverId) ++ { ++ list($source, $timestamp, $uid) = $this->resolveGALEntryUID($serverId); ++ ++ if ($source && $uid && ($book = kolab_sync_data_gal::get_address_book($source))) { ++ $book->reset(); ++ ++ $set = $book->search('uid', array($uid), rcube_addressbook::SEARCH_STRICT, true, true); ++ $result = $set->first(); ++ ++ if ($result'uid' == $uid && $result'changed' == $timestamp) { ++ // As in kolab_sync_data_gal we use only one email address ++ if (empty($result'email')) { ++ $emails = $book->get_col_values('email', $result, true); ++ $result'email' = array($emails0); ++ } ++ ++ return $result; ++ } ++ } ++ } ++ ++ /** ++ * Return LDAP address books list ++ * ++ * @return array Address books array ++ */ ++ protected function getGALSources() ++ { ++ if ($this->galSources === null) { ++ $rcube = rcube::get_instance(); ++ $gal_sync = $rcube->config->get('activesync_gal_sync'); ++ $enabled = false; ++ ++ if ($gal_sync === true) { ++ $enabled = true; ++ } ++ else if (is_array($gal_sync)) { ++ $enabled = $this->deviceTypeFilter($gal_sync); ++ } ++ ++ $this->galSources = $enabled ? kolab_sync_data_gal::get_address_sources() : array(); ++ ++ if ($cache_type = $rcube->config->get('activesync_gal_cache', 'db')) { ++ $cache_ttl = $rcube->config->get('activesync_gal_cache_ttl', '1d'); ++ $this->galCache = $rcube->get_cache('activesync_gal', $cache_type, $cache_ttl, false); ++ ++ // expunge cache every now and then ++ if (rand(0, 10) === 0) { ++ $this->galCache->expunge(); ++ } ++ } ++ } ++ ++ return $this->galSources; ++ } ++ ++ /** ++ * Builds contact identifier from contact data and source id ++ */ ++ protected function createGALEntryUID($contact, $source_id) ++ { ++ return $this->galPrefix . sprintf('%s:%s:%s', rcube_ldap::dn_encode($source_id), $contact'changed', $contact'uid'); ++ } ++ ++ /** ++ * Extracts contact identification data from contact identifier ++ */ ++ protected function resolveGALEntryUID($uid) ++ { ++ if (strpos($uid, $this->galPrefix) === 0) { ++ $items = explode(':', substr($uid, strlen($this->galPrefix))); ++ $items0 = rcube_ldap::dn_decode($items0); ++ return $items; // source, timestamp, uid ++ } ++ ++ return array(); ++ } + } +diff --git a/lib/kolab_sync_data_gal.php b/lib/kolab_sync_data_gal.php +index 297a4a9..95894e7 100644 +--- a/lib/kolab_sync_data_gal.php ++++ b/lib/kolab_sync_data_gal.php +@@ -42,7 +42,7 @@ class kolab_sync_data_gal extends kolab_sync_data implements Syncroton_Data_IDat + * + * @var array + */ +- protected $address_books = array(); ++ protected static $address_books = array(); + + /** + * Mapping from ActiveSync Contacts namespace fields +@@ -193,7 +193,7 @@ class kolab_sync_data_gal extends kolab_sync_data implements Syncroton_Data_IDat + + // @TODO: caching with Options->RebuildResults support + +- $books = $this->get_address_sources(); ++ $books = self::get_address_sources(); + $mode = 2; // use prefix mode + $fields = $rcube->config->get('contactlist_fields'); + +@@ -202,7 +202,7 @@ class kolab_sync_data_gal extends kolab_sync_data implements Syncroton_Data_IDat + } + + foreach ($books as $idx => $book) { +- $book = $this->get_address_book($idx); ++ $book = self::get_address_book($idx); + + if (!$book) { + continue; +@@ -284,14 +284,14 @@ class kolab_sync_data_gal extends kolab_sync_data implements Syncroton_Data_IDat + * + * @return rcube_contacts Address book object + */ +- protected function get_address_book($id) ++ public static function get_address_book($id) + { + $config = rcube::get_instance()->config; + $ldap_config = (array) $config->get('ldap_public'); + + // use existing instance +- if (isset($this->address_books$id) && ($this->address_books$id instanceof rcube_addressbook)) { +- $book = $this->address_books$id; ++ if (isset(self::$address_books$id) && (self::$address_books$id instanceof rcube_addressbook)) { ++ $book = self::$address_books$id; + } + else if ($id && $ldap_config$id) { + $book = new rcube_ldap($ldap_config$id, $config->get('ldap_debug'), +@@ -313,7 +313,7 @@ class kolab_sync_data_gal extends kolab_sync_data implements Syncroton_Data_IDat + $book->set_sort_order($sort_col); + */ + // add to the 'books' array for shutdown function +- $this->address_books$id = $book; ++ self::$address_books$id = $book; + + return $book; + } +@@ -324,7 +324,7 @@ class kolab_sync_data_gal extends kolab_sync_data implements Syncroton_Data_IDat + * + * @return array Address books array + */ +- protected function get_address_sources() ++ public static function get_address_sources() + { + $config = rcube::get_instance()->config; + $ldap_config = (array) $config->get('ldap_public'); +@@ -338,21 +338,12 @@ class kolab_sync_data_gal extends kolab_sync_data implements Syncroton_Data_IDat + + foreach ((array)$async_books as $id) { + $prop = $ldap_config$id; +- // handle misconfiguration +- if (empty($prop) || !is_array($prop)) { +- continue; ++ if (!empty($prop) && is_array($prop)) { ++ $list$id = array( ++ 'id' => $id, ++ 'name' => $prop'name', ++ ); + } +- +- $list$id = array( +- 'id' => $id, +- 'name' => $prop'name', +- ); +-/* +- // register source for shutdown function +- if (!is_object($this->address_books$id)) +- $this->address_books$id = $list$id; +- } +-*/ + } + + return $list; +-- +2.13.0 +
View file
0002-Fix-MeetingStatus-value-Bifrost-T34257.patch
Added
@@ -0,0 +1,71 @@ +From 07a86bbd51c6444135eb8ed7ded465aad64167eb Mon Sep 17 00:00:00 2001 +From: Aleksander Machniak <machniak@kolabsys.com> +Date: Fri, 16 Jun 2017 15:07:10 +0000 +Subject: PATCH 2/2 Fix MeetingStatus value (Bifrost#T34257) + +Wrong value caused Outlook to think every event with attendees is organized +by the current user even if he was an attendee not organizer. +--- + lib/kolab_sync_data_calendar.php | 39 ++++++++++++++++++++++++++++++++++++++- + 1 file changed, 38 insertions(+), 1 deletion(-) + +diff --git a/lib/kolab_sync_data_calendar.php b/lib/kolab_sync_data_calendar.php +index 14d929f..fcfd51f 100644 +--- a/lib/kolab_sync_data_calendar.php ++++ b/lib/kolab_sync_data_calendar.php +@@ -308,7 +308,7 @@ class kolab_sync_data_calendar extends kolab_sync_data implements Syncroton_Data + } + + // Event meeting status +- $result'meetingStatus' = intval(!empty($result'attendees')); ++ $this->meeting_status_from_kolab($collection, $event, $result); + + // Recurrence (and exceptions) + $this->recurrence_from_kolab($collection, $event, $result); +@@ -543,6 +543,43 @@ class kolab_sync_data_calendar extends kolab_sync_data implements Syncroton_Data + } + + /** ++ * Set MeetingStatus according to event data ++ */ ++ protected function meeting_status_from_kolab($collection, $event, &$result) ++ { ++ // 0 - The event is an appointment, which has no attendees. ++ // 1 - The event is a meeting and the user is the meeting organizer. ++ // 3 - This event is a meeting, and the user is not the meeting organizer. ++ // 5 - The meeting has been canceled and the user was the meeting organizer. ++ // 7 - The meeting has been canceled. The user was not the meeting organizer. ++ $status = 0; ++ ++ if (!empty($event'attendees')) { ++ // Find out if the user is an organizer ++ // TODO: Delegation/aliases support ++ $user_emails = kolab_sync::get_instance()->user->list_emails(); ++ $user_emails = array_map(function($v) { return $v'email'; }, $user_emails); ++ $is_organizer = true; ++ ++ foreach ($event'attendees' as $attendee) { ++ if (in_array_nocase($attendee'email', $user_emails)) { ++ $is_organizer = false; ++ break; ++ } ++ } ++ ++ if ($event'status' == 'CANCELLED') { ++ $status = !empty($is_organizer) ? 5 : 7; ++ } ++ else { ++ $status = !empty($is_organizer) ? 1 : 3; ++ } ++ } ++ ++ $result'meetingStatus' = $status; ++ } ++ ++ /** + * Converts libkolab alarms spec. into a number of minutes + */ + protected function from_kolab_alarm($event) +-- +2.13.0 +
View file
debian.changelog
Changed
@@ -1,3 +1,10 @@ +kolab-syncroton (2.3.5-0~kolab3) unstable; urgency=low + + * Allow a GAL to be distributed from LDAP, for Outlook over Activesync + * Fix organizer / ownership of events + + -- Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen@kolabsys.com> Sun, 18 Jun 2017 15:13:40 +0200 + kolab-syncroton (2.3.5-0~kolab2) unstable; urgency=low * Ship lib/ext/Syncroton
View file
debian.series
Changed
@@ -0,0 +1,2 @@ +0001-T2477-GAL-for-Outlook.patch -p1 +0002-Fix-MeetingStatus-value-Bifrost-T34257.patch -p1
View file
kolab-syncroton.dsc
Changed
@@ -2,7 +2,7 @@ Source: kolab-syncroton Binary: kolab-syncroton Architecture: all -Version: 2.3.5-0~kolab2 +Version: 2.3.5-0~kolab3 Maintainer: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen@kolabsys.com> Uploaders: Paul Klos <kolab@klos2day.nl> 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
.