* Calendar/Mail: handle meeting requests for single recurrences and exceptions

This commit is contained in:
Ralf Becker 2018-06-27 18:26:42 +02:00
parent 36726c7400
commit 8e419f1c1a
5 changed files with 115 additions and 18 deletions

View File

@ -1042,9 +1042,10 @@ class calendar_bo
* @param string $date_format ='ts' date-formats: 'ts'=timestamp, 'server'=timestamp in servertime, 'array'=array, or string with date-format * @param string $date_format ='ts' date-formats: 'ts'=timestamp, 'server'=timestamp in servertime, 'array'=array, or string with date-format
* @param array|int $clear_private_infos_users =null if not null, return events with self::ACL_FREEBUSY too, * @param array|int $clear_private_infos_users =null if not null, return events with self::ACL_FREEBUSY too,
* but call clear_private_infos() with the given users * but call clear_private_infos() with the given users
* @param boolean $read_recurrence =false true: read the exception, not the series master (only for recur_date && $ids='<uid>'!)
* @return boolean|array event or array of id => event pairs, false if the acl-check went wrong, null if $ids not found * @return boolean|array event or array of id => event pairs, false if the acl-check went wrong, null if $ids not found
*/ */
function read($ids,$date=null,$ignore_acl=False,$date_format='ts',$clear_private_infos_users=null) function read($ids,$date=null, $ignore_acl=False, $date_format='ts', $clear_private_infos_users=null, $read_recurrence=false)
{ {
if (!$ids) return false; if (!$ids) return false;
@ -1056,10 +1057,10 @@ class calendar_bo
if ($ignore_acl || is_array($ids) || ($return = $this->check_perms($check,$ids,0,$date_format,$date))) if ($ignore_acl || is_array($ids) || ($return = $this->check_perms($check,$ids,0,$date_format,$date)))
{ {
if (is_array($ids) || !isset(self::$cached_event['id']) || self::$cached_event['id'] != $ids || if (is_array($ids) || !isset(self::$cached_event['id']) || self::$cached_event['id'] != $ids ||
self::$cached_event_date_format != $date_format || self::$cached_event_date_format != $date_format || $read_recurrence ||
self::$cached_event['recur_type'] != MCAL_RECUR_NONE && self::$cached_event_date != $date) self::$cached_event['recur_type'] != MCAL_RECUR_NONE && self::$cached_event_date != $date)
{ {
$events = $this->so->read($ids,$date ? $this->date2ts($date,true) : 0); $events = $this->so->read($ids,$date ? $this->date2ts($date,true) : 0, $read_recurrence);
if ($events) if ($events)
{ {

View File

@ -1995,10 +1995,10 @@ class calendar_boupdate extends calendar_bo
* *
* @param array $event * @param array $event
* @param array $old_event * @param array $old_event
* @param Api\DateTime $instance_date For recurring events, this is the date we * @param Api\DateTime|int|null $instance_date For recurring events, this is the date we
* are dealing with * are dealing with
*/ */
function check_move_alarms(Array &$event, Array $old_event = null, Api\DateTime $instance_date = null) function check_move_alarms(Array &$event, Array $old_event = null, $instance_date = null)
{ {
if ($old_event !== null && $event['start'] == $old_event['start']) return; if ($old_event !== null && $event['start'] == $old_event['start']) return;
@ -2008,11 +2008,20 @@ class calendar_boupdate extends calendar_bo
$event['alarm'] = $this->so->read_alarms($event['id']); $event['alarm'] = $this->so->read_alarms($event['id']);
} }
if (is_object($instance_date))
{
if (!is_a($instance_date, 'EGroupware\\Api\\DateTime'))
{
throw new Api\Exception\WrongParameter('$instance_date must be integer or Api\DateTime!');
}
$instance_date = $instance_date->format('ts');
}
foreach($event['alarm'] as &$alarm) foreach($event['alarm'] as &$alarm)
{ {
if($event['recur_type'] != MCAL_RECUR_NONE && is_object($instance_date)) if($event['recur_type'] != MCAL_RECUR_NONE && $instance_date)
{ {
calendar_so::shift_alarm($event, $alarm, $instance_date->format('ts')); calendar_so::shift_alarm($event, $alarm, $instance_date);
} }
else if ($alarm['time'] !== $time->format('ts') - $alarm['offset']) else if ($alarm['time'] !== $time->format('ts') - $alarm['offset'])
{ {

View File

@ -307,9 +307,10 @@ class calendar_so
* *
* @param int|array|string $ids id or array of id's of the entries to read, or string with a single uid * @param int|array|string $ids id or array of id's of the entries to read, or string with a single uid
* @param int $recur_date =0 if set read the next recurrence at or after the timestamp, default 0 = read the initital one * @param int $recur_date =0 if set read the next recurrence at or after the timestamp, default 0 = read the initital one
* @param boolean $read_recurrence =false true: read the exception, not the series master (only for recur_date && $ids='<uid>'!)
* @return array|boolean array with cal_id => event array pairs or false if entry not found * @return array|boolean array with cal_id => event array pairs or false if entry not found
*/ */
function read($ids,$recur_date=0) function read($ids, $recur_date=0, $read_recurrence=false)
{ {
//error_log(__METHOD__.'('.array2string($ids).",$recur_date) ".function_backtrace()); //error_log(__METHOD__.'('.array2string($ids).",$recur_date) ".function_backtrace());
$cols = self::get_columns('calendar', $this->cal_table); $cols = self::get_columns('calendar', $this->cal_table);
@ -322,7 +323,15 @@ class calendar_so
{ {
// We want only the parents to match // We want only the parents to match
$where['cal_uid'] = $ids; $where['cal_uid'] = $ids;
$where['cal_reference'] = 0; $where[] = 'cal_deleted IS NULL';
if ($read_recurrence)
{
$where['cal_recurrence'] = $recur_date;
}
else
{
$where['cal_reference'] = 0;
}
} }
elseif(is_array($ids) && isset($ids[count($ids)-1]) || is_scalar($ids)) // one or more cal_id's elseif(is_array($ids) && isset($ids[count($ids)-1]) || is_scalar($ids)) // one or more cal_id's
{ {
@ -340,7 +349,7 @@ class calendar_so
)); ));
unset($where['cal_id']); unset($where['cal_id']);
} }
if ((int) $recur_date) if ((int) $recur_date && !$read_recurrence)
{ {
$where[] = 'cal_start >= '.(int)$recur_date; $where[] = 'cal_start >= '.(int)$recur_date;
$group_by = 'GROUP BY '.$cols; $group_by = 'GROUP BY '.$cols;
@ -355,6 +364,12 @@ class calendar_so
$events =& $this->get_events($this->db->select($this->cal_table, $cols, $where, __LINE__, __FILE__, false, $group_by, 'calendar', 0, $join), $recur_date); $events =& $this->get_events($this->db->select($this->cal_table, $cols, $where, __LINE__, __FILE__, false, $group_by, 'calendar', 0, $join), $recur_date);
// if we wanted to read the real recurrence, but we have eg. only a virtual one, we need to try again without $read_recurrence
if ((!$events || ($e = current($events)) && $e['deleted']) && $recur_date && $read_recurrence)
{
return $this->read($ids, $recur_date);
}
return $events ? $events : false; return $events ? $events : false;
} }

View File

@ -1991,7 +1991,8 @@ class calendar_uiforms extends calendar_ui
// convert event from servertime returned by calendar_ical to user-time // convert event from servertime returned by calendar_ical to user-time
$this->bo->server2usertime($event); $this->bo->server2usertime($event);
if (($existing_event = $this->bo->read($event['uid'])) && !$existing_event['deleted']) if (($existing_event = $this->bo->read($event['uid'], $event['recurrence'], false, 'ts', null, true)) && // true = read the exception
!$existing_event['deleted'])
{ {
switch(strtolower($ical_method)) switch(strtolower($ical_method))
{ {
@ -2113,23 +2114,72 @@ class calendar_uiforms extends calendar_ui
break; break;
} }
} }
// set status and send notification / meeting response // do we need to update the event itself
if ($this->bo->set_status($event['id'], $user, $status)) elseif (self::event_changed($event, $event['old']))
{ {
if (!$msg) $msg = lang('Status changed'); // check if we are allowed to update the event
if($this->bo->check_perms(Acl::EDIT, $event['old']))
{
if ($event['recurrence'] && !$event['old']['reference'] && ($recur_event = $this->bo->read($event['id'])))
{
// first we need to add the exception to the recurrence master
$recur_event['recur_exception'][] = $event['recurrence'];
// check if we need to move the alarms, because they are next on that exception
$this->bo->check_move_alarms($recur_event, null, $event['recurrence']);
unset($recur_event['start']); unset($recur_event['end']); // no update necessary
unset($recur_event['alarm']); // unsetting alarms too, as they cant be updated without start!
$this->bo->update($recur_event, $ignore_conflicts=true, true, false, true, $msg, true);
// then we need to create the exception as new event
unset($event['id']);
$event['reference'] = $event['old']['id'];
$event['caldav_name'] = $event['old']['caldav_name'];
}
else
{
// keep all EGroupware only values of existing events plus alarms
unset($event['alarm']);
$event = array_merge($event['old'], $event);
}
unset($event['old']);
if (($event['id'] = $this->bo->update($event, $ignore_conflicts=true, true, false, true, $msg, true)))
{
$msg[] = lang('Event saved');
}
else
{
$msg[] = lang('Error saving the event!');
break;
}
}
else
{
$event['id'] = $event['old']['id'];
$msg[] = lang('Not enough rights to update the event!');
}
}
else
{
$event['id'] = $event['old']['id'];
}
// set status and send notification / meeting response
if ($this->bo->set_status($event['id'], $user, $status, $event['recurrence']))
{
$msg[] = lang('Status changed');
} }
break; break;
case 'apply': case 'apply':
// set status and send notification / meeting response // set status and send notification / meeting response
if ($this->bo->set_status($event['id'], $event['ical_sender_uid'], $event['ical_sender_status'])) if ($this->bo->set_status($event['id'], $event['ical_sender_uid'], $event['ical_sender_status'], $event['recurrence']))
{ {
$msg = lang('Status changed'); $msg = lang('Status changed');
} }
break; break;
case 'cancel': case 'cancel':
if ($event['id'] && $this->bo->set_status($event['id'], $user, 'R')) if ($event['id'] && $this->bo->set_status($event['id'], $user, 'R', $event['recurrence']))
{ {
$msg = lang('Status changed'); $msg = lang('Status changed');
} }
@ -2155,7 +2205,29 @@ class calendar_uiforms extends calendar_ui
break; break;
} }
$tpl = new Etemplate('calendar.meeting'); $tpl = new Etemplate('calendar.meeting');
$tpl->exec('calendar.calendar_uiforms.meeting', $event, array(), $readonlys, $event, 2); $tpl->exec('calendar.calendar_uiforms.meeting', $event, array(), $readonlys, $event+array(
'old' => $existing_event,
), 2);
}
/**
* Check if an event changed and need to be updated
*
* @param array $_a
* @param array $_b
* @return boolean true if there are some changes, false if not
*/
function event_changed($_a, $_b)
{
static $keys_to_check = array('start', 'end', 'title', 'description', 'location', 'participants',
'recur_type', 'recur_data', 'recur_interval', 'recur_exception');
$a = array_intersect_key($_a, array_flip($keys_to_check));
$b = array_intersect_key($_b, array_flip($keys_to_check));
$ret = $a != $b;
error_log(__METHOD__."() returning ".array2string($ret)." diff=".array2string(array_diff_key($a, $b)));
return $ret;
} }
/** /**

View File

@ -825,7 +825,7 @@ class calendar_zpush implements activesync_plugin_write, activesync_plugin_meeti
{ {
$event['recur_enddate'] = Api\DateTime::server2user($message->recurrence->until); $event['recur_enddate'] = Api\DateTime::server2user($message->recurrence->until);
} }
$event['recur_exceptions'] = array(); $event['recur_exception'] = array();
if ($message->exceptions) if ($message->exceptions)
{ {
foreach($message->exceptions as $exception) foreach($message->exceptions as $exception)