From be8f50c46490f89b470fa5cdc763b2f3014f6af8 Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 29 Aug 2011 00:54:10 +0200 Subject: [PATCH] Display invitation status from attached file if event is not yet in calendar; send decline reply when deleting an event; update German translations --- plugins/calendar/calendar.php | 56 ++++++++++++++----- plugins/calendar/calendar_ui.js | 41 ++++++++++---- .../drivers/database/database_driver.php | 4 +- .../calendar/drivers/database/sql/mysql.sql | 3 +- plugins/calendar/localization/de_CH.inc | 22 +++++++- plugins/calendar/localization/de_DE.inc | 23 +++++++- plugins/calendar/localization/en_US.inc | 1 + 7 files changed, 121 insertions(+), 29 deletions(-) diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php index 029fa2a2..de652bae 100644 --- a/plugins/calendar/calendar.php +++ b/plugins/calendar/calendar.php @@ -530,7 +530,7 @@ class calendar extends rcube_plugin unset($event['notify']); // read old event data in order to find changes - if ($event['notify'] && $action != 'new') + if (($event['notify'] || $event['decline']) && $action != 'new') $old = $this->driver->get_event($event); switch ($action) { @@ -584,6 +584,22 @@ class calendar extends rcube_plugin $got_msg = true; } + // send iTIP reply that participant has declined the event + if ($success && $event['decline']) { + $emails = $this->get_user_emails(); + foreach ($old['attendees'] as $i => $attendee) { + if ($attendee['role'] == 'ORGANIZER') + $organizer = $attendee; + else if ($attendee['email'] && in_array($attendee['email'], $emails)) { + $old['attendees'][$i]['status'] = 'DECLINED'; + } + } + + if ($organizer && $this->send_itip_message($old, 'REPLY', $organizer, 'itipsubjectdeclined', 'itipmailbodydeclined')) + $this->rc->output->command('display_message', $this->gettext(array('name' => 'sentresponseto', 'vars' => array('mailto' => $organizer['name']))), 'confirmation'); + else + $this->rc->output->command('display_message', $this->gettext('itipresponseerror'), 'error'); + } break; case "undo": @@ -603,8 +619,8 @@ class calendar extends rcube_plugin break; case "rsvp-status": - $status = 'unknown'; $action = 'rsvp'; + $status = $event['fallback']; $html = html::div('rsvp-status', $this->gettext('acceptinvitation')); $this->load_driver(); if ($existing = $this->driver->get_event($event)) { @@ -615,12 +631,14 @@ class calendar extends rcube_plugin break; } } - - if (in_array($status, array('ACCEPTED','TENTATIVE','DECLINED'))) { - $html = html::div('rsvp-status ' . strtolower($status), $this->gettext('youhave'.strtolower($status))); - if ($event['changed'] < $existing['changed']) { - $action = ''; - } + } + else + $action = 'import'; + + if (in_array($status, array('ACCEPTED','TENTATIVE','DECLINED'))) { + $html = html::div('rsvp-status ' . strtolower($status), $this->gettext('youhave'.strtolower($status))); + if ($existing['changed'] && $event['changed'] < $existing['changed']) { + $action = ''; } } @@ -1305,12 +1323,12 @@ class calendar extends rcube_plugin // check for organizer in attendees if ($event['attendees']) { - $identity = $this->rc->user->get_identity(); + $emails = $this->get_user_emails(); $organizer = $owner = false; foreach ($event['attendees'] as $i => $attendee) { if ($attendee['role'] == 'ORGANIZER') $organizer = true; - if ($attendee['email'] == $identity['email']) + if ($attendee['email'] == in_array($attendee['email'], $emails)) $owner = $i; else if (!isset($attendee['rsvp'])) $event['attendees'][$i]['rsvp'] = true; @@ -1321,7 +1339,7 @@ class calendar extends rcube_plugin $event['attendees'][$owner]['role'] = 'ORGANIZER'; unset($event['attendees'][$owner]['rsvp']); } - else if (!$organizer && $identity['email'] && $action == 'new') { + else if (!$organizer && $action == 'new' && ($identity = $this->rc->user->get_identity()) && $identity['email']) { array_unshift($event['attendees'], array('role' => 'ORGANIZER', 'name' => $identity['name'], 'email' => $identity['email'], 'status' => 'ACCEPTED')); } } @@ -1348,6 +1366,8 @@ class calendar extends rcube_plugin $event['cancelled'] = true; $is_cancelled = true; } + + $emails = $this->get_user_emails(); // compose multipart message using PEAR:Mail_Mime $method = $action == 'remove' ? 'CANCEL' : 'REQUEST'; @@ -1363,7 +1383,7 @@ class calendar extends rcube_plugin $sent = 0; foreach ((array)$event['attendees'] as $attendee) { // skip myself for obvious reasons - if (!$attendee['email'] || $attendee['email'] == $myself['email']) + if (!$attendee['email'] || in_array($attendee['email'], $emails)) continue; // which template to use for mail text @@ -1606,6 +1626,7 @@ class calendar extends rcube_plugin )); } else if ($this->ical->method == 'REQUEST') { + $emails = $this->get_user_emails(); $title = $event['SEQUENCE'] > 0 ? $this->gettext('itipupdate') : $this->gettext('itipinvitation'); // add (hidden) buttons and activate them from asyncronous request @@ -1624,12 +1645,21 @@ class calendar extends rcube_plugin 'value' => $this->gettext('importtocalendar'), )); + // check my status + $status = 'NEEDS-ACTION'; + foreach ($event['attendees'] as $i => $attendee) { + if ($attendee['email'] && in_array($attendee['email'], $emails)) { + $status = strtoupper($attendee['status']); + break; + } + } + $dom_id = asciiwords($event['uid'], true); $buttons = html::div(array('id' => 'rsvp-'.$dom_id, 'style' => 'display:none'), $rsvp_buttons); $buttons .= html::div(array('id' => 'import-'.$dom_id, 'style' => 'display:none'), $import_button); $buttons_pre = html::div(array('id' => 'loading-'.$dom_id, 'class' => 'rsvp-status loading'), $this->gettext('loading')); - $this->rc->output->add_script('rcube_calendar.fetch_event_rsvp_status(' . json_serialize(array('uid' => $event['uid'], 'changed' => $event['changed'])) . ')', 'docready'); + $this->rc->output->add_script('rcube_calendar.fetch_event_rsvp_status(' . json_serialize(array('uid' => $event['uid'], 'changed' => $event['changed'], 'fallback' => $status)) . ')', 'docready'); } else if ($this->ical->method == 'CANCEL') { $title = $this->gettext('itipcancellation'); diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js index 05f440bd..e9bbed5e 100644 --- a/plugins/calendar/calendar_ui.js +++ b/plugins/calendar/calendar_ui.js @@ -143,14 +143,20 @@ function rcube_calendar_ui(settings) return (event.attendees && (event.attendees.length > 1 || event.attendees[0].email != settings.identity.email)); }; + // check if the current user is an attendee of this event + var is_attendee = function(event, role) + { + 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) + return true; + } + return false; + }; + // check if the current user is the organizer var is_organizer = function(event) { - for (var i=0; event.attendees && i < event.attendees.length; i++) { - if (event.attendees[i].role == 'ORGANIZER' && event.attendees[i].email && event.attendees[i].email == settings.identity.email) - return true; - } - return !event.id; + return is_attendee(event, 'ORGANIZER') || !event.id; }; // create a nice human-readable string for the date/time range @@ -301,8 +307,8 @@ function rcube_calendar_ui(settings) dispname = '' + dispname + ''; if (data.role == 'ORGANIZER') organizer = true; - else if (data.status == 'NEEDS-ACTION' || data.status == 'TENTATIVE' && settings.identity.emails.indexOf(';'+data.email) >= 0) - rsvp = true; + else if ((data.status == 'NEEDS-ACTION' || data.status == 'TENTATIVE') && data.email && settings.identity.emails.indexOf(';'+data.email) >= 0) + rsvp = data.status.toLowerCase(); } html += '' + dispname + ' '; @@ -321,6 +327,7 @@ function rcube_calendar_ui(settings) } $('#event-rsvp')[(rsvp?'show':'hide')](); + $('#event-rsvp .rsvp-buttons input').prop('disabled', false).filter('input[rel='+rsvp+']').prop('disabled', true); } var buttons = {}; @@ -1456,17 +1463,24 @@ function rcube_calendar_ui(settings) var update_event_confirm = function(action, event, data) { if (!data) data = event; - var notify = false, html = ''; + var decline = false, notify = false, html = ''; // event has attendees, ask whether to notify them if (has_attendees(event)) { if (is_organizer(event)) { notify = true; html += '
' + - '
'; } + else if (action == 'remove' && is_attendee(event)) { + decline = true; + html += '
' + + '
'; + } else { html += '
' + rcmail.gettext('localchangeswarning', 'calendar') + '
'; } @@ -1492,6 +1506,8 @@ function rcube_calendar_ui(settings) data.savemode = String(this.href).replace(/.+#/, ''); if ($dialog.find('input.confirm-attendees-donotify').get(0)) data.notify = notify && $dialog.find('input.confirm-attendees-donotify').get(0).checked ? 1 : 0; + if (decline && $dialog.find('input.confirm-attendees-decline:checked')) + data.decline = 1; update_event(action, data); $dialog.dialog("destroy").hide(); return false; @@ -1509,6 +1525,7 @@ function rcube_calendar_ui(settings) text: rcmail.gettext((action == 'remove' ? 'remove' : 'save'), 'calendar'), click: function() { data.notify = notify && $dialog.find('input.confirm-attendees-donotify').get(0).checked ? 1 : 0; + data.decline = decline && $dialog.find('input.confirm-attendees-decline:checked').length ? 1 : 0; update_event(action, data); $(this).dialog("close"); } @@ -1571,7 +1588,7 @@ function rcube_calendar_ui(settings) // delete the given event after showing a confirmation dialog this.delete_event = function(event) { // show confirm dialog for recurring events, use jquery UI dialog - return update_event_confirm('remove', event, { id:event.id, calendar:event.calendar }); + return update_event_confirm('remove', event, { id:event.id, calendar:event.calendar, attendees:event.attendees }); }; // opens a jquery UI dialog with event properties (or empty for creating a new calendar) @@ -2181,8 +2198,10 @@ function rcube_calendar_ui(settings) if (freebusy_ui.needsupdate && me.selected_event) update_freebusy_status(me.selected_event); // add current user as organizer if non added yet - if (!event_attendees.length) + if (!event_attendees.length) { add_attendee($.extend({ role:'ORGANIZER' }, settings.identity)); + $('#edit-attendees-form .attendees-invitebox').show(); + } } } }); diff --git a/plugins/calendar/drivers/database/database_driver.php b/plugins/calendar/drivers/database/database_driver.php index eafbfdc8..3387b9ab 100644 --- a/plugins/calendar/drivers/database/database_driver.php +++ b/plugins/calendar/drivers/database/database_driver.php @@ -640,8 +640,8 @@ class database_driver extends calendar_driver */ public function get_event($event) { - $id = is_array($event) ? $event['id'] : $event; - $col = is_numeric($event['id']) ? 'event_id' : 'uid'; + $id = is_array($event) ? ($event['id'] ? $event['id'] : $event['uid']) : $event; + $col = $event['id'] && is_numeric($event['id']) ? 'event_id' : 'uid'; if ($this->cache[$id]) return $this->cache[$id]; diff --git a/plugins/calendar/drivers/database/sql/mysql.sql b/plugins/calendar/drivers/database/sql/mysql.sql index e036e014..da5b8747 100644 --- a/plugins/calendar/drivers/database/sql/mysql.sql +++ b/plugins/calendar/drivers/database/sql/mysql.sql @@ -45,7 +45,8 @@ CREATE TABLE `events` ( `attendees` text DEFAULT NULL, `notifyat` datetime DEFAULT NULL, PRIMARY KEY(`event_id`), - INDEX `recurrence_idx` (`recurrence_id`), + INDEX `uid_idx` (`uid`,`calendar_id`), + INDEX `recurrence_idx` (`recurrence_id`), CONSTRAINT `fk_events_calendar_id` FOREIGN KEY (`calendar_id`) REFERENCES `calendars`(`calendar_id`) ON DELETE CASCADE ON UPDATE CASCADE ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; diff --git a/plugins/calendar/localization/de_CH.inc b/plugins/calendar/localization/de_CH.inc index d6f281f0..88dd8ba0 100644 --- a/plugins/calendar/localization/de_CH.inc +++ b/plugins/calendar/localization/de_CH.inc @@ -122,9 +122,26 @@ $labels['eventcancelsubject'] = '"$title" wurde abgesagt'; $labels['eventcancelmailbody'] = "*\$title*\n\nWann: \$date\n\nTeilnehmer: \$attendees\n\nDer Termin wurde von \$organizer abgesagt.\n\nIm Anhang finden Sie eine iCalendar-Datei mit den Termindaten."; // invitation handling -$labels['itipreply'] = 'Antwort zu '; +$labels['itipinvitation'] = 'Einladung zu'; +$labels['itipreply'] = 'Antwort zu'; +$labels['itipupdate'] = 'Aktialisiert:'; +$labels['itipcancellation'] = 'Abgesagt:'; +$labels['itipaccepted'] = 'Akzeptieren'; +$labels['itiptentative'] = 'Mit Vorbehalt'; +$labels['itipdeclined'] = 'Ablehnen'; +$labels['itipsubjectaccepted'] = 'Einladung zu "$title" wurde von $name angenommen'; +$labels['itipsubjecttentative'] = 'Einladung zu "$title" wurde von $name mit Vorbehalt angenommen'; +$labels['itipsubjectdeclined'] = 'Einladung zu "$title" wurde von $name abgelehnt'; +$labels['itipmailbodyaccepted'] = "\$sender hat die Einladung zum folgenden Termin angenommen:\n\n*\$title*\n\nWann: \$date\n\nTeilnehmer: \$attendees"; +$labels['itipmailbodytentative'] = "\$sender hat die Einladung mit Vorbehalt zum folgenden Termin angenommen:\n\n*\$title*\n\nWann: \$date\n\nTeilnehmer: \$attendees"; +$labels['itipmailbodydeclined'] = "\$sender hat die Einladung zum folgenden Termin abgelehnt:\n\n*\$title*\n\nWann: \$date\n\nTeilnehmer: \$attendees"; +$labels['itipdeclineevent'] = 'Möchten Sie die Einladung zu diesem Termin ablehnen?'; $labels['importtocalendar'] = 'In Kalender übernehmen'; $labels['updateattendeestatus'] = 'Teilnehmerstatus aktualisieren'; +$labels['acceptinvitation'] = 'Möchten Sie die Einladung zu diesem Termin annehmen?'; +$labels['youhaveaccepted'] = 'Sie haben die Einladung angenommen'; +$labels['youhavetentative'] = 'Sie haben die Einladung mit Vorbehalt angenommen'; +$labels['youhavedeclined'] = 'Sie haben die Einladung abgelehnt'; // event dialog tabs $labels['tabsummary'] = 'Übersicht'; @@ -149,6 +166,9 @@ $labels['newerversionexists'] = 'Eine neuere Version dieses Termins exisitert be $labels['nowritecalendarfound'] = 'Kein Kalender zum Speichern gefunden'; $labels['importedsuccessfully'] = 'Der Termin wurde erfolgreich in \'$calendar\' gespeichert'; $labels['attendeupdateesuccess'] = 'Teilnehmerstatus erfolgreich aktualisiert'; +$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.'; // recurrence form $labels['repeat'] = 'Wiederholung'; diff --git a/plugins/calendar/localization/de_DE.inc b/plugins/calendar/localization/de_DE.inc index fd37db30..8d26448d 100644 --- a/plugins/calendar/localization/de_DE.inc +++ b/plugins/calendar/localization/de_DE.inc @@ -121,9 +121,26 @@ $labels['eventcancelsubject'] = '"$title" wurde abgesagt'; $labels['eventcancelmailbody'] = "*\$title*\n\nWann: \$date\n\nTeilnehmer: \$attendees\n\nDer Termin wurde von \$organizer abgesagt.\n\nIm Anhang finden Sie eine iCalendar-Datei mit den Termindaten."; // invitation handling -$labels['itipreply'] = 'Antwort zu '; +$labels['itipinvitation'] = 'Einladung zu'; +$labels['itipreply'] = 'Antwort zu'; +$labels['itipupdate'] = 'Aktialisiert:'; +$labels['itipcancellation'] = 'Abgesagt:'; +$labels['itipaccepted'] = 'Akzeptieren'; +$labels['itiptentative'] = 'Mit Vorbehalt'; +$labels['itipdeclined'] = 'Ablehnen'; +$labels['itipsubjectaccepted'] = 'Einladung zu "$title" wurde von $name angenommen'; +$labels['itipsubjecttentative'] = 'Einladung zu "$title" wurde von $name mit Vorbehalt angenommen'; +$labels['itipsubjectdeclined'] = 'Einladung zu "$title" wurde von $name abgelehnt'; +$labels['itipmailbodyaccepted'] = "\$sender hat die Einladung zum folgenden Termin angenommen:\n\n*\$title*\n\nWann: \$date\n\nTeilnehmer: \$attendees"; +$labels['itipmailbodytentative'] = "\$sender hat die Einladung mit Vorbehalt zum folgenden Termin angenommen:\n\n*\$title*\n\nWann: \$date\n\nTeilnehmer: \$attendees"; +$labels['itipmailbodydeclined'] = "\$sender hat die Einladung zum folgenden Termin abgelehnt:\n\n*\$title*\n\nWann: \$date\n\nTeilnehmer: \$attendees"; +$labels['itipdeclineevent'] = 'Möchten Sie die Einladung zu diesem Termin ablehnen?'; $labels['importtocalendar'] = 'In Kalender übernehmen'; $labels['updateattendeestatus'] = 'Teilnehmerstatus aktualisieren'; +$labels['acceptinvitation'] = 'Möchten Sie die Einladung zu diesem Termin annehmen?'; +$labels['youhaveaccepted'] = 'Sie haben die Einladung angenommen'; +$labels['youhavetentative'] = 'Sie haben die Einladung mit Vorbehalt angenommen'; +$labels['youhavedeclined'] = 'Sie haben die Einladung abgelehnt'; // event dialog tabs $labels['tabsummary'] = 'Übersicht'; @@ -147,6 +164,10 @@ $labels['errorimportingevent'] = 'Fehler beim Importieren'; $labels['newerversionexists'] = 'Eine neuere Version dieses Termins exisitert bereits! Import abgebrochen.'; $labels['nowritecalendarfound'] = 'Kein Kalender zum Speichern gefunden'; $labels['importedsuccessfully'] = 'Der Termin wurde erfolgreich in \'$calendar\' gespeichert'; +$labels['attendeupdateesuccess'] = 'Teilnehmerstatus erfolgreich aktualisiert'; +$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.'; // recurrence form $labels['repeat'] = 'Wiederholung'; diff --git a/plugins/calendar/localization/en_US.inc b/plugins/calendar/localization/en_US.inc index cca5b592..1388442d 100644 --- a/plugins/calendar/localization/en_US.inc +++ b/plugins/calendar/localization/en_US.inc @@ -135,6 +135,7 @@ $labels['itipsubjectdeclined'] = '"$title" has been declined by $name'; $labels['itipmailbodyaccepted'] = "\$sender has accepted the invitation to the following event:\n\n*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees"; $labels['itipmailbodytentative'] = "\$sender has tentatively accepted the invitation to the following event:\n\n*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees"; $labels['itipmailbodydeclined'] = "\$sender has declined the invitation to the following event:\n\n*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees"; +$labels['itipdeclineevent'] = 'Do you want to decline your invitation to this event?'; $labels['importtocalendar'] = 'Save to my calendar'; $labels['updateattendeestatus'] = 'Update the participant\'s status'; $labels['acceptinvitation'] = 'Do you accept this invitation?';