diff --git a/.tx/config b/.tx/config
index 54caf13a..e3eee2ea 100644
--- a/.tx/config
+++ b/.tx/config
@@ -1,6 +1,6 @@
[main]
host = https://www.transifex.com
-lang_map = de: de_DE, es: es_ES, fr: fr_FR, ja: ja_JP, nl: nl_NL
+lang_map = en: en_US, de: de_DE, es: es_ES, fr: fr_FR, ja: ja_JP, nl: nl_NL
type = PHP_ALT_ARRAY
[kolab.calendar]
diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js
index c80b8941..40997764 100644
--- a/plugins/calendar/calendar_ui.js
+++ b/plugins/calendar/calendar_ui.js
@@ -170,19 +170,20 @@ function rcube_calendar_ui(settings)
};
// check if the current user is an attendee of this event
- var is_attendee = function(event, role)
+ var is_attendee = function(event, role, email)
{
+ var emails = email ? ';'+email : settings.identity.emails;
for (var i=0; event.attendees && i < event.attendees.length; i++) {
- if ((!role || event.attendees[i].role == role) && event.attendees[i].email && settings.identity.emails.indexOf(';'+event.attendees[i].email) >= 0)
+ if ((!role || event.attendees[i].role == role) && event.attendees[i].email && emails.indexOf(';'+event.attendees[i].email) >= 0)
return event.attendees[i];
}
return false;
};
// check if the current user is the organizer
- var is_organizer = function(event)
+ var is_organizer = function(event, email)
{
- return is_attendee(event, 'ORGANIZER') || !event.id;
+ return is_attendee(event, 'ORGANIZER', email) || !event.id;
};
var load_attachment = function(event, att)
@@ -534,7 +535,7 @@ function rcube_calendar_ui(settings)
event_attendees = [];
attendees_list = $('#edit-attendees-table > tbody').html('');
$('#edit-attendees-notify')[(notify.checked && organizer ? 'show' : 'hide')]();
- $('#edit-localchanges-warning')[(has_attendees(event) && !organizer ? 'show' : 'hide')]();
+ $('#edit-localchanges-warning')[(has_attendees(event) && !(organizer || (calendar.owner && is_organizer(event, calendar.owner))) ? 'show' : 'hide')]();
var load_attendees_tab = function()
{
diff --git a/plugins/calendar/drivers/database/database_driver.php b/plugins/calendar/drivers/database/database_driver.php
index 958e8ca9..7669350d 100644
--- a/plugins/calendar/drivers/database/database_driver.php
+++ b/plugins/calendar/drivers/database/database_driver.php
@@ -34,7 +34,7 @@ class database_driver extends calendar_driver
public $attendees = true;
public $freebusy = false;
public $attachments = true;
- public $alarm_types = array('DISPLAY','EMAIL');
+ public $alarm_types = array('DISPLAY');
private $rc;
private $cal;
diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php
index 439bda7c..12d60c28 100644
--- a/plugins/calendar/drivers/kolab/kolab_driver.php
+++ b/plugins/calendar/drivers/kolab/kolab_driver.php
@@ -33,7 +33,7 @@ class kolab_driver extends calendar_driver
public $freebusy = true;
public $attachments = true;
public $undelete = true;
- public $alarm_types = array('DISPLAY','EMAIL');
+ public $alarm_types = array('DISPLAY');
public $categoriesimmutable = true;
private $rc;
@@ -128,6 +128,7 @@ class kolab_driver extends calendar_driver
'class_name' => $cal->get_namespace(),
'default' => $cal->storage->default,
'active' => $cal->storage->is_active(),
+ 'owner' => $cal->get_owner(),
);
}
diff --git a/plugins/calendar/localization/de_CH.inc b/plugins/calendar/localization/de_CH.inc
index 1bd8a977..025f655f 100644
--- a/plugins/calendar/localization/de_CH.inc
+++ b/plugins/calendar/localization/de_CH.inc
@@ -183,7 +183,7 @@ $labels['attendeupdateesuccess'] = 'Teilnehmerstatus erfolgreich aktualisiert';
$labels['itipsendsuccess'] = 'Einladung an Teilnehmer versendet.';
$labels['itipresponseerror'] = 'Die Antwort auf diese Einladung konnte nicht versendet werden';
$labels['sentresponseto'] = 'Antwort auf diese Einladung erfolgreich an $mailto gesendet';
-$labels['localchangeswarning'] = 'Die Änderungen an diesem Termin können nur in Ihrem persönlichen Kalender gespeichert werden.';
+$labels['localchangeswarning'] = 'Änderungen an diesem Termin werden nur in Ihrem Kalender gespeichert und nicht an den Organisator des Termins gesendet.';
$labels['importsuccess'] = 'Es wurden $nr Termine erfolgreich importiert';
$labels['importnone'] = 'Keine Termine zum Importieren gefunden';
$labels['importerror'] = 'Fehler beim Importieren';
diff --git a/plugins/calendar/localization/de_DE.inc b/plugins/calendar/localization/de_DE.inc
index ea1d0a74..8d0f2bb4 100644
--- a/plugins/calendar/localization/de_DE.inc
+++ b/plugins/calendar/localization/de_DE.inc
@@ -183,7 +183,7 @@ $labels['attendeupdateesuccess'] = 'Teilnehmerstatus erfolgreich aktualisiert';
$labels['itipsendsuccess'] = 'Einladung an Teilnehmer versendet.';
$labels['itipresponseerror'] = 'Die Antwort auf diese Einladung konnte nicht versendet werden';
$labels['sentresponseto'] = 'Antwort auf diese Einladung erfolgreich an $mailto gesendet';
-$labels['localchangeswarning'] = 'Die Änderungen an diesem Termin können nur in Ihrem persönlichen Kalender gespeichert werden.';
+$labels['localchangeswarning'] = 'Änderungen an diesem Termin werden nur in Ihrem Kalender gespeichert und nicht an den Organisator des Termins gesendet.';
$labels['importsuccess'] = 'Es wurden $nr Termine erfolgreich importiert';
$labels['importnone'] = 'Keine Termine zum Importieren gefunden';
$labels['importerror'] = 'Fehler beim Importieren';
diff --git a/plugins/calendar/localization/en.inc b/plugins/calendar/localization/en.inc
deleted file mode 100644
index 6cc76d7a..00000000
--- a/plugins/calendar/localization/en.inc
+++ /dev/null
@@ -1,230 +0,0 @@
-
diff --git a/plugins/calendar/localization/en_US.inc b/plugins/calendar/localization/en_US.inc
index 6cc76d7a..4164d892 100644
--- a/plugins/calendar/localization/en_US.inc
+++ b/plugins/calendar/localization/en_US.inc
@@ -184,7 +184,7 @@ $labels['itipsendsuccess'] = 'Invitation sent to participants.';
$labels['itipresponseerror'] = 'Failed to send the response to this event invitation';
$labels['itipinvalidrequest'] = 'This invitation is no longer valid';
$labels['sentresponseto'] = 'Successfully sent invitation response to $mailto';
-$labels['localchangeswarning'] = 'You are about to make changes that will only be reflected on your personal calendar';
+$labels['localchangeswarning'] = 'You are about to make changes that will only be reflected on your calendar and not be sent to the organizer of the event.';
$labels['importsuccess'] = 'Successfully imported $nr events';
$labels['importnone'] = 'No events found to be imported';
$labels['importerror'] = 'An error occured while importing';
diff --git a/plugins/kolab_activesync/localization/en.inc b/plugins/kolab_activesync/localization/en.inc
deleted file mode 100644
index 2399b13e..00000000
--- a/plugins/kolab_activesync/localization/en.inc
+++ /dev/null
@@ -1,33 +0,0 @@
-
In order to register a device, please connect it to the server first, using the instructions in the Wiki. Afterwards the device should become available for configuration here.';
-$labels['savingdata'] = 'Saving data...';
-$labels['savingerror'] = 'Failed to save configuration';
-$labels['notsupported'] = 'Your server does not support metadata/annotations';
-$labels['devicedeleteconfirm'] = 'Do you really want to delete the configuration for this device?';
-$labels['successfullydeleted'] = 'The device configuration was successfully removed';
-$labels['devicenotfound'] = 'Unable to read device configuration';
-
-?>
diff --git a/plugins/kolab_addressbook/localization/en.inc b/plugins/kolab_addressbook/localization/en.inc
deleted file mode 100644
index a66426f4..00000000
--- a/plugins/kolab_addressbook/localization/en.inc
+++ /dev/null
@@ -1,47 +0,0 @@
-
diff --git a/plugins/kolab_auth/localization/en.inc b/plugins/kolab_auth/localization/en.inc
deleted file mode 100644
index e1adb3f2..00000000
--- a/plugins/kolab_auth/localization/en.inc
+++ /dev/null
@@ -1,5 +0,0 @@
-
diff --git a/plugins/kolab_delegation/localization/en.inc b/plugins/kolab_delegation/localization/en.inc
deleted file mode 100644
index c445f7f9..00000000
--- a/plugins/kolab_delegation/localization/en.inc
+++ /dev/null
@@ -1,30 +0,0 @@
-
diff --git a/plugins/kolab_folders/localization/en.inc b/plugins/kolab_folders/localization/en.inc
deleted file mode 100644
index 856f59d5..00000000
--- a/plugins/kolab_folders/localization/en.inc
+++ /dev/null
@@ -1,26 +0,0 @@
-
diff --git a/plugins/libkolab/lib/kolab_format_event.php b/plugins/libkolab/lib/kolab_format_event.php
index 2b3f0c8f..ec97767c 100644
--- a/plugins/libkolab/lib/kolab_format_event.php
+++ b/plugins/libkolab/lib/kolab_format_event.php
@@ -192,7 +192,7 @@ class kolab_format_event extends kolab_format_xcal
}
// read exception event objects
- if (($exceptions = $this->obj->exceptions()) && $exceptions->size()) {
+ if (($exceptions = $this->obj->exceptions()) && is_object($exceptions) && $exceptions->size()) {
for ($i=0; $i < $exceptions->size(); $i++) {
if (($exobj = $exceptions->get($i))) {
$exception = new kolab_format_event($exobj);
diff --git a/plugins/libkolab/lib/kolab_storage_folder.php b/plugins/libkolab/lib/kolab_storage_folder.php
index 7e10f2e6..dd0e8d2d 100644
--- a/plugins/libkolab/lib/kolab_storage_folder.php
+++ b/plugins/libkolab/lib/kolab_storage_folder.php
@@ -548,9 +548,9 @@ class kolab_storage_folder
// detect old Kolab 2.0 format
if (strpos($xmlhead, '<' . $xmltype) !== false && strpos($xmlhead, 'xmlns=') === false)
- $format_version = 2.0;
+ $format_version = '2.0';
else
- $format_version = 3.0; // assume 3.0
+ $format_version = '3.0'; // assume 3.0
}
// get Kolab format handler for the given type
@@ -588,7 +588,6 @@ class kolab_storage_folder
return false;
}
-
/**
* Save an object in this folder.
*
@@ -659,6 +658,11 @@ class kolab_storage_folder
}
}
+ // save recurrence exceptions as individual objects due to lack of support in Kolab v2 format
+ if (kolab_storage::$version == '2.0' && $object['recurrence']['EXCEPTIONS']) {
+ $this->save_recurrence_exceptions($object, $type);
+ }
+
// check IMAP BINARY extension support for 'file' objects
// allow configuration to workaround bug in Cyrus < 2.4.17
$rcmail = rcube::get_instance();
@@ -666,6 +670,12 @@ class kolab_storage_folder
// generate and save object message
if ($raw_msg = $this->build_message($object, $type, $binary)) {
+ // resolve old msguid before saving
+ if ($uid && empty($object['_msguid']) && ($msguid = $this->cache->uid2msguid($uid))) {
+ $object['_msguid'] = $msguid;
+ $object['_mailbox'] = $this->name;
+ }
+
if (is_array($raw_msg)) {
$result = $this->imap->save_message($this->name, $raw_msg[0], $raw_msg[1], true, null, null, $binary);
@unlink($raw_msg[0]);
@@ -679,10 +689,6 @@ class kolab_storage_folder
$this->imap->delete_message($object['_msguid'], $object['_mailbox']);
$this->cache->set($object['_msguid'], false, $object['_mailbox']);
}
- else if ($result && $uid && ($msguid = $this->cache->uid2msguid($uid))) {
- $this->imap->delete_message($msguid, $this->name);
- $this->cache->set($object['_msguid'], false);
- }
// update cache with new UID
if ($result) {
@@ -694,6 +700,68 @@ class kolab_storage_folder
return $result;
}
+ /**
+ * Save recurrence exceptions as individual objects.
+ * The Kolab v2 format doesn't allow us to save fully embedded exception objects.
+ *
+ * @param array Hash array with event properties
+ * @param string Object type
+ */
+ private function save_recurrence_exceptions(&$object, $type = null)
+ {
+ if ($object['recurrence']['EXCEPTIONS']) {
+ $exdates = array();
+ foreach ((array)$object['recurrence']['EXDATE'] as $exdate) {
+ $key = is_a($exdate, 'DateTime') ? $exdate->format('Y-m-d') : strval($exdate);
+ $exdates[$key] = 1;
+ }
+
+ // save every exception as individual object
+ foreach((array)$object['recurrence']['EXCEPTIONS'] as $exception) {
+ $exception['uid'] = self::recurrence_exception_uid($object['uid'], $exception['start']->format('Ymd'));
+ $exception['sequence'] = $object['sequence'] + 1;
+
+ if ($exception['thisandfuture']) {
+ $exception['recurrence'] = $object['recurrence'];
+
+ // adjust the recurrence duration of the exception
+ if ($object['recurrence']['COUNT']) {
+ $recurrence = new kolab_date_recurrence($object['_formatobj']);
+ if ($end = $recurrence->end()) {
+ unset($exception['recurrence']['COUNT']);
+ $exception['recurrence']['UNTIL'] = new DateTime('@'.$end);
+ }
+ }
+
+ // set UNTIL date if we have a thisandfuture exception
+ $untildate = clone $exception['start'];
+ $untildate->sub(new DateInterval('P1D'));
+ $object['recurrence']['UNTIL'] = $untildate;
+ unset($object['recurrence']['COUNT']);
+ }
+ else {
+ if (!$exdates[$exception['start']->format('Y-m-d')])
+ $object['recurrence']['EXDATE'][] = clone $exception['start'];
+ unset($exception['recurrence']);
+ }
+
+ unset($exception['recurrence']['EXCEPTIONS'], $exception['_formatobj'], $exception['_msguid']);
+ $this->save($exception, $type, $exception['uid']);
+ }
+
+ unset($object['recurrence']['EXCEPTIONS']);
+ }
+ }
+
+ /**
+ * Generate an object UID with the given recurrence-ID in a way that it is
+ * unique (the original UID is not a substring) but still recoverable.
+ */
+ private static function recurrence_exception_uid($uid, $recurrence_id)
+ {
+ $offset = -2;
+ return substr($uid, 0, $offset) . '-' . $recurrence_id . '-' . substr($uid, $offset);
+ }
/**
* Delete the specified object from this folder.
diff --git a/plugins/owncloud/localization/en.inc b/plugins/owncloud/localization/en.inc
deleted file mode 100644
index f5cb8fb8..00000000
--- a/plugins/owncloud/localization/en.inc
+++ /dev/null
@@ -1,6 +0,0 @@
-
diff --git a/plugins/tasklist/localization/de_CH.inc b/plugins/tasklist/localization/de_CH.inc
index 4fbb9768..4a6b3263 100644
--- a/plugins/tasklist/localization/de_CH.inc
+++ b/plugins/tasklist/localization/de_CH.inc
@@ -15,7 +15,7 @@ $labels['edit'] = 'Bearbeiten';
$labels['delete'] = 'Löschen';
$labels['title'] = 'Titel';
$labels['description'] = 'Beschreibung';
-$labels['datetime'] = 'Datum/Zeit';
+$labels['datetime'] = 'Fällig';
$labels['start'] = 'Beginn';
$labels['alarms'] = 'Erinnerung';
diff --git a/plugins/tasklist/localization/de_DE.inc b/plugins/tasklist/localization/de_DE.inc
index e8c0ada7..1cbd2c0d 100644
--- a/plugins/tasklist/localization/de_DE.inc
+++ b/plugins/tasklist/localization/de_DE.inc
@@ -2,67 +2,68 @@
$labels = array();
$labels['navtitle'] = 'Aufgaben';
-$labels['lists'] = 'Tasklists';
-$labels['list'] = 'Tasklist';
+$labels['lists'] = 'Aufgabenlisten';
+$labels['list'] = 'Liste';
$labels['tags'] = 'Tags';
-$labels['newtask'] = 'New Task';
-$labels['createnewtask'] = 'Create new Task (e.g. Saturday, Mow the lawn)';
-$labels['createfrommail'] = 'Save as task';
-$labels['mark'] = 'Mark';
-$labels['unmark'] = 'Unmark';
-$labels['edit'] = 'Edit';
+$labels['newtask'] = 'Neue Aufgabe';
+$labels['createnewtask'] = 'Neue Aufgabe eingeben (z.B. Samstag, Rasenmähen)';
+$labels['createfrommail'] = 'Als Aufgabe speichern';
+$labels['mark'] = 'Markieren';
+$labels['unmark'] = 'Markierung aufheben';
+$labels['edit'] = 'Bearbeiten';
$labels['delete'] = 'Löschen';
$labels['title'] = 'Titel';
-$labels['description'] = 'Description';
-$labels['datetime'] = 'Date/Time';
-$labels['start'] = 'Start';
-$labels['alarms'] = 'Reminder';
+$labels['description'] = 'Beschreibung';
+$labels['datetime'] = 'Fällig';
+$labels['start'] = 'Beginn';
+$labels['alarms'] = 'Erinnerung';
-$labels['all'] = 'All';
-$labels['flagged'] = 'Flagged';
-$labels['complete'] = 'Complete';
-$labels['overdue'] = 'Overdue';
-$labels['today'] = 'Today';
-$labels['tomorrow'] = 'Tomorrow';
-$labels['next7days'] = 'Next 7 days';
-$labels['later'] = 'Later';
-$labels['nodate'] = 'no date';
-$labels['removetag'] = 'Remove';
+$labels['all'] = 'Alle';
+$labels['flagged'] = 'Markiert';
+$labels['complete'] = 'Erledigt';
+$labels['overdue'] = 'Überfällig';
+$labels['today'] = 'Heute';
+$labels['tomorrow'] = 'Morgen';
+$labels['next7days'] = 'Nächste 7 Tage';
+$labels['later'] = 'Später';
+$labels['nodate'] = 'kein Datum';
+$labels['removetag'] = 'Löschen';
$labels['taskdetails'] = 'Details';
-$labels['newtask'] = 'New Task';
-$labels['edittask'] = 'Edit Task';
+$labels['newtask'] = 'Neue Aufgabe';
+$labels['edittask'] = 'Aufgabe bearbeiten';
$labels['save'] = 'Speichern';
-$labels['cancel'] = 'Cancel';
-$labels['addsubtask'] = 'Add subtask';
-$labels['deletetask'] = 'Delete task';
-$labels['deletethisonly'] = 'Delete this task only';
-$labels['deletewithchilds'] = 'Delete with all subtasks';
+$labels['cancel'] = 'Abbrechen';
+$labels['addsubtask'] = 'Neue Teilaufgabe';
+$labels['deletetask'] = 'Aufgabe löschen';
+$labels['deletethisonly'] = 'Nur diese Aufgabe löschen';
+$labels['deletewithchilds'] = 'Mit allen Teilaufhaben löschen';
-$labels['tabsummary'] = 'Summary';
-$labels['tabrecurrence'] = 'Recurrence';
-$labels['tabattachments'] = 'Attachments';
-$labels['tabsharing'] = 'Sharing';
-$labels['editlist'] = 'Edit list';
-$labels['createlist'] = 'Add list';
-$labels['listactions'] = 'List options...';
+$labels['tabsummary'] = 'Übersicht';
+$labels['tabrecurrence'] = 'Wiederholung';
+$labels['tabattachments'] = 'Anhänge';
+$labels['tabsharing'] = 'Freigabe';
+
+$labels['editlist'] = 'Liste bearbeiten';
+$labels['createlist'] = 'Neue Liste';
+$labels['listactions'] = 'Listenoptionen...';
$labels['listname'] = 'Name';
-$labels['showalarms'] = 'Show alarms';
-$labels['import'] = 'Import';
+$labels['showalarms'] = 'Erinnerungen anzeigen';
+$labels['import'] = 'Importieren';
// date words
-$labels['on'] = 'on';
-$labels['at'] = 'at';
-$labels['this'] = 'this';
-$labels['next'] = 'next';
+$labels['on'] = 'am';
+$labels['at'] = 'um';
+$labels['this'] = 'diesen';
+$labels['next'] = 'nächsten';
// mesages
$labels['savingdata'] = 'Daten werden gespeichert...';
-$labels['errorsaving'] = 'Failed to save data.';
-$labels['notasksfound'] = 'No tasks found for the given criteria';
-$labels['invalidstartduedates'] = 'Start date must not be greater than due date.';
-$labels['deletetasktconfirm'] = 'Do you really want to delete this task?';
-$labels['deleteparenttasktconfirm'] = 'Do you really want to delete this task and all its subtasks?';
-$labels['deletelistconfirm'] = 'Do you really want to delete this list with all its tasks?';
+$labels['errorsaving'] = 'Fehler beim Speichern.';
+$labels['notasksfound'] = 'Für die aktuellen Kriterien wurden keine Aufgaben gefunden.';
+$labels['invalidstartduedates'] = 'Beginn der Aufgabe darf nicht größer als das Enddatum sein.';
+$labels['deletetasktconfirm'] = 'Möchten Sie diese Aufgabe wirklich löschen?';
+$labels['deleteparenttasktconfirm'] = 'Möchten Sie diese Aufgabe inklusive aller Teilaufgaben wirklich löschen?';
+$labels['deletelistconfirm'] = 'Möchten Sie diese Liste mit allen Aufgaben wirklich löschen?';
diff --git a/plugins/tasklist/localization/en.inc b/plugins/tasklist/localization/en.inc
deleted file mode 100644
index 7d5415ab..00000000
--- a/plugins/tasklist/localization/en.inc
+++ /dev/null
@@ -1,68 +0,0 @@
-