Branch 'dev/recurring-invitations' - 3 commits - plugins/calendar plugins/libkolab
Thomas Brüderli
bruederli at kolabsys.com
Mon Feb 16 12:02:34 CET 2015
plugins/calendar/calendar.php | 43 ++++++++++-
plugins/calendar/calendar_ui.js | 1
plugins/calendar/drivers/calendar_driver.php | 7 +
plugins/calendar/drivers/kolab/kolab_calendar.php | 82 ++++++++++------------
plugins/calendar/drivers/kolab/kolab_driver.php | 1
plugins/libkolab/lib/kolab_format_event.php | 9 +-
plugins/libkolab/lib/kolab_format_task.php | 15 ++--
plugins/libkolab/lib/kolab_format_xcal.php | 18 +++-
8 files changed, 112 insertions(+), 64 deletions(-)
New commits:
commit d7733e7879c7c16074d11ebf58515c50dddf53bb
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date: Mon Feb 16 11:06:42 2015 +0100
Allow to RSVP reply on a single occurence when viewing the event in the calendar preview
This will copy the main event from the iTip invitation with unchanged partstat into the
user calendar and register a recurrence exception with the selected partsat and send a
reply for this occurrence only.
diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php
index 7682ba0..786cd76 100644
--- a/plugins/calendar/calendar.php
+++ b/plugins/calendar/calendar.php
@@ -2655,6 +2655,8 @@ class calendar extends rcube_plugin
$delete = intval(rcube_utils::get_input_value('_del', rcube_utils::INPUT_POST));
$noreply = intval(rcube_utils::get_input_value('_noreply', rcube_utils::INPUT_POST));
$noreply = $noreply || $status == 'needs-action' || $itip_sending === 0;
+ $instance = rcube_utils::get_input_value('_instance', rcube_utils::INPUT_POST);
+ $savemode = rcube_utils::get_input_value('_savemode', rcube_utils::INPUT_POST);
$error_msg = $this->gettext('errorimportingevent');
$success = false;
@@ -2747,7 +2749,7 @@ class calendar extends rcube_plugin
if ($existing) {
// forward savemode for correct updates of recurring events
- $existing['_savemode'] = $event['_savemode'];
+ $existing['_savemode'] = $savemode ?: $event['_savemode'];
// only update attendee status
if ($event['_method'] == 'REPLY') {
@@ -2850,9 +2852,43 @@ class calendar extends rcube_plugin
if ($status == 'declined' || $event['status'] == 'CANCELLED' || $event_attendee['role'] == 'NON-PARTICIPANT') {
$event['free_busy'] = 'free';
}
+
+ // if the RSVP reply only refers to a single instance:
+ // store unmodified master event with current instance as exception
+ if (!empty($instance) && $savemode != 'all') {
+ $master = $this->lib->mail_get_itip_object($mbox, $uid, $mime_id, 'event');
+ if ($master['recurrence'] && !$master['_instance']) {
+ // compute recurring events until this instance's date
+ if ($recurrence_date = rcube_utils::anytodatetime($instance, $master['start']->getTimezone())) {
+ $recurrence_date->setTime(23,59,59);
+
+ foreach ($this->driver->get_recurring_events($master, $master['start'], $recurrence_date) as $recurring) {
+ if ($recurring['_instance'] == $instance) {
+ // copy attendees block with my partstat to exception
+ $recurring['attendees'] = $event['attendees'];
+ $master['recurrence']['EXCEPTIONS'][] = $recurring;
+ $event = $recurring; // set reference for iTip reply
+ break;
+ }
+ }
+
+ $master['calendar'] = $event['calendar'] = $calendar['id'];
+ $success = $this->driver->new_event($master);
+ }
+ else {
+ $master = null;
+ }
+ }
+ else {
+ $master = null;
+ }
+ }
+
// save to the selected/default calendar
- $event['calendar'] = $calendar['id'];
- $success = $this->driver->new_event($event);
+ if (!$master) {
+ $event['calendar'] = $calendar['id'];
+ $success = $this->driver->new_event($event);
+ }
}
else if ($status == 'declined')
$error_msg = null;
diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js
index f9e0d28..ec83d1b 100644
--- a/plugins/calendar/calendar_ui.js
+++ b/plugins/calendar/calendar_ui.js
@@ -2434,6 +2434,7 @@ function rcube_calendar_ui(settings)
_rsvp: (delegate && delegate.rsvp) ? 1 : 0,
_noreply: noreply,
_comment: submit_data.comment,
+ _instance: submit_data._instance,
_savemode: submit_data._savemode
});
}
commit aaaa9c58185d96a120893f2f3e3c361d44309e8a
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date: Mon Feb 16 11:00:26 2015 +0100
Take differing parstat values in recurrence exceptions into account when querying for pending/declined/regular events:
- Colelct partstat tags from recurrence exceptions when caching
- Querying for 'tags != x-partstat:<email>:needs-action' may miss some valid records
- Do post-filtering on all events, including recurring instances
diff --git a/plugins/calendar/drivers/calendar_driver.php b/plugins/calendar/drivers/calendar_driver.php
index e402db9..742830b 100644
--- a/plugins/calendar/drivers/calendar_driver.php
+++ b/plugins/calendar/drivers/calendar_driver.php
@@ -450,6 +450,7 @@ abstract class calendar_driver
$rcmail = rcmail::get_instance();
$recurrence = new calendar_recurrence($rcmail->plugins->get_plugin('calendar'), $event);
+ $recurrence_id_format = $event['allday'] ? 'Ymd' : 'Ymd\THis';
// determine a reasonable end date if none given
if (!$end) {
@@ -465,10 +466,10 @@ abstract class calendar_driver
$i = 0;
while ($next_event = $recurrence->next_instance()) {
- $next_event['uid'] = $event['uid'] . '-' . ++$i;
// add to output if in range
if (($next_event['start'] <= $end && $next_event['end'] >= $start)) {
- $next_event['id'] = $next_event['uid'];
+ $next_event['_instance'] = $next_event['start']->format($recurrence_id_format);
+ $next_event['id'] = $next_event['uid'] . '-' . $exception['_instance'];
$next_event['recurrence_id'] = $event['uid'];
$events[] = $next_event;
}
@@ -477,7 +478,7 @@ abstract class calendar_driver
}
// avoid endless recursion loops
- if ($i > 1000) {
+ if (++$i > 1000) {
break;
}
}
diff --git a/plugins/calendar/drivers/kolab/kolab_calendar.php b/plugins/calendar/drivers/kolab/kolab_calendar.php
index 06a8244..b5b272f 100644
--- a/plugins/calendar/drivers/kolab/kolab_calendar.php
+++ b/plugins/calendar/drivers/kolab/kolab_calendar.php
@@ -236,35 +236,31 @@ class kolab_calendar extends kolab_storage_folder_api
$query[] = array('dtstart', '<=', $end);
$query[] = array('dtend', '>=', $start);
- // add query to exclude pending/declined invitations
- if (empty($filter_query) && $this->get_namespace() != 'other') {
- 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)) {
+ if (is_array($filter_query)) {
$query = array_merge($query, $filter_query);
}
if (!empty($search)) {
$search = mb_strtolower($search);
+ $words = rcube_utils::tokenize_string($search, 1);
foreach (rcube_utils::normalize_string($search, true) as $word) {
$query[] = array('words', 'LIKE', $word);
}
}
+ else {
+ $words = array();
+ }
+
+ // set partstat filter to skip pending and declined invitations
+ if (empty($filter_query) && $this->get_namespace() != 'other') {
+ $partstat_exclude = array('NEEDS-ACTION','DECLINED');
+ }
+ else {
+ $partstat_exclude = array();
+ }
$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']) && $this->get_namespace() != 'other') {
- 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;
@@ -272,18 +268,6 @@ class kolab_calendar extends kolab_storage_folder_api
if ($event['categories'])
$this->categories[$event['categories']]++;
- // filter events by search query
- if (!empty($search)) {
- $hits = 0;
- $words = rcube_utils::tokenize_string($search, 1);
- foreach ($words as $word) {
- $hits += $this->_fulltext_match($event, $word);
- }
-
- if ($hits < count($words)) // skip this event if not match with search term
- continue;
- }
-
// list events in requested time window
if ($event['start'] <= $end && $event['end'] >= $start) {
unset($event['_attendees']);
@@ -312,24 +296,38 @@ class kolab_calendar extends kolab_storage_folder_api
if ($add)
$events[] = $event;
}
-
+
// resolve recurring events
if ($record['recurrence'] && $virtual == 1) {
$events = array_merge($events, $this->get_recurring_events($record, $start, $end));
+ }
+ }
- // when searching, only recurrence exceptions may match the query so post-filter the results again
- if (!empty($search) && $record['recurrence']['EXCEPTIONS']) {
- $me = $this;
- $events = array_filter($events, function($event) use ($words, $me) {
- $hits = 0;
- foreach ($words as $word) {
- $hits += $me->_fulltext_match($event, $word, false);
- }
- return $hits >= count($words);
- });
+ // post-filter all events by fulltext search and partstat values
+ $me = $this;
+ $events = array_filter($events, function($event) use ($words, $partstat_exclude, $user_emails, $me) {
+ // fulltext search
+ if (count($words)) {
+ $hits = 0;
+ foreach ($words as $word) {
+ $hits += $me->_fulltext_match($event, $word, false);
+ }
+ if ($hits < count($words)) {
+ return false;
}
}
- }
+
+ // partstat filter
+ if (count($partstat_exclude) && is_array($event['attendees'])) {
+ foreach ($event['attendees'] as $attendee) {
+ if (in_array($attendee['email'], $user_emails) && in_array($attendee['status'], $partstat_exclude)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ });
// avoid session race conditions that will loose temporary subscriptions
$this->cal->rc->session->nowrite = true;
diff --git a/plugins/libkolab/lib/kolab_format_event.php b/plugins/libkolab/lib/kolab_format_event.php
index a5b0f73..0fda1e3 100644
--- a/plugins/libkolab/lib/kolab_format_event.php
+++ b/plugins/libkolab/lib/kolab_format_event.php
@@ -223,15 +223,16 @@ class kolab_format_event extends kolab_format_xcal
*
* @return array List of tags to save in cache
*/
- public function get_tags()
+ public function get_tags($obj = null)
{
- $tags = parent::get_tags();
+ $tags = parent::get_tags($obj);
+ $object = $obj ?: $this->data;
- foreach ((array)$this->data['categories'] as $cat) {
+ foreach ((array)$object['categories'] as $cat) {
$tags[] = rcube_utils::normalize_string($cat);
}
- return $tags;
+ return array_unique($tags);
}
/**
diff --git a/plugins/libkolab/lib/kolab_format_task.php b/plugins/libkolab/lib/kolab_format_task.php
index d3ddfe9..4640875 100644
--- a/plugins/libkolab/lib/kolab_format_task.php
+++ b/plugins/libkolab/lib/kolab_format_task.php
@@ -121,20 +121,21 @@ class kolab_format_task extends kolab_format_xcal
*
* @return array List of tags to save in cache
*/
- public function get_tags()
+ public function get_tags($obj = null)
{
- $tags = parent::get_tags();
+ $tags = parent::get_tags($obj);
+ $object = $obj ?: $this->data;
- if ($this->data['status'] == 'COMPLETED' || ($this->data['complete'] == 100 && empty($this->data['status'])))
+ if ($object['status'] == 'COMPLETED' || ($object['complete'] == 100 && empty($object['status'])))
$tags[] = 'x-complete';
- if ($this->data['priority'] == 1)
+ if ($object['priority'] == 1)
$tags[] = 'x-flagged';
- if ($this->data['parent_id'])
- $tags[] = 'x-parent:' . $this->data['parent_id'];
+ if ($object['parent_id'])
+ $tags[] = 'x-parent:' . $object['parent_id'];
- return $tags;
+ return array_unique($tags);
}
}
diff --git a/plugins/libkolab/lib/kolab_format_xcal.php b/plugins/libkolab/lib/kolab_format_xcal.php
index 6d49ad1..3d7bc27 100644
--- a/plugins/libkolab/lib/kolab_format_xcal.php
+++ b/plugins/libkolab/lib/kolab_format_xcal.php
@@ -611,23 +611,31 @@ abstract class kolab_format_xcal extends kolab_format
*
* @return array List of tags to save in cache
*/
- public function get_tags()
+ public function get_tags($obj = null)
{
$tags = array();
+ $object = $obj ?: $this->data;
- if (!empty($this->data['valarms'])) {
+ if (!empty($object['valarms'])) {
$tags[] = 'x-has-alarms';
}
// create tags reflecting participant status
- if (is_array($this->data['attendees'])) {
- foreach ($this->data['attendees'] as $attendee) {
+ if (is_array($object['attendees'])) {
+ foreach ($object['attendees'] as $attendee) {
if (!empty($attendee['email']) && !empty($attendee['status']))
$tags[] = 'x-partstat:' . $attendee['email'] . ':' . strtolower($attendee['status']);
}
}
- return $tags;
+ // collect tags from recurrence exceptions
+ if (is_array($object['recurrence']) && $object['recurrence']['EXCEPTIONS']) {
+ foreach((array)$object['recurrence']['EXCEPTIONS'] as $exception) {
+ $tags = array_merge($tags, $this->get_tags($exception));
+ }
+ }
+
+ return array_unique($tags);
}
/**
commit 8a74dc2d28aba4af89d123228fb638e4b4999dbd
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date: Sun Feb 15 19:09:10 2015 +0100
Don't copy recurrence_date to future occurrences
diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php
index f61b681..7682ba0 100644
--- a/plugins/calendar/calendar.php
+++ b/plugins/calendar/calendar.php
@@ -1676,6 +1676,7 @@ class calendar extends rcube_plugin
if ($event['recurrence']) {
$event['recurrence_text'] = $this->lib->recurrence_text($event['recurrence']);
$event['recurrence'] = $this->lib->to_client_recurrence($event['recurrence'], $event['allday']);
+ unset($event['recurrence_date']);
}
foreach ((array)$event['attachments'] as $k => $attachment) {
diff --git a/plugins/calendar/drivers/kolab/kolab_calendar.php b/plugins/calendar/drivers/kolab/kolab_calendar.php
index 15f030d..06a8244 100644
--- a/plugins/calendar/drivers/kolab/kolab_calendar.php
+++ b/plugins/calendar/drivers/kolab/kolab_calendar.php
@@ -661,7 +661,7 @@ class kolab_calendar extends kolab_storage_folder_api
*/
private function _merge_event_data(&$event, $overlay)
{
- static $forbidden = array('id','uid','recurrence','organizer','_attachments');
+ static $forbidden = array('id','uid','recurrence','recurrence_date','organizer','_attachments');
foreach ($overlay as $prop => $value) {
// adjust time of the recurring event instance
diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php
index e414b87..0cd962d 100644
--- a/plugins/calendar/drivers/kolab/kolab_driver.php
+++ b/plugins/calendar/drivers/kolab/kolab_driver.php
@@ -962,6 +962,7 @@ class kolab_driver extends calendar_driver
// save as new exception to master event
if ($add_exception) {
$event['_instance'] = $old['_instance'];
+ $event['recurrence_date'] = $old['recurrence_date'];
$master['recurrence']['EXCEPTIONS'][] = $event;
}
$success = $storage->update_event($master);
More information about the commits
mailing list