show changes in participants, new and old start-date

for exceptions only sending the concerned recurrence, instead of the whole series, so user understands what has changed
This commit is contained in:
ralf 2024-07-09 21:39:40 +02:00
parent 42178c432e
commit 1509f30971
3 changed files with 102 additions and 28 deletions

View File

@ -570,10 +570,10 @@ class calendar_boupdate extends calendar_bo
* *
* @param array $new_event the updated event * @param array $new_event the updated event
* @param array $old_event the event before the update * @param array $old_event the event before the update
* @param int $notify_max_recurrences notify only about this number of single recurrences at maximum
*/ */
function check4update($new_event,$old_event) function check4update($new_event, $old_event, int $notify_max_recurrences=3)
{ {
//error_log(__METHOD__."($new_event[title])");
$modified = $added = $deleted = array(); $modified = $added = $deleted = array();
//echo "<p>calendar_boupdate::check4update() new participants = ".print_r($new_event['participants'],true).", old participants =".print_r($old_event['participants'],true)."</p>\n"; //echo "<p>calendar_boupdate::check4update() new participants = ".print_r($new_event['participants'],true).", old participants =".print_r($old_event['participants'],true)."</p>\n";
@ -582,6 +582,63 @@ class calendar_boupdate extends calendar_bo
{ {
if($new_event[$field] !== $old_event[$field]) if($new_event[$field] !== $old_event[$field])
{ {
$n = 0;
switch($field)
{
case 'recur_exception':
foreach ($added_exceptions=array_diff($new_event[$field]??[],$old_event[$field]??[]) as $key => $recurrence)
{
if ($this->read($new_event['uid'], $recurrence))
{
unset($added_exceptions[$key]);
continue; // explicit exception --> no need to notify, already done for the exception itself
}
// send a cancel for each RECURRENCE-ID
$this->send_update(MSG_DELETED, $new_event['participants'], [
'recurrence' => $recurrence,
'start' => $recurrence,
'end' => $recurrence + $new_event['end'] - $new_event['start'],
]+$new_event);
// limit number of notifications to the first N recurrences
if (++$n >= $notify_max_recurrences) break;
}
$n = 0;
foreach($removed_exceptions=array_diff($old_event[$field]??[],$new_event[$field]??[]) as $key => $recurrence)
{
if ($this->read($new_event['uid'], $recurrence))
{
unset($removed_exceptions[$key]);
continue; // explicit exception --> no need to notify, already done for the exception itself
}
// send an add for each RECURRENCE-ID
$this->send_update(MSG_ADDED, $new_event['participants'], null, [
'recurrence' => $recurrence,
'start' => $recurrence,
'end' => $recurrence + $new_event['end'] - $new_event['start'],
]+$new_event);
// limit number of notifications to the first N recurrences
if (++$n >= $notify_max_recurrences) break;
}
continue 2;
case 'recur_rdates':
foreach(array_diff($new_event[$field]??[],$old_event[$field]??[]) as $recurrence)
{
// send an add for each RECURRENCE-ID
$this->send_update(MSG_ADDED, $new_event['participants'], null, [
'recurrence' => $recurrence,
'start' => $recurrence,
'end' => $recurrence + $new_event['end'] - $new_event['start'],
]+$new_event);
// limit number of notifications to the first N recurrences
if (++$n >= $notify_max_recurrences) break;
}
continue 2;
case 'recur_enddate':
if ($new_event['recur_type'] == MCAL_RECUR_RDATE) continue 2; // already handled above
break;
}
$modified = $new_event['participants']; $modified = $new_event['participants'];
break; break;
} }
@ -1288,7 +1345,7 @@ class calendar_boupdate extends calendar_bo
'videoconference' => $details['videoconference'], 'videoconference' => $details['videoconference'],
), $event['id']); ), $event['id']);
} }
if ($m_type === MSG_ALARM) elseif ($m_type === MSG_ALARM)
{ {
$notification->set_popupdata('calendar', $notification->set_popupdata('calendar',
array('egw_pr_notify' => 1, array('egw_pr_notify' => 1,
@ -1299,6 +1356,10 @@ class calendar_boupdate extends calendar_bo
) )
+ ($alarm ? ['alarm-offset' => (int)$alarm['offset']] : []), $event['id']); + ($alarm ? ['alarm-offset' => (int)$alarm['offset']] : []), $event['id']);
} }
else
{
$notification->set_popupdata('calendar', null, $event['id']);
}
$notification->set_popupmessage($subject . "\n\n" . $notify_body . "\n\n" . $details['description'] . "\n\n" . $details_body . "\n\n"); $notification->set_popupmessage($subject . "\n\n" . $notify_body . "\n\n" . $details['description'] . "\n\n" . $details_body . "\n\n");
$notification->set_popuplinks(array($details['link_arr'] + array('app' => 'calendar'))); $notification->set_popuplinks(array($details['link_arr'] + array('app' => 'calendar')));

View File

@ -2225,14 +2225,6 @@ 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);
// Check if this is an exception
if($event['recur_type'] && count($event['recur_exception']) && !$event['recurrence'])
{
$diff = $event['recur_exception'][0] - $event['start'];
$event['start'] += $diff;
$event['end'] += $diff;
}
if (($existing_event = $this->bo->read($event['uid'], $event['recurrence'], false, 'ts', null, true))) // true = read the exception if (($existing_event = $this->bo->read($event['uid'], $event['recurrence'], false, 'ts', null, true))) // true = read the exception
{ {
// check if mail is from extern organizer // check if mail is from extern organizer
@ -2251,6 +2243,20 @@ class calendar_uiforms extends calendar_ui
{ {
$master = $this->bo->read($event['uid']); $master = $this->bo->read($event['uid']);
} }
$all_participants = ($event['participants'] ?? []) + ($existing_event['participants'] ?? []);
$event['participantChanges'] = array_map(function($uid, $status) use ($existing_event, $event) {
return [
'changed' => !isset($event['participants'][$uid]) ? 'meetingRequestParticipantDeleted' :
(!isset($existing_event['participants'][$uid]) ? 'meetingRequestChanged' :
($status !== $existing_event['participants'][$uid] ? 'meetingRequestChangedStatus' : '')),
'label' => $this->bo->participant_name($uid),
'status' => lang($this->bo->verbose_status[calendar_so::split_status($status, $quantity, $role)]),
'role' => $role === 'REQ-PARTICIPANT' ? '' : lang($this->bo->roles[$role] ??
(substr($role,0,6) === 'X-CAT-' && ($cat_id = (int)substr($role,6)) > 0 ?
$GLOBALS['egw']->categories->id2name($cat_id) : str_replace('X-','',$role))),
];
}, array_keys($all_participants), $all_participants);
switch(strtolower($ical_method)) switch(strtolower($ical_method))
{ {
case 'reply': case 'reply':
@ -2338,22 +2344,14 @@ class calendar_uiforms extends calendar_ui
$readonlys['button[reject]'] = $readonlys['button[cancel]'] = true; $readonlys['button[reject]'] = $readonlys['button[cancel]'] = true;
} }
} }
$all_participants = ($event['participants'] ?? []) + ($existing_event['participants'] ?? []);
$event['participantChanges'] = array_map(function($uid, $status) use ($existing_event, $event) {
return [
'changed' => !isset($event['participants'][$uid]) ? 'meetingRequestParticipantDeleted' :
(!isset($existing_event['participants'][$uid]) ? 'meetingRequestChanged' :
($status !== $existing_event['participants'][$uid] ? 'meetingRequestChangedStatus' : '')),
'label' => $this->bo->participant_name($uid),
'status' => lang($this->bo->verbose_status[calendar_so::split_status($status, $quantity, $role)]),
'role' => $role === 'REQ-PARTICIPANT' ? '' : lang($this->bo->roles[$role] ??
(substr($role,0,6) === 'X-CAT-' && ($cat_id = (int)substr($role,6)) > 0 ?
$GLOBALS['egw']->categories->id2name($cat_id) : str_replace('X-','',$role))),
];
}, array_keys($all_participants), $all_participants);
break; break;
case 'cancel': case 'cancel':
// first participant is the (external) organizer (our iCal parser adds owner first!) // first participant is the (external) organizer (our iCal parser adds owner first!)
$event['changed'] = [
'start' => 'meetingRequestChangedValue',
'end' => 'meetingRequestChangedValue',
'participants' => 'meetingRequestParticipantDeleted',
];
$parts = $event['participants'] ?? []; $parts = $event['participants'] ?? [];
unset($parts[$existing_event['owner']]); unset($parts[$existing_event['owner']]);
$event['ical_sender_uid'] = key($parts); $event['ical_sender_uid'] = key($parts);
@ -2600,6 +2598,18 @@ class calendar_uiforms extends calendar_ui
unset($changes['recure']); unset($changes['recure']);
} }
// if start changed, make old start-time available to template
if (isset($changes['start']) && $_event['start'] != $changes['start'])
{
$_event['old_start'] = $changes['start'];
}
// if we have an explicit exception, always report that as a change from the regular recurrence
elseif (!empty($_event['recurrence']) && empty($_event['recur_type']) && $_event['recurrence'] != $_event['start'])
{
$_event['old_start'] = $changes['start'] = $_event['recurrence'];
$changes['end'] = $_event['recurrence'] + $_event['end']-$_event['start'];
}
return $changes; return $changes;
} }

View File

@ -78,7 +78,7 @@
<et2-description value="Title"></et2-description> <et2-description value="Title"></et2-description>
<et2-description id="title" noLang="1" class="@changed[title]"></et2-description> <et2-description id="title" noLang="1" class="@changed[title]"></et2-description>
</row> </row>
<row class="row" disabled="!$cont[location]$cont[##videoconference]"> <row class="row" disabled="!$cont[location]{$cont['##videoconference']}"> <!-- '##videoconference' must be in quotes because of #! -->
<et2-description value="Location"></et2-description> <et2-description value="Location"></et2-description>
<et2-hbox> <et2-hbox>
<et2-description id="location" noLang="1" class="@changed[location]"></et2-description> <et2-description id="location" noLang="1" class="@changed[location]"></et2-description>
@ -90,6 +90,7 @@
<et2-description value="Date"></et2-description> <et2-description value="Date"></et2-description>
<et2-hbox class="dates"> <et2-hbox class="dates">
<et2-date-time id="start" readonly="true" class="@changed[start]"></et2-date-time> <et2-date-time id="start" readonly="true" class="@changed[start]"></et2-date-time>
<et2-date-time id="old_start" readonly="true" class="meetingRequestChangedValue" disabled="!@old_start"></et2-date-time>
<et2-date-time label="-" id="end" readonly="true" class="@changed[end]"></et2-date-time> <et2-date-time label="-" id="end" readonly="true" class="@changed[end]"></et2-date-time>
</et2-hbox> </et2-hbox>
</row> </row>
@ -111,7 +112,7 @@
</row> </row>
<row class="row" valign="top"> <row class="row" valign="top">
<et2-description value="Participants"></et2-description> <et2-description value="Participants"></et2-description>
<grid id="participantChanges" class="meetingRequestParticipants"> <grid id="participantChanges" class="meetingRequestParticipants $cont[changed][participants]">
<columns> <columns>
<column/> <column/>
</columns> </columns>
@ -169,6 +170,7 @@
} }
et2-hbox.dates { et2-hbox.dates {
padding-left: 5px; padding-left: 5px;
width: fit-content;
} }
.meetingWarning tr.th { .meetingWarning tr.th {
background-color: #dc2625; background-color: #dc2625;
@ -176,7 +178,7 @@
.meetingWarning tr.row td { .meetingWarning tr.row td {
font-size: 120% !important; font-size: 120% !important;
} }
.meetingRequestError, .meetingRequestChanged, .meetingRequestChangedStatus et2-description[id$=status\]], .meetingRequestParticipantDeleted { .meetingRequestError, .meetingRequestChanged, .meetingRequestChangedStatus et2-description[id$=status\]] {
font-style: italic; font-style: italic;
color: red; color: red;
} }
@ -184,7 +186,8 @@
padding: 0 !important; padding: 0 !important;
padding-left: 0 !important; padding-left: 0 !important;
} }
.meetingRequestParticipantDeleted { .meetingRequestParticipantDeleted, .meetingRequestChangedValue {
font-style: italic;
text-decoration: line-through; text-decoration: line-through;
} }
</styles> </styles>