diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php index 9c0a8929..5616b28b 100644 --- a/plugins/calendar/calendar.php +++ b/plugins/calendar/calendar.php @@ -2237,6 +2237,37 @@ class calendar extends rcube_plugin $response['select'] = html::tag('input', array('type' => 'hidden', 'name' => 'calendar', 'id' => 'itip-saveto', 'value' => '')); } + // render small agenda view for the respective day + if ($data['method'] == 'REQUEST' && !empty($data['date']) && $response['action'] == 'rsvp') { + $event_start = rcube_utils::anytodatetime($data['date']); + $day_start = new Datetime(gmdate('Y-m-d 00:00', $data['date']), $this->lib->timezone); + $day_end = new Datetime(gmdate('Y-m-d 23:59', $data['date']), $this->lib->timezone); + + // get events on that day from the user's personal calendars + $calendars = $this->driver->list_calendars(false, true); + $events = $this->driver->load_events($day_start->format('U'), $day_end->format('U'), null, array_keys($calendars)); + usort($events, function($a, $b) { return $a['start'] > $b['start'] ? 1 : -1; }); + + $before = $after = array(); + foreach ($events as $event) { + // TODO: skip events with free_busy == 'free' ? + if ($event['uid'] == $data['uid'] || $event['end'] < $day_start || $event['start'] > $day_end) + continue; + else if ($event['start'] < $event_start) + $before[] = $this->mail_agenda_event_row($event); + else + $after[] = $this->mail_agenda_event_row($event); + } + + $response['append'] = array( + 'selector' => '.calendar-agenda-preview', + 'replacements' => array( + '%before%' => !empty($before) ? join("\n", array_slice($before, -3)) : html::div('event-row no-event', $this->gettext('noearlierevents')), + '%after%' => !empty($after) ? join("\n", array_slice($after, 0, 3)) : html::div('event-row no-event', $this->gettext('nolaterevents')), + ), + ); + } + $this->rc->output->command('plugin.update_itip_object_status', $response); } @@ -2338,6 +2369,21 @@ class calendar extends rcube_plugin $hidden = new html_hiddenfield(array('name' => "_t", 'value' => $this->token)); return html::tag('form', array('action' => $this->rc->url(array('task' => 'calendar', 'action' => 'attend')), 'method' => 'post', 'noclose' => true) + $attrib) . $hidden->show(); } + + /** + * + */ + private function mail_agenda_event_row($event, $class = '') + { + $time = $event['all-day'] ? $this->gettext('allday') : + $this->rc->format_date($event['start'], $this->rc->config->get('time_format')) . ' - ' . + $this->rc->format_date($event['end'], $this->rc->config->get('time_format')); + + return html::div(rtrim('event-row ' . $class), + html::span('event-date', $time) . + html::span('event-title', Q($event['title'])) + ); + } /** * @@ -2391,6 +2437,16 @@ class calendar extends rcube_plugin // get prepared inline UI for this event object if ($ical_objects->method) { + $append = ''; + + // prepare a small agenda preview to be filled with actual event data on async request + if ($ical_objects->method == 'REQUEST') { + $append = html::div('calendar-agenda-preview', + html::tag('h3', 'preview-title', $this->gettext('agenda') . ' ' . + html::span('date', $this->rc->format_date($event['start'], $this->rc->config->get('date_format'))) + ) . '%before%' . $this->mail_agenda_event_row($event, 'current') . '%after%'); + } + $html .= html::div('calendar-invitebox', $this->itip->mail_itip_inline_ui( $event, @@ -2399,7 +2455,7 @@ class calendar extends rcube_plugin 'calendar', rcube_utils::anytodatetime($ical_objects->message_date), $this->rc->url(array('task' => 'calendar')) . '&view=agendaDay&date=' . $event['start']->format('U') - ) + ) . $append ); } diff --git a/plugins/calendar/localization/en_US.inc b/plugins/calendar/localization/en_US.inc index 76fbceec..7755aa70 100644 --- a/plugins/calendar/localization/en_US.inc +++ b/plugins/calendar/localization/en_US.inc @@ -202,6 +202,8 @@ $labels['saveincalendar'] = 'save in'; $labels['updatemycopy'] = 'Update in my calendar'; $labels['savetocalendar'] = 'Save to calendar'; $labels['openpreview'] = 'Check Calendar'; +$labels['noearlierevents'] = 'No earlier events'; +$labels['nolaterevents'] = 'No later events'; // resources $labels['resource'] = 'Resource'; diff --git a/plugins/calendar/skins/classic/calendar.css b/plugins/calendar/skins/classic/calendar.css index f7e0c1ca..f8a4fa31 100644 --- a/plugins/calendar/skins/classic/calendar.css +++ b/plugins/calendar/skins/classic/calendar.css @@ -1688,6 +1688,42 @@ div.calendar-invitebox .rsvp-status.delegated { background-position: 2px -180px; } +div.calendar-invitebox .calendar-agenda-preview { + display: none; + border-top: 1px solid #dfdfdf; + margin-top: 1em; + padding-top: 0.6em; +} + +div.calendar-invitebox .calendar-agenda-preview h3.preview-title { + margin: 0 0 0.5em 0; + font-size: 12px; +} + +div.calendar-invitebox .calendar-agenda-preview .event-row { + color: #777; + padding: 2px 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +div.calendar-invitebox .calendar-agenda-preview .event-row.current { + color: #000; + font-weight: bold; +} + +div.calendar-invitebox .calendar-agenda-preview .event-row.no-event { + font-style: italic; +} + +div.calendar-invitebox .calendar-agenda-preview .event-date { + display: inline-block; + min-width: 8em; + margin-right: 1em; + white-space: nowrap; +} + /* iTIP attend reply page */ .calendaritipattend .centerbox { diff --git a/plugins/calendar/skins/larry/calendar.css b/plugins/calendar/skins/larry/calendar.css index 54e0141d..40d79fd5 100644 --- a/plugins/calendar/skins/larry/calendar.css +++ b/plugins/calendar/skins/larry/calendar.css @@ -2110,6 +2110,44 @@ div.calendar-invitebox .rsvp-status.needs-action { background-position: 2px 0; } +div.calendar-invitebox .calendar-agenda-preview { + display: none; + border-top: 1px solid #dfdfdf; + margin-top: 1em; + padding-top: 0.6em; +} + +div.calendar-invitebox .calendar-agenda-preview h3.preview-title { + margin: 0 0 0.5em 0; + font-size: 12px; + color: #333; +} + +div.calendar-invitebox .calendar-agenda-preview .event-row { + color: #777; + padding: 2px 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +div.calendar-invitebox .calendar-agenda-preview .event-row.current { + color: #333; + font-weight: bold; +} + +div.calendar-invitebox .calendar-agenda-preview .event-row.no-event { + font-style: italic; +} + +div.calendar-invitebox .calendar-agenda-preview .event-date { + display: inline-block; + min-width: 8em; + margin-right: 1em; + white-space: nowrap; +} + + /* iTIP attend reply page */ .calendaritipattend .centerbox { diff --git a/plugins/libcalendaring/lib/libcalendaring_itip.php b/plugins/libcalendaring/lib/libcalendaring_itip.php index a5b056f7..6740bfa5 100644 --- a/plugins/libcalendaring/lib/libcalendaring_itip.php +++ b/plugins/libcalendaring/lib/libcalendaring_itip.php @@ -434,6 +434,9 @@ class libcalendaring_itip $emails = $this->lib->get_user_emails(); $title = $event['sequence'] > 0 ? $this->gettext('itipupdate') : $this->gettext('itipinvitation'); $metadata['rsvp'] = true; + if (is_object($event['start'])) { + $metadata['date'] = $event['start']->format('U'); + } // check for X-KOLAB-INVITATIONTYPE property and only show accept/decline buttons if (self::get_custom_property($event, 'X-KOLAB-INVITATIONTYPE') == 'CONFIRMATION') { diff --git a/plugins/libcalendaring/libcalendaring.js b/plugins/libcalendaring/libcalendaring.js index b23a3fee..5b8ad5cd 100644 --- a/plugins/libcalendaring/libcalendaring.js +++ b/plugins/libcalendaring/libcalendaring.js @@ -912,6 +912,20 @@ rcube_libcalendaring.update_itip_object_status = function(p) // show rsvp/import buttons (with calendar selector) $('#'+p.action+'-'+p.id).show().find('input.button').last().after(p.select); + + // show itip box appendix after replacing the given placeholders + if (p.append && p.append.selector) { + var elem = $(p.append.selector); + if (p.append.replacements) { + $.each(p.append.replacements, function(k, html) { + elem.html(elem.html().replace(k, html)); + }); + } + else if (p.append.html) { + elem.html(p.append.html) + } + elem.show(); + } }; /**