List virtual calendars showing pending/declined inivtations (#1796)
This commit is contained in:
parent
bdf2faafae
commit
7affe524f1
14 changed files with 561 additions and 25 deletions
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
324
plugins/calendar/drivers/kolab/kolab_invitation_calendar.php
Normal file
324
plugins/calendar/drivers/kolab/kolab_invitation_calendar.php
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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:';
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
Loading…
Add table
Reference in a new issue