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
This commit is contained in:
parent
8a74dc2d28
commit
aaaa9c5818
5 changed files with 70 additions and 61 deletions
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,25 +296,39 @@ 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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Add table
Reference in a new issue