diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php index a370c22e..ac7ee9fe 100644 --- a/plugins/calendar/calendar.php +++ b/plugins/calendar/calendar.php @@ -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(); } diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js index 66525f28..b8339183 100644 --- a/plugins/calendar/calendar_ui.js +++ b/plugins/calendar/calendar_ui.js @@ -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) @@ -1926,10 +1929,16 @@ function rcube_calendar_ui(settings) data.status = response.toUpperCase(); } 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) { diff --git a/plugins/calendar/config.inc.php.dist b/plugins/calendar/config.inc.php.dist index f09d30f0..5216642d 100644 --- a/plugins/calendar/config.inc.php.dist +++ b/plugins/calendar/config.inc.php.dist @@ -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 diff --git a/plugins/calendar/drivers/calendar_driver.php b/plugins/calendar/drivers/calendar_driver.php index 9fb6ffb8..4c4f5161 100644 --- a/plugins/calendar/drivers/calendar_driver.php +++ b/plugins/calendar/drivers/calendar_driver.php @@ -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 * diff --git a/plugins/calendar/drivers/database/database_driver.php b/plugins/calendar/drivers/database/database_driver.php index 3b833abd..158adc91 100644 --- a/plugins/calendar/drivers/database/database_driver.php +++ b/plugins/calendar/drivers/database/database_driver.php @@ -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, diff --git a/plugins/calendar/drivers/kolab/kolab_calendar.php b/plugins/calendar/drivers/kolab/kolab_calendar.php index 401b2954..706c3cd3 100644 --- a/plugins/calendar/drivers/kolab/kolab_calendar.php +++ b/plugins/calendar/drivers/kolab/kolab_calendar.php @@ -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) { diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php index ca14d89c..ba634cb5 100644 --- a/plugins/calendar/drivers/kolab/kolab_driver.php +++ b/plugins/calendar/drivers/kolab/kolab_driver.php @@ -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); } diff --git a/plugins/calendar/drivers/kolab/kolab_invitation_calendar.php b/plugins/calendar/drivers/kolab/kolab_invitation_calendar.php new file mode 100644 index 00000000..ab5fe4be --- /dev/null +++ b/plugins/calendar/drivers/kolab/kolab_invitation_calendar.php @@ -0,0 +1,324 @@ + + * + * Copyright (C) 2014, Kolab Systems AG + * + * 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 . + */ + +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; + } + + +} diff --git a/plugins/calendar/localization/en_US.inc b/plugins/calendar/localization/en_US.inc index 52da7460..308364f7 100644 --- a/plugins/calendar/localization/en_US.inc +++ b/plugins/calendar/localization/en_US.inc @@ -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:'; diff --git a/plugins/calendar/skins/larry/calendar.css b/plugins/calendar/skins/larry/calendar.css index c79e3346..e44a0ebb 100644 --- a/plugins/calendar/skins/larry/calendar.css +++ b/plugins/calendar/skins/larry/calendar.css @@ -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 { diff --git a/plugins/calendar/skins/larry/templates/calendar.html b/plugins/calendar/skins/larry/templates/calendar.html index 6e7ed499..d6081573 100644 --- a/plugins/calendar/skins/larry/templates/calendar.html +++ b/plugins/calendar/skins/larry/templates/calendar.html @@ -130,7 +130,7 @@
-