List virtual calendars showing pending/declined inivtations (#1796)

This commit is contained in:
Thomas Bruederli 2014-07-08 12:36:34 +02:00
parent bdf2faafae
commit 7affe524f1
14 changed files with 561 additions and 25 deletions

View file

@ -234,6 +234,9 @@ class calendar extends rcube_plugin
if (!$this->itip) {
require_once($this->home . '/lib/calendar_itip.php');
$this->itip = new calendar_itip($this);
if ($this->rc->config->get('kolab_invitation_calendars'))
$this->itip->set_rsvp_actions(array('accepted','tentative','declined','needs-action'));
}
return $this->itip;
@ -883,12 +886,13 @@ class calendar extends rcube_plugin
break;
case "rsvp":
$status = get_input_value('status', RCUBE_INPUT_GPC);
$ev = $this->driver->get_event($event);
$ev['attendees'] = $event['attendees'];
$event = $ev;
if ($success = $this->driver->edit_event($event)) {
$status = get_input_value('status', RCUBE_INPUT_GPC);
if ($success = $this->driver->edit_rsvp($event, $status)) {
$reload = $event['calendar'] != $ev['calendar'] ? 2 : 1;
$organizer = null;
foreach ($event['attendees'] as $i => $attendee) {
if ($attendee['role'] == 'ORGANIZER') {
@ -947,7 +951,7 @@ class calendar extends rcube_plugin
if ($reload > 1)
$args['refetch'] = true;
else if ($success && $action != 'remove')
$args['update'] = $this->_client_event($this->driver->get_event($event));
$args['update'] = $this->_client_event($this->driver->get_event($event), true);
$this->rc->output->command('plugin.refresh_calendar', $args);
}
}
@ -1314,6 +1318,7 @@ class calendar extends rcube_plugin
$settings['event_coloring'] = (int)$this->rc->config->get('calendar_event_coloring', $this->defaults['calendar_event_coloring']);
$settings['time_indicator'] = (int)$this->rc->config->get('calendar_time_indicator', $this->defaults['calendar_time_indicator']);
$settings['invite_shared'] = (int)$this->rc->config->get('calendar_allow_invite_shared', $this->defaults['calendar_allow_invite_shared']);
$settings['invitation_calendars'] = (bool)$this->rc->config->get('kolab_invitation_calendars', false);
// get user identity to create default attendee
if ($this->ui->screen == 'calendar') {
@ -2398,7 +2403,7 @@ class calendar extends rcube_plugin
else
$error_msg = $this->gettext('newerversionexists');
}
else if (!$existing && $status != 'declined') {
else if (!$existing && ($status != 'declined' || $this->rc->config->get('kolab_invitation_calendars'))) {
$success = $this->driver->new_event($event);
}
else if ($status == 'declined')
@ -2609,7 +2614,7 @@ class calendar extends rcube_plugin
/**
* Get a list of email addresses of the current user (from login and identities)
*/
private function get_user_emails()
public function get_user_emails()
{
return $this->lib->get_user_emails();
}

View file

@ -384,6 +384,9 @@ function rcube_calendar_ui(settings)
var calendar = event.calendar && me.calendars[event.calendar] ? me.calendars[event.calendar] : { editable:false };
me.selected_event = event;
if ($dialog.is(':ui-dialog'))
$dialog.dialog('close');
// allow other plugins to do actions when event form is opened
rcmail.triggerEvent('calendar-event-init', {o: event});
@ -408,7 +411,7 @@ function rcube_calendar_ui(settings)
$('#event-alarm').show().children('.event-text').html(Q(event.alarms_text));
if (calendar.name)
$('#event-calendar').show().children('.event-text').html(Q(calendar.name)).attr('class', 'event-text').addClass('cal-'+calendar.id);
$('#event-calendar').show().children('.event-text').html(Q(calendar.name)).attr('class', 'event-text cal-'+calendar.id).css('color', calendar.textColor || calendar.color || '');
if (event.categories)
$('#event-category').show().children('.event-text').html(Q(event.categories)).attr('class', 'event-text cat-'+String(event.categories).toLowerCase().replace(rcmail.identifier_expr, ''));
if (event.free_busy)
@ -1928,8 +1931,14 @@ function rcube_calendar_ui(settings)
event_show_dialog(me.selected_event);
// submit status change to server
me.saving_lock = rcmail.set_busy(true, 'calendar.savingdata');
rcmail.http_post('event', { action:'rsvp', e:me.selected_event, status:response });
var submit_data = $.extend({}, me.selected_event, { source:null });
if (settings.invitation_calendars) {
update_event('rsvp', submit_data, { status:response });
}
else {
me.saving_lock = rcmail.set_busy(true, 'calendar.savingdata');
rcmail.http_post('event', { action:'rsvp', e:submit_data, status:response });
}
}
};
@ -1962,10 +1971,10 @@ function rcube_calendar_ui(settings)
}
// post the given event data to server
var update_event = function(action, data)
var update_event = function(action, data, add)
{
me.saving_lock = rcmail.set_busy(true, 'calendar.savingdata');
rcmail.http_post('calendar/event', { action:action, e:data });
rcmail.http_post('calendar/event', $.extend({ action:action, e:data }, (add || {})));
// render event temporarily into the calendar
if ((data.start && data.end) || data.id) {

View file

@ -127,6 +127,9 @@ $rcmail_config['calendar_itip_smtp_user'] = 'smtpauth';
// SMTP password used to send (anonymous) itip messages
$rcmail_config['calendar_itip_smtp_pass'] = '123456';
// show virtual invitation calendars (Kolab driver only)
$rcmail_config['kolab_invitation_calendars'] = true;
// Base URL to build fully qualified URIs to access calendars via CALDAV
// The following replacement variables are supported:
// %h - Current HTTP host

View file

@ -190,6 +190,18 @@ abstract class calendar_driver
*/
abstract function edit_event($event);
/**
* Extended event editing with possible changes to the argument
*
* @param array Hash array with event properties
* @param string New participant status
* @return boolean True on success, False on error
*/
public function edit_rsvp(&$event, $status)
{
return $this->edit_event($event);
}
/**
* Move a single event
*

View file

@ -138,7 +138,7 @@ class database_driver extends calendar_driver
'color' => $prefs['color'],
'showalarms' => (bool)$this->rc->config->get('calendar_birthdays_alarm_type'),
'active' => !in_array($id, $hidden),
'group' => 'birthdays',
'group' => 'x-birthdays',
'readonly' => true,
'default' => false,
'children' => false,

View file

@ -209,9 +209,10 @@ class kolab_calendar extends kolab_storage_folder_api
* @param string Search query (optional)
* @param boolean Include virtual events (optional)
* @param array Additional parameters to query storage
* @param array Additional query to filter events
* @return array A list of event records
*/
public function list_events($start, $end, $search = null, $virtual = 1, $query = array())
public function list_events($start, $end, $search = null, $virtual = 1, $query = array(), $filter_query = null)
{
// convert to DateTime for comparisons
try {
@ -227,10 +228,24 @@ class kolab_calendar extends kolab_storage_folder_api
$end = new DateTime('today +10 years');
}
// get email addresses of the current user
$user_emails = $this->cal->get_user_emails();
// query Kolab storage
$query[] = array('dtstart', '<=', $end);
$query[] = array('dtend', '>=', $start);
// add query to exclude pending/declined invitations
if (empty($filter_query)) {
foreach ($user_emails as $email) {
$query[] = array('tags', '!=', 'x-partstat:' . $email . ':needs-action');
$query[] = array('tags', '!=', 'x-partstat:' . $email . ':declined');
}
}
else if (is_array($filter_query)) {
$query = array_merge($query, $filter_query);
}
if (!empty($search)) {
$search = mb_strtolower($search);
foreach (rcube_utils::normalize_string($search, true) as $word) {
@ -240,6 +255,15 @@ class kolab_calendar extends kolab_storage_folder_api
$events = array();
foreach ($this->storage->select($query) as $record) {
// post-filter events to skip pending and declined invitations
if (empty($filter_query) && is_array($record['attendees'])) {
foreach ($record['attendees'] as $attendee) {
if (in_array($attendee['email'], $user_emails) && in_array($attendee['status'], array('NEEDS-ACTION','DECLINED'))) {
continue 2;
}
}
}
$event = $this->_to_rcube_event($record);
$this->events[$event['id']] = $event;
@ -671,7 +695,7 @@ class kolab_calendar extends kolab_storage_folder_api
}
// remove some internal properties which should not be saved
unset($event['_savemode'], $event['_fromcalendar'], $event['_identity']);
unset($event['_savemode'], $event['_fromcalendar'], $event['_identity'], $event['_folder_id'], $event['className']);
// copy meta data (starting with _) from old object
foreach ((array)$old as $key => $val) {

View file

@ -25,9 +25,14 @@
require_once(dirname(__FILE__) . '/kolab_calendar.php');
require_once(dirname(__FILE__) . '/kolab_user_calendar.php');
require_once(dirname(__FILE__) . '/kolab_invitation_calendar.php');
class kolab_driver extends calendar_driver
{
const INVITATIONS_CALENDAR_PENDING = '--invitation--pending';
const INVITATIONS_CALENDAR_DECLINED = '--invitation--declined';
// features this backend supports
public $alarms = true;
public $attendees = true;
@ -202,6 +207,35 @@ class kolab_driver extends calendar_driver
}
}
// list virtual calendars showing invitations
if ($this->rc->config->get('kolab_invitation_calendars')) {
foreach (array(self::INVITATIONS_CALENDAR_PENDING, self::INVITATIONS_CALENDAR_DECLINED) as $id) {
$cal = new kolab_invitation_calendar($id, $this->cal);
$this->calendars[$cal->id] = $cal;
if (!$active || $cal->is_active()) {
$calendars[$id] = array(
'id' => $cal->id,
'name' => $cal->get_name(),
'listname' => $cal->get_name(),
'editname' => $cal->get_foldername(),
'title' => $cal->get_title(),
'color' => $cal->get_color(),
'readonly' => $cal->readonly,
'showalarms' => $cal->alarms,
'group' => 'x-invitations',
'default' => false,
'active' => $cal->is_active(),
'owner' => $cal->get_owner(),
'children' => false,
);
if (is_object($tree)) {
$tree->children[] = $cal;
}
}
}
}
// append the virtual birthdays calendar
if ($this->rc->config->get('calendar_contact_birthdays', false)) {
$id = self::BIRTHDAY_CALENDAR_ID;
@ -214,7 +248,7 @@ class kolab_driver extends calendar_driver
'color' => $prefs[$id]['color'],
'active' => $prefs[$id]['active'],
'showalarms' => (bool)$this->rc->config->get('calendar_birthdays_alarm_type'),
'group' => 'birthdays',
'group' => 'x-birthdays',
'readonly' => true,
'default' => false,
'children' => false,
@ -273,10 +307,13 @@ class kolab_driver extends calendar_driver
* @param string Calendar identifier (encoded imap folder name)
* @return object kolab_calendar Object nor null if calendar doesn't exist
*/
protected function get_calendar($id)
public function get_calendar($id)
{
// create calendar object if necesary
if (!$this->calendars[$id] && $id !== self::BIRTHDAY_CALENDAR_ID) {
if (!$this->calendars[$id] && in_array($id, array(self::INVITATIONS_CALENDAR_PENDING, self::INVITATIONS_CALENDAR_DECLINED))) {
$this->calendars[$id] = new kolab_invitation_calendar($id, $this->cal);
}
else if (!$this->calendars[$id] && $id !== self::BIRTHDAY_CALENDAR_ID) {
$calendar = kolab_calendar::factory($id, $this->cal);
if ($calendar->ready)
$this->calendars[$calendar->id] = $calendar;
@ -363,7 +400,7 @@ class kolab_driver extends calendar_driver
*/
public function subscribe_calendar($prop)
{
if ($prop['id'] && ($cal = $this->get_calendar($prop['id']))) {
if ($prop['id'] && ($cal = $this->get_calendar($prop['id'])) && is_object($cal->storage)) {
$ret = false;
if (isset($prop['permanent']))
$ret |= $cal->storage->subscribe(intval($prop['permanent']));
@ -452,6 +489,7 @@ class kolab_driver extends calendar_driver
// don't list the birthday calendar
$this->rc->config->set('calendar_contact_birthdays', false);
$this->rc->config->set('kolab_invitation_calendars', false);
return $this->list_calendars();
}
@ -535,6 +573,29 @@ class kolab_driver extends calendar_driver
return $this->update_event($event);
}
/**
* Extended event editing with possible changes to the argument
*
* @param array Hash array with event properties
* @param string New participant status
* @return boolean True on success, False on error
*/
public function edit_rsvp(&$event, $status)
{
if (($ret = $this->update_event($event)) && $this->rc->config->get('kolab_invitation_calendars')) {
// re-assign to the according (virtual) calendar
if (strtoupper($status) == 'DECLINED')
$event['calendar'] = self::INVITATIONS_CALENDAR_DECLINED;
else if (strtoupper($status) == 'NEEDS-ACTION')
$event['calendar'] = self::INVITATIONS_CALENDAR_PENDING;
else if ($event['_folder_id'])
$event['calendar'] = $event['_folder_id'];
}
return $ret;
}
/**
* Move a single event
*
@ -580,6 +641,7 @@ class kolab_driver extends calendar_driver
{
$success = false;
$savemode = $event['_savemode'];
$decline = $event['decline'];
if (($storage = $this->get_calendar($event['calendar'])) && ($event = $storage->get_event($event['id']))) {
$event['_savemode'] = $savemode;
@ -664,7 +726,15 @@ class kolab_driver extends calendar_driver
}
default: // 'all' is default
$success = $storage->delete_event($master, $force);
if ($decline && $this->rc->config->get('kolab_invitation_calendars')) {
// don't delete but set PARTSTAT=DECLINED
if ($this->cal->lib->set_partstat($master, 'DECLINED')) {
$success = $storage->update_event($master);
}
}
if (!$success)
$success = $storage->delete_event($master, $force);
break;
}
}
@ -1227,7 +1297,9 @@ class kolab_driver extends calendar_driver
public function calendar_form($action, $calendar, $formfields)
{
// show default dialog for birthday calendar
if ($calendar['id'] == self::BIRTHDAY_CALENDAR_ID) {
if (in_array($calendar['id'], array(self::BIRTHDAY_CALENDAR_ID, self::INVITATIONS_CALENDAR_PENDING, self::INVITATIONS_CALENDAR_DECLINED))) {
if ($calendar['id'] != self::BIRTHDAY_CALENDAR_ID)
unset($formfields['showalarms']);
return parent::calendar_form($action, $calendar, $formfields);
}

View file

@ -0,0 +1,324 @@
<?php
/**
* Kolab calendar storage class simulating a virtual calendar listing pedning/declined invitations
*
* @author Thomas Bruederli <bruederli@kolabsys.com>
*
* Copyright (C) 2014, 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 by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class kolab_invitation_calendar
{
public $id = '__invitation__';
public $ready = true;
public $alarms = false;
public $readonly = true;
public $attachments = false;
public $subscriptions = false;
public $partstats = array('unknown');
public $categories = array();
public $name = 'Invitations';
/**
* Default constructor
*/
public function __construct($id, $calendar)
{
$this->cal = $calendar;
$this->id = $id;
switch ($this->id) {
case kolab_driver::INVITATIONS_CALENDAR_PENDING:
$this->partstats = array('NEEDS-ACTION');
$this->name = $this->cal->gettext('invitationspending');
if (!empty($_REQUEST['_quickview']))
$this->partstats[] = 'TENTATIVE';
break;
case kolab_driver::INVITATIONS_CALENDAR_DECLINED:
$this->partstats = array('DECLINED');
$this->name = $this->cal->gettext('invitationsdeclined');
break;
}
// user-specific alarms settings win
$prefs = $this->cal->rc->config->get('kolab_calendars', array());
if (isset($prefs[$this->id]['showalarms']))
$this->alarms = $prefs[$this->id]['showalarms'];
}
/**
* Getter for a nice and human readable name for this calendar
*
* @return string Name of this calendar
*/
public function get_name()
{
return $this->name;
}
/**
* Getter for the IMAP folder owner
*
* @return string Name of the folder owner
*/
public function get_owner()
{
return $this->cal->rc->get_user_name();
}
/**
*
*/
public function get_title()
{
return $this->get_name();
}
/**
* Getter for the name of the namespace to which the IMAP folder belongs
*
* @return string Name of the namespace (personal, other, shared)
*/
public function get_namespace()
{
return 'x-special';
}
/**
* Getter for the top-end calendar folder name (not the entire path)
*
* @return string Name of this calendar
*/
public function get_foldername()
{
return $this->get_name();
}
/**
* Return color to display this calendar
*/
public function get_color()
{
// calendar color is stored in local user prefs
$prefs = $this->cal->rc->config->get('kolab_calendars', array());
if (!empty($prefs[$this->id]) && !empty($prefs[$this->id]['color']))
return $prefs[$this->id]['color'];
return 'ffffff';
}
/**
* Compose an URL for CalDAV access to this calendar (if configured)
*/
public function get_caldav_url()
{
return false;
}
/**
* Check activation status of this folder
*
* @return boolean True if enabled, false if not
*/
public function is_active()
{
$prefs = $this->cal->rc->config->get('kolab_calendars', array()); // read local prefs
return (bool)$prefs[$this->id]['active'];
}
/**
* Update properties of this calendar folder
*
* @see calendar_driver::edit_calendar()
*/
public function update(&$prop)
{
// don't change anything.
// let kolab_driver save props in local prefs
return $prop['id'];
}
/**
* Getter for a single event object
*/
public function get_event($id)
{
// redirect call to kolab_driver::get_event()
$event = $this->cal->driver->get_event($id, true);
if (is_array($event)) {
// add pointer to original calendar folder
$event['_folder_id'] = $event['calendar'];
$event = $this->_mod_event($event);
}
return $event;
}
/**
* @param integer Event's new start (unix timestamp)
* @param integer Event's new end (unix timestamp)
* @param string Search query (optional)
* @param boolean Include virtual events (optional)
* @param array Additional parameters to query storage
* @return array A list of event records
*/
public function list_events($start, $end, $search = null, $virtual = 1, $query = array())
{
// convert to DateTime for comparisons
try {
$start_dt = new DateTime('@'.$start);
}
catch (Exception $e) {
$start_dt = new DateTime('@0');
}
try {
$end_dt = new DateTime('@'.$end);
}
catch (Exception $e) {
$end_dt = new DateTime('today +10 years');
}
// get email addresses of the current user
$user_emails = $this->cal->get_user_emails();
$subquery = array();
foreach ($user_emails as $email) {
foreach ($this->partstats as $partstat) {
$subquery[] = array('tags', '=', 'x-partstat:' . $email . ':' . strtolower($partstat));
}
}
// aggregate events from all calendar folders
$events = array();
foreach (kolab_storage::list_folders('', '*', 'event', null) as $foldername) {
$cal = new kolab_calendar($foldername, $this->cal);
foreach ($cal->list_events($start, $end, $search, 1, $query, array(array($subquery, 'OR'))) as $event) {
$match = false;
// post-filter events to skip pending and declined invitations
if (is_array($event['attendees'])) {
foreach ($event['attendees'] as $attendee) {
if (in_array($attendee['email'], $user_emails) && in_array($attendee['status'], $this->partstats)) {
$match = true;
break;
}
}
}
if ($match) {
$events[$event['id']] = $this->_mod_event($event);
}
}
// merge list of event categories (really?)
$this->categories += $cal->categories;
}
return $events;
}
/**
* Helper method to modify some event properties
*/
private function _mod_event($event)
{
// set classes according to PARTSTAT
if (is_array($event['attendees'])) {
$user_emails = $this->cal->get_user_emails();
$partstat = 'UNKNOWN';
foreach ($event['attendees'] as $attendee) {
if (in_array($attendee['email'], $user_emails)) {
$partstat = $attendee['status'];
break;
}
}
if (in_array($partstat, $this->partstats)) {
$event['className'] = 'fc-invitation-' . strtolower($partstat);
$event['calendar'] = $this->id;
}
}
return $event;
}
/**
* Create a new event record
*
* @see calendar_driver::new_event()
*
* @return mixed The created record ID on success, False on error
*/
public function insert_event($event)
{
return false;
}
/**
* Update a specific event record
*
* @see calendar_driver::new_event()
* @return boolean True on success, False on error
*/
public function update_event($event, $exception_id = null)
{
// forward call to the actual storage folder
if ($event['_folder_id']) {
$cal = $this->cal->driver->get_calendar($event['_folder_id']);
if ($cal && $cal->ready) {
return $cal->update_event($event, $exception_id);
}
}
return false;
}
/**
* Delete an event record
*
* @see calendar_driver::remove_event()
* @return boolean True on success, False on error
*/
public function delete_event($event, $force = true)
{
return false;
}
/**
* Restore deleted event record
*
* @see calendar_driver::undelete_event()
* @return boolean True on success, False on error
*/
public function restore_event($event)
{
return false;
}
}

View file

@ -95,6 +95,8 @@ $labels['calendarsubscribe'] = 'List permanently';
$labels['nocalendarsfound'] = 'No calendars found';
$labels['nrcalendarsfound'] = '$nr calendars found';
$labels['quickview'] = 'View only this calendar';
$labels['invitationspending'] = 'Pending invitations';
$labels['invitationsdeclined'] = 'Declined invitations';
// agenda view
$labels['listrange'] = 'Range to display:';

View file

@ -237,6 +237,11 @@ pre {
left: 20px;
}
#calendars .treelist li.x-birthdays span.calname,
#calendars .treelist li.x-invitations span.calname {
font-style: italic;
}
#calendars .treelist.flat li span.calname {
left: 24px;
right: 42px;
@ -1524,6 +1529,43 @@ a.dropdown-link:after {
top: -5000px;
}
.fc-invitation-declined {
}
.fc-event-vert.fc-invitation-needs-action,
.fc-event-hori.fc-invitation-needs-action {
border: 1px dashed #5757c7 !important;
}
.fc-event-vert.fc-invitation-tentative,
.fc-event-hori.fc-invitation-tentative {
border: 1px dashed #eb8900 !important;
}
.fc-event-vert.fc-invitation-declined,
.fc-event-hori.fc-invitation-declined {
border: 1px dashed #c00 !important;
}
.fc-event-vert.fc-invitation-tentative .fc-event-head,
.fc-event-vert.fc-invitation-declined .fc-event-head,
.fc-event-vert.fc-invitation-needs-action .fc-event-head {
/* background-color: transparent !important; */
}
.fc-event-vert.fc-invitation-tentative .fc-event-bg {
background: url(data:image/gif;base64,R0lGODlhCAAIAPABAOuJAP///yH/C1hNUCBEYXRhWE1QAT8AIfkEBQAAAQAsAAAAAAgACAAAAg4Egmipx+ZaDPCtVPFNBQA7) 0 0 repeat #fff;
}
.fc-event-vert.fc-invitation-needs-action .fc-event-bg {
background: url(data:image/gif;base64,R0lGODlhCAAIAPABAFdXx////yH/C1hNUCBEYXRhWE1QAT8AIfkEBQAAAQAsAAAAAAgACAAAAg4Egmipx+ZaDPCtVPFNBQA7) 0 0 repeat #fff;
}
.fc-event-vert.fc-invitation-declined .fc-event-bg {
background: url(data:image/gif;base64,R0lGODlhCAAIAPABAMwAAP///yH/C1hNUCBEYXRhWE1QAT8AIfkEBQAAAQAsAAAAAAgACAAAAg4Egmipx+ZaDPCtVPFNBQA7) 0 0 repeat #fff;
}
.calendarmain .fc-event:focus {
outline: 1px solid rgba(71,135,177, 0.4);
-webkit-box-shadow: 0 0 2px 3px rgba(71,135,177, 0.6);
@ -1767,7 +1809,8 @@ div.calendar-invitebox .rsvp-status.hint {
div.calendar-invitebox .rsvp-status.declined,
div.calendar-invitebox .rsvp-status.tentative,
div.calendar-invitebox .rsvp-status.accepted,
div.calendar-invitebox .rsvp-status.delegated {
div.calendar-invitebox .rsvp-status.delegated,
div.calendar-invitebox .rsvp-status.needs-action {
padding: 0 0 1px 22px;
background: url(images/attendee-status.png) 2px -20px no-repeat;
}
@ -1784,6 +1827,10 @@ div.calendar-invitebox .rsvp-status.delegated {
background-position: 2px -180px;
}
div.calendar-invitebox .rsvp-status.needs-action {
background-position: 2px 0;
}
/* iTIP attend reply page */
.calendaritipattend .centerbox {

View file

@ -130,7 +130,7 @@
<div class="event-text"></div>
</div>
<roundcube:object name="plugin.event_rsvp_buttons" id="event-rsvp" style="display:none" />
<roundcube:object name="plugin.event_rsvp_buttons" id="event-rsvp" class="event-dialog-message" style="display:none" />
</div>
<roundcube:include file="/templates/eventedit.html" />

View file

@ -30,6 +30,8 @@ class libcalendaring_itip
protected $sender;
protected $domain;
protected $itip_send = false;
protected $rsvp_actions = array('accepted','tentative','declined');
protected $rsvp_status = array('accepted','tentative','declined','delegated');
function __construct($plugin, $domain = 'libcalendaring')
{
@ -52,6 +54,12 @@ class libcalendaring_itip
$this->sender['email'] = $email;
}
public function set_rsvp_actions($actions)
{
$this->rsvp_actions = (array)$actions;
// $this->rsvp_status = array_merge($this->rsvp_actions, array('delegated'));
}
/**
* Wrapper for rcube_plugin::gettext()
* Checking for a label in different domains
@ -276,7 +284,7 @@ class libcalendaring_itip
$html = html::div('rsvp-status', $this->gettext('notanattendee'));
$action = 'import';
}
else if (in_array($status, array('ACCEPTED','TENTATIVE','DECLINED','DELEGATED'))) {
else if (in_array(strtolower($status), $this->rsvp_status)) {
$html = html::div('rsvp-status ' . strtolower($status), $this->gettext('youhave'.strtolower($status)));
if ($existing && ($existing['sequence'] > $event['sequence'] || (!$event['sequence'] && $existing['changed'] && $existing['changed'] > $event['changed']))) {
@ -402,7 +410,7 @@ class libcalendaring_itip
$metadata['rsvp'] = true;
// 1. display RSVP buttons (if the user was invited)
foreach (array('accepted','tentative','declined') as $method) {
foreach ($this->rsvp_actions as $method) {
$rsvp_buttons .= html::tag('input', array(
'type' => 'button',
'class' => "button $method",

View file

@ -329,6 +329,13 @@ class libcalendaring extends rcube_plugin
*/
public function get_user_emails()
{
static $emails;
// return cached result
if (is_array($emails)) {
return $emails;
}
$emails = array();
$plugin = $this->rc->plugins->exec_hook('calendar_user_emails', array('emails' => $emails));
$emails = array_map('strtolower', $plugin['emails']);
@ -342,7 +349,28 @@ class libcalendaring extends rcube_plugin
$emails[] = strtolower($identity['email']);
}
return array_unique($emails);
$emails = array_unique($emails);
return $emails;
}
/**
* Set the given participant status to the attendee matching the current user's identities
*
* @param array Hash array with event struct
* @param string The PARTSTAT value to set
* @return mixed Email address of the updated attendee or False if none matching found
*/
public function set_partstat(&$event, $status)
{
$emails = $this->get_user_emails();
foreach ((array)$event['attendees'] as $i => $attendee) {
if ($attendee['email'] && in_array(strtolower($attendee['email']), $emails)) {
$event['attendees'][$i]['status'] = strtoupper($status);
return $attendee['email'];
}
}
return false;
}

View file

@ -75,6 +75,7 @@ $labels['itipreply'] = 'Reply to';
$labels['itipaccepted'] = 'Accept';
$labels['itiptentative'] = 'Maybe';
$labels['itipdeclined'] = 'Decline';
$labels['itipneeds-action'] = 'Postpone';
$labels['itipcomment'] = 'Your response';
$labels['itipeditresponse'] = 'Enter a response text';
$labels['itipsendercomment'] = 'Sender\'s comment: ';
@ -96,6 +97,7 @@ $labels['youhaveaccepted'] = 'You have accepted this invitation';
$labels['youhavetentative'] = 'You have tentatively accepted this invitation';
$labels['youhavedeclined'] = 'You have declined this invitation';
$labels['youhavedelegated'] = 'You have delegated this invitation';
$labels['youhaveneeds-action'] = 'You have copied this invitation into your calendar';
$labels['attendeeaccepted'] = 'Participant has accepted';
$labels['attendeetentative'] = 'Participant has tentatively accepted';
$labels['attendeedeclined'] = 'Participant has declined';