mirror of
https://github.com/EGroupware/egroupware.git
synced 2025-02-25 22:51:43 +01:00
* Calendar/Mail: improved display of meeting requests: what's changed, single recurrence or whole series, display and enter comment when accepting/rejecting a request
This commit is contained in:
parent
8a984c46ea
commit
c37fd3e380
@ -900,446 +900,474 @@ class calendar_boupdate extends calendar_bo
|
||||
*/
|
||||
function _send_update($msg_type, $to_notify, $old_event, $new_event=null, $user=0, ?array $alarm=null, $ignore_prefs = false)
|
||||
{
|
||||
//error_log(__METHOD__."($msg_type, ".json_encode($to_notify).", ..., ".json_encode($new_event).", ...)");
|
||||
if (!is_array($to_notify))
|
||||
{
|
||||
$to_notify = array();
|
||||
}
|
||||
$notify_externals = $new_event ? ($new_event['##notify_externals']??null) : ($old_event['##notify_externals']??null);
|
||||
$disinvited = $msg_type == MSG_DISINVITE ? array_keys($to_notify) : array();
|
||||
|
||||
$owner = $old_event ? $old_event['owner'] : $new_event['owner'];
|
||||
if($owner && !isset($to_notify[$owner]) && $msg_type != MSG_ALARM)
|
||||
{
|
||||
$to_notify[$owner] = 'OCHAIR'; // always include the event-owner
|
||||
}
|
||||
|
||||
// ignore events in the past (give a tolerance of 10 seconds for the script)
|
||||
if($new_event && !$this->eventInFuture($new_event) || !$new_event && $old_event && !$this->eventInFuture($old_event))
|
||||
{
|
||||
error_log(__METHOD__."($msg_type, ".json_encode($to_notify).", ..., ".json_encode($new_event).", ...) --> ignoring event in the past: start=".
|
||||
date('Y-m-d H:i:s', ($new_event ?: $old_event)['start'])." < ".date('Y-m-d H:i:s', $this->now_su-10));
|
||||
return False;
|
||||
}
|
||||
// check if default timezone is set correctly to server-timezone (ical-parser messes with it!!!)
|
||||
if($GLOBALS['egw_info']['server']['server_timezone'] && ($tz = date_default_timezone_get()) != $GLOBALS['egw_info']['server']['server_timezone'])
|
||||
{
|
||||
$restore_tz = $tz;
|
||||
date_default_timezone_set($GLOBALS['egw_info']['server']['server_timezone']);
|
||||
}
|
||||
$temp_user = $GLOBALS['egw_info']['user']; // save user-date of the enviroment to restore it after
|
||||
|
||||
if (!$user)
|
||||
{
|
||||
$user = $temp_user['account_id'];
|
||||
}
|
||||
$lang = $GLOBALS['egw_info']['user']['preferences']['common']['lang'];
|
||||
if ($GLOBALS['egw']->preferences->account_id != $user)
|
||||
{
|
||||
// Get correct preferences
|
||||
$GLOBALS['egw']->preferences->__construct(is_numeric($user) ? $user : $temp_user['account_id']);
|
||||
$GLOBALS['egw_info']['user']['preferences'] = $GLOBALS['egw']->preferences->read_repository();
|
||||
|
||||
// If target user/participant is not an account, try to get good notification message
|
||||
if(!is_numeric($user))
|
||||
try {
|
||||
//error_log(__METHOD__."($msg_type, ".json_encode($to_notify).", ..., ".json_encode($new_event).", ...)");
|
||||
if (!is_array($to_notify))
|
||||
{
|
||||
$res_info = $this->resource_info($user);
|
||||
$title = $res_info['name'] ?: Link::title($res_info['app'], $res_info['res_id']) ?: $res_info['res_id'] ?: $user;
|
||||
$GLOBALS['egw']->preferences->values['fullname'] = $GLOBALS['egw']->preferences->values['lastname'] = $title;
|
||||
$GLOBALS['egw']->preferences->values['firstname'] = '';
|
||||
$msg = $GLOBALS['egw']->preferences->user['calendar']['notifyResponse'] ?: $GLOBALS['egw']->preferences->default['calendar']['notifyResponse'] ?: $GLOBALS['egw']->preferences->forced['calendar']['notifyResponse'];
|
||||
$GLOBALS['egw_info']['user']['preferences']['calendar']['notifyResponse'] = $GLOBALS['egw']->preferences->parse_notify(
|
||||
$msg
|
||||
);
|
||||
$to_notify = array();
|
||||
}
|
||||
$notify_externals = $new_event ? ($new_event['##notify_externals'] ?? null) : ($old_event['##notify_externals'] ?? null);
|
||||
$disinvited = $msg_type == MSG_DISINVITE ? array_keys($to_notify) : array();
|
||||
|
||||
$owner = $old_event ? $old_event['owner'] : $new_event['owner'];
|
||||
if ($owner && !isset($to_notify[$owner]) && $msg_type != MSG_ALARM)
|
||||
{
|
||||
$to_notify[$owner] = 'OCHAIR'; // always include the event-owner
|
||||
}
|
||||
|
||||
}
|
||||
$senderid = $this->user;
|
||||
$event = $msg_type == MSG_ADDED || $msg_type == MSG_MODIFIED ? $new_event : $old_event;
|
||||
|
||||
// add all group-members to the notification, unless they are already participants
|
||||
foreach($to_notify as $userid => $statusid)
|
||||
{
|
||||
if (is_numeric($userid) && $GLOBALS['egw']->accounts->get_type($userid) == 'g' &&
|
||||
($members = $GLOBALS['egw']->accounts->members($userid, true)))
|
||||
// ignore events in the past (give a tolerance of 10 seconds for the script)
|
||||
if ($new_event && !$this->eventInFuture($new_event) || !$new_event && $old_event && !$this->eventInFuture($old_event))
|
||||
{
|
||||
foreach($members as $member)
|
||||
error_log(__METHOD__ . "($msg_type, " . json_encode($to_notify) . ", ..., " . json_encode($new_event) . ", ...) --> ignoring event in the past: start=" .
|
||||
date('Y-m-d H:i:s', ($new_event ?: $old_event)['start']) . " < " . date('Y-m-d H:i:s', $this->now_su - 10));
|
||||
return False;
|
||||
}
|
||||
// check if default timezone is set correctly to server-timezone (ical-parser messes with it!!!)
|
||||
if ($GLOBALS['egw_info']['server']['server_timezone'] && ($tz = date_default_timezone_get()) != $GLOBALS['egw_info']['server']['server_timezone'])
|
||||
{
|
||||
$restore_tz = $tz;
|
||||
date_default_timezone_set($GLOBALS['egw_info']['server']['server_timezone']);
|
||||
}
|
||||
$temp_user = $GLOBALS['egw_info']['user']; // save user-date of the enviroment to restore it after
|
||||
|
||||
if (!$user)
|
||||
{
|
||||
$user = $temp_user['account_id'];
|
||||
}
|
||||
$lang = $GLOBALS['egw_info']['user']['preferences']['common']['lang'];
|
||||
if ($GLOBALS['egw']->preferences->account_id != $user)
|
||||
{
|
||||
// Get correct preferences
|
||||
$GLOBALS['egw']->preferences->__construct(is_numeric($user) ? $user : $temp_user['account_id']);
|
||||
$GLOBALS['egw_info']['user']['preferences'] = $GLOBALS['egw']->preferences->read_repository();
|
||||
|
||||
// If target user/participant is not an account, try to get good notification message
|
||||
if (!is_numeric($user))
|
||||
{
|
||||
if (!isset($to_notify[$member]))
|
||||
$res_info = $this->resource_info($user);
|
||||
$title = $res_info['name'] ?: Link::title($res_info['app'], $res_info['res_id']) ?: $res_info['res_id'] ?: $user;
|
||||
$GLOBALS['egw']->preferences->values['fullname'] = $GLOBALS['egw']->preferences->values['lastname'] = $title;
|
||||
$GLOBALS['egw']->preferences->values['firstname'] = '';
|
||||
$msg = $GLOBALS['egw']->preferences->user['calendar']['notifyResponse'] ?: $GLOBALS['egw']->preferences->default['calendar']['notifyResponse'] ?: $GLOBALS['egw']->preferences->forced['calendar']['notifyResponse'];
|
||||
$GLOBALS['egw_info']['user']['preferences']['calendar']['notifyResponse'] = $GLOBALS['egw']->preferences->parse_notify(
|
||||
$msg
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
$senderid = $this->user;
|
||||
$event = $msg_type == MSG_ADDED || $msg_type == MSG_MODIFIED ? $new_event : $old_event;
|
||||
|
||||
// add all group-members to the notification, unless they are already participants
|
||||
foreach ($to_notify as $userid => $statusid)
|
||||
{
|
||||
if (is_numeric($userid) && $GLOBALS['egw']->accounts->get_type($userid) == 'g' &&
|
||||
($members = $GLOBALS['egw']->accounts->members($userid, true)))
|
||||
{
|
||||
foreach ($members as $member)
|
||||
{
|
||||
$to_notify[$member] = 'G'; // Group-invitation
|
||||
if (!isset($to_notify[$member]))
|
||||
{
|
||||
$to_notify[$member] = 'G'; // Group-invitation
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// unless we notify externals about everything aka 'responses'
|
||||
// we will notify only an external chair, if only one exists
|
||||
if (($notify_externals ?: $GLOBALS['egw_info']['user']['calendar']['notify_externals'] ?? null) !== 'responses')
|
||||
{
|
||||
// check if we have *only* an external chair
|
||||
$chair = null;
|
||||
foreach($to_notify as $userid => $statusid)
|
||||
// unless we notify externals about everything aka 'responses'
|
||||
// we will notify only an external chair, if only one exists
|
||||
if (($notify_externals ?: $GLOBALS['egw_info']['user']['calendar']['notify_externals'] ?? null) !== 'responses')
|
||||
{
|
||||
// check if we have *only* an external chair
|
||||
$chair = null;
|
||||
foreach ($to_notify as $userid => $statusid)
|
||||
{
|
||||
$res_info = $quantity = $role = null;
|
||||
calendar_so::split_status($statusid, $quantity, $role);
|
||||
if ($role == 'CHAIR' && (empty($chair) || !is_numeric($chair)))
|
||||
{
|
||||
$chair = $userid;
|
||||
}
|
||||
}
|
||||
// *only* an external chair --> do not notify anyone, but the external chair and the current user
|
||||
if (!empty($chair) && !is_numeric($chair))
|
||||
{
|
||||
$to_notify = array($chair => $to_notify[$chair]) +
|
||||
(isset($to_notify[$user]) ? array($user => $to_notify[$user]) : array());
|
||||
}
|
||||
}
|
||||
|
||||
// Event is passed in user time, make sure that's taken into account for date calculations
|
||||
$user_prefs = $GLOBALS['egw_info']['user']['preferences'];
|
||||
$date = new Api\DateTime('now', new DateTimeZone($user_prefs['common']['tz']));
|
||||
$startdate = new Api\DateTime($event['start'], new DateTimeZone($user_prefs['common']['tz']));
|
||||
$enddate = new Api\DateTime($event['end'], new DateTimeZone($user_prefs['common']['tz']));
|
||||
$modified = new Api\DateTime($event['modified'], new DateTimeZone($user_prefs['common']['tz']));
|
||||
if ($old_event) $olddate = new Api\DateTime($old_event['start'], new DateTimeZone($user_prefs['common']['tz']));
|
||||
$rdates = array_map(static function ($rdate) use ($user_prefs) {
|
||||
return new Api\DateTime($rdate, new DateTimeZone($user_prefs['common']['tz']));
|
||||
}, $event['recur_rdates'] ?? []);
|
||||
$recur_date = isset($event['recur_date']) ? new Api\DateTime($event['recur_date'], new DateTimeZone($user_prefs['common']['tz'])) : null;
|
||||
$recurrence = isset($event['recurrence']) ? new Api\DateTime($event['recurrence'], new DateTimeZone($user_prefs['common']['tz'])) : null;
|
||||
|
||||
//error_log(__METHOD__."() date_default_timezone_get()=".date_default_timezone_get().", user-timezone=".Api\DateTime::$user_timezone->getName().", startdate=".$startdate->format().", enddate=".$enddate->format().", updated=".$modified->format().", olddate=".($olddate ? $olddate->format() : ''));
|
||||
$owner_prefs = $ics = null;
|
||||
foreach ($to_notify as $userid => $statusid)
|
||||
{
|
||||
$res_info = $quantity = $role = null;
|
||||
calendar_so::split_status($statusid, $quantity, $role);
|
||||
if ($role == 'CHAIR' && (empty($chair) || !is_numeric($chair)))
|
||||
if ($this->debug > 0) error_log(__METHOD__ . " trying to notify $userid, with $statusid ($role)");
|
||||
|
||||
// hack to add videoconference in event description, by always setting $cleared_event
|
||||
// Can't re-load, if we're notifying of a cancelled recurrence we'll load the next event in the series
|
||||
$cleared_event = $event;
|
||||
|
||||
if (!is_numeric($userid))
|
||||
{
|
||||
$chair = $userid;
|
||||
}
|
||||
}
|
||||
// *only* an external chair --> do not notify anyone, but the external chair and the current user
|
||||
if (!empty($chair) && !is_numeric($chair))
|
||||
{
|
||||
$to_notify = array($chair => $to_notify[$chair])+
|
||||
(isset($to_notify[$user]) ? array($user => $to_notify[$user]) : array());
|
||||
}
|
||||
}
|
||||
$res_info = $this->resource_info($userid);
|
||||
|
||||
// Event is passed in user time, make sure that's taken into account for date calculations
|
||||
$user_prefs = $GLOBALS['egw_info']['user']['preferences'];
|
||||
$date = new Api\DateTime('now',new DateTimeZone($user_prefs['common']['tz']));
|
||||
$startdate = new Api\DateTime($event['start'], new DateTimeZone($user_prefs['common']['tz']));
|
||||
$enddate = new Api\DateTime($event['end'], new DateTimeZone($user_prefs['common']['tz']));
|
||||
$modified = new Api\DateTime($event['modified'], new DateTimeZone($user_prefs['common']['tz']));
|
||||
if ($old_event) $olddate = new Api\DateTime($old_event['start'], new DateTimeZone($user_prefs['common']['tz']));
|
||||
$rdates = array_map(static function($rdate) use ($user_prefs)
|
||||
{
|
||||
return new Api\DateTime($rdate, new DateTimeZone($user_prefs['common']['tz']));
|
||||
}, $event['recur_rdates'] ?? []);
|
||||
$recur_date = isset($event['recur_date']) ? new Api\DateTime($event['recur_date'], new DateTimeZone($user_prefs['common']['tz'])) : null;
|
||||
|
||||
//error_log(__METHOD__."() date_default_timezone_get()=".date_default_timezone_get().", user-timezone=".Api\DateTime::$user_timezone->getName().", startdate=".$startdate->format().", enddate=".$enddate->format().", updated=".$modified->format().", olddate=".($olddate ? $olddate->format() : ''));
|
||||
$owner_prefs = $ics = null;
|
||||
foreach($to_notify as $userid => $statusid)
|
||||
{
|
||||
$res_info = $quantity = $role = null;
|
||||
calendar_so::split_status($statusid, $quantity, $role);
|
||||
if ($this->debug > 0) error_log(__METHOD__." trying to notify $userid, with $statusid ($role)");
|
||||
|
||||
// hack to add videoconference in event description, by always setting $cleared_event
|
||||
// Can't re-load, if we're notifying of a cancelled recurrence we'll load the next event in the series
|
||||
$cleared_event = $event;
|
||||
|
||||
if (!is_numeric($userid))
|
||||
{
|
||||
$res_info = $this->resource_info($userid);
|
||||
|
||||
// check if responsible for a resource has read rights on event (might be private!)
|
||||
if ($res_info['app'] == 'resources' && $res_info['responsible'] &&
|
||||
!$this->check_perms(Acl::READ, $event, 0, 'ts', null, $res_info['responsible']))
|
||||
{
|
||||
// --> use only details from (private-)cleared event only containing resource ($userid)
|
||||
// reading server timezone, to be able to use cleared event for iCal generation further down
|
||||
//$cleared_event = $this->read($event['id'], null, true, 'server');
|
||||
$this->clear_private_infos($cleared_event, array($userid));
|
||||
}
|
||||
$userid = $res_info['responsible'] ?? null;
|
||||
|
||||
if (empty($userid)) // no resource responsible: $userid===0
|
||||
{
|
||||
if (empty($res_info['email'])) continue; // no way to notify
|
||||
// check if event-owner wants non-EGroupware users notified
|
||||
if (is_null($owner_prefs))
|
||||
// check if responsible for a resource has read rights on event (might be private!)
|
||||
if ($res_info['app'] == 'resources' && $res_info['responsible'] &&
|
||||
!$this->check_perms(Acl::READ, $event, 0, 'ts', null, $res_info['responsible']))
|
||||
{
|
||||
$preferences = new Api\Preferences($owner);
|
||||
$owner_prefs = $preferences->read_repository();
|
||||
if (!empty($notify_externals)) $owner_prefs['calendar']['notify_externals'] = $notify_externals;
|
||||
// --> use only details from (private-)cleared event only containing resource ($userid)
|
||||
// reading server timezone, to be able to use cleared event for iCal generation further down
|
||||
//$cleared_event = $this->read($event['id'], null, true, 'server');
|
||||
$this->clear_private_infos($cleared_event, array($userid));
|
||||
}
|
||||
if ($role != 'CHAIR' && // always notify externals CHAIRs
|
||||
(empty($owner_prefs['calendar']['notify_externals']) ||
|
||||
$owner_prefs['calendar']['notify_externals'] == 'no'))
|
||||
$userid = $res_info['responsible'] ?? null;
|
||||
|
||||
if (empty($userid)) // no resource responsible: $userid===0
|
||||
{
|
||||
if (empty($res_info['email'])) continue; // no way to notify
|
||||
// check if event-owner wants non-EGroupware users notified
|
||||
if (is_null($owner_prefs))
|
||||
{
|
||||
$preferences = new Api\Preferences($owner);
|
||||
$owner_prefs = $preferences->read_repository();
|
||||
if (!empty($notify_externals)) $owner_prefs['calendar']['notify_externals'] = $notify_externals;
|
||||
}
|
||||
if ($role != 'CHAIR' && // always notify externals CHAIRs
|
||||
(empty($owner_prefs['calendar']['notify_externals']) ||
|
||||
$owner_prefs['calendar']['notify_externals'] == 'no'))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
$userid = $res_info['email'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($statusid == 'R' || $GLOBALS['egw']->accounts->get_type($userid) == 'g')
|
||||
{
|
||||
continue; // dont notify rejected participants or groups
|
||||
}
|
||||
|
||||
if ($userid != $GLOBALS['egw_info']['user']['account_id'] ||
|
||||
($userid == $GLOBALS['egw_info']['user']['account_id'] &&
|
||||
$user_prefs['calendar']['receive_own_updates'] == 1) ||
|
||||
$msg_type == MSG_ALARM)
|
||||
{
|
||||
$tfn = $tln = $lid = null; //cleanup of lastname and fullname (in case they are set in a previous loop)
|
||||
if (is_numeric($userid))
|
||||
{
|
||||
$preferences = new Api\Preferences($userid);
|
||||
$GLOBALS['egw_info']['user']['preferences'] = $part_prefs = $preferences->read_repository();
|
||||
$fullname = Api\Accounts::username($userid);
|
||||
$tfn = Api\Accounts::id2name($userid, 'account_firstname');
|
||||
$tln = Api\Accounts::id2name($userid, 'account_lastname');
|
||||
}
|
||||
else // external email address: use Api\Preferences of event-owner, plus some hardcoded settings (eg. ical notification)
|
||||
{
|
||||
if (is_null($owner_prefs))
|
||||
{
|
||||
$preferences = new Api\Preferences($owner);
|
||||
$GLOBALS['egw_info']['user']['preferences'] = $owner_prefs = $preferences->read_repository();
|
||||
if (!empty($notify_externals)) $owner_prefs['calendar']['notify_externals'] = $notify_externals;
|
||||
}
|
||||
$part_prefs = $owner_prefs;
|
||||
$part_prefs['calendar']['receive_updates'] = $owner_prefs['calendar']['notify_externals'];
|
||||
$part_prefs['calendar']['update_format'] = 'ical'; // use ical format
|
||||
$fullname = $res_info && !empty($res_info['name']) ? $res_info['name'] : $userid;
|
||||
}
|
||||
$m_type = $msg_type;
|
||||
if (!$ignore_prefs && !$this->update_requested($userid, $part_prefs, $m_type, $old_event, $new_event, $role,
|
||||
$event['participants'][$GLOBALS['egw_info']['user']['account_id']]))
|
||||
{
|
||||
//error_log("--> Update/notification NOT requested / ignored");
|
||||
continue;
|
||||
}
|
||||
$userid = $res_info['email'];
|
||||
}
|
||||
}
|
||||
$action = $notify_msg = null;
|
||||
$method = $this->msg_type2ical_method($m_type, $action, $notify_msg, $user_prefs['calendar']);
|
||||
|
||||
if ($statusid == 'R' || $GLOBALS['egw']->accounts->get_type($userid) == 'g')
|
||||
{
|
||||
continue; // dont notify rejected participants or groups
|
||||
}
|
||||
|
||||
if ($userid != $GLOBALS['egw_info']['user']['account_id'] ||
|
||||
($userid == $GLOBALS['egw_info']['user']['account_id'] &&
|
||||
$user_prefs['calendar']['receive_own_updates']==1) ||
|
||||
$msg_type == MSG_ALARM)
|
||||
{
|
||||
$tfn = $tln = $lid = null; //cleanup of lastname and fullname (in case they are set in a previous loop)
|
||||
if (is_numeric($userid))
|
||||
{
|
||||
$preferences = new Api\Preferences($userid);
|
||||
$GLOBALS['egw_info']['user']['preferences'] = $part_prefs = $preferences->read_repository();
|
||||
$fullname = Api\Accounts::username($userid);
|
||||
$tfn = Api\Accounts::id2name($userid,'account_firstname');
|
||||
$tln = Api\Accounts::id2name($userid,'account_lastname');
|
||||
}
|
||||
else // external email address: use Api\Preferences of event-owner, plus some hardcoded settings (eg. ical notification)
|
||||
{
|
||||
if (is_null($owner_prefs))
|
||||
if ($lang !== $part_prefs['common']['lang'])
|
||||
{
|
||||
$preferences = new Api\Preferences($owner);
|
||||
$GLOBALS['egw_info']['user']['preferences'] = $owner_prefs = $preferences->read_repository();
|
||||
if (!empty($notify_externals)) $owner_prefs['calendar']['notify_externals'] = $notify_externals;
|
||||
Api\Translation::init();
|
||||
$lang = $part_prefs['common']['lang'];
|
||||
}
|
||||
$part_prefs = $owner_prefs;
|
||||
$part_prefs['calendar']['receive_updates'] = $owner_prefs['calendar']['notify_externals'];
|
||||
$part_prefs['calendar']['update_format'] = 'ical'; // use ical format
|
||||
$fullname = $res_info && !empty($res_info['name']) ? $res_info['name'] : $userid;
|
||||
}
|
||||
$m_type = $msg_type;
|
||||
if (!$ignore_prefs && !$this->update_requested($userid, $part_prefs, $m_type, $old_event, $new_event, $role,
|
||||
$event['participants'][$GLOBALS['egw_info']['user']['account_id']]))
|
||||
{
|
||||
//error_log("--> Update/notification NOT requested / ignored");
|
||||
continue;
|
||||
}
|
||||
$action = $notify_msg = null;
|
||||
$method = $this->msg_type2ical_method($m_type, $action, $notify_msg, $user_prefs['calendar']);
|
||||
|
||||
if ($lang !== $part_prefs['common']['lang'])
|
||||
{
|
||||
Api\Translation::init();
|
||||
$lang = $part_prefs['common']['lang'];
|
||||
}
|
||||
// Since we're running from cron, make sure notifications uses user's theme (for images)
|
||||
$GLOBALS['egw_info']['server']['template_set'] = $GLOBALS['egw_info']['user']['preferences']['common']['template_set'];
|
||||
|
||||
// Since we're running from cron, make sure notifications uses user's theme (for images)
|
||||
$GLOBALS['egw_info']['server']['template_set'] = $GLOBALS['egw_info']['user']['preferences']['common']['template_set'];
|
||||
$event_arr = null;
|
||||
$details = $this->_get_event_details(isset($cleared_event) ? $cleared_event : $event,
|
||||
$action, $event_arr, $disinvited);
|
||||
$details['fullname'] = is_numeric($user) ? Api\Accounts::username($user) : $fullname;
|
||||
$details['to-fullname'] = $fullname;
|
||||
$details['to-firstname'] = isset($tfn) ? $tfn : '';
|
||||
$details['to-lastname'] = isset($tln) ? $tln : '';
|
||||
|
||||
$event_arr = null;
|
||||
$details = $this->_get_event_details(isset($cleared_event) ? $cleared_event : $event,
|
||||
$action, $event_arr, $disinvited);
|
||||
$details['fullname'] = is_numeric($user) ? Api\Accounts::username($user) : $fullname;
|
||||
$details['to-fullname'] = $fullname;
|
||||
$details['to-firstname'] = isset($tfn)? $tfn: '';
|
||||
$details['to-lastname'] = isset($tln)? $tln: '';
|
||||
|
||||
// event is in user-time of current user, now we need to calculate the tz-difference to the notified user and take it into account
|
||||
if (!isset($part_prefs['common']['tz'])) $part_prefs['common']['tz'] = $GLOBALS['egw_info']['server']['server_timezone'];
|
||||
try
|
||||
{
|
||||
$timezone = new DateTimeZone($part_prefs['common']['tz']);
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
$timezone = new DateTimeZone($GLOBALS['egw_info']['server']['server_timezone']);
|
||||
}
|
||||
$timeformat = $part_prefs['common']['timeformat'];
|
||||
switch($timeformat)
|
||||
{
|
||||
case '24':
|
||||
$timeformat = 'H:i';
|
||||
break;
|
||||
case '12':
|
||||
$timeformat = 'h:i a';
|
||||
break;
|
||||
}
|
||||
$timeformat = $part_prefs['common']['dateformat'] . ', ' . $timeformat;
|
||||
|
||||
// Set dates:
|
||||
// $details in "preference" format, $cleared_event as DateTime so calendar_ical->exportVCal() gets
|
||||
// the times right, since it assumes a timestamp is in server time
|
||||
$cleared_event['start'] = $startdate->setTimezone($timezone);
|
||||
$details['startdate'] = $startdate->format($timeformat);
|
||||
|
||||
$cleared_event['end'] = $enddate->setTimezone($timezone);
|
||||
$details['enddate'] = $enddate->format($timeformat);
|
||||
|
||||
$cleared_event['updated'] = $modified->setTimezone($timezone);
|
||||
$details['updated'] = $modified->format($timeformat) . ', ' . Api\Accounts::username($event['modifier']);
|
||||
|
||||
// we also need to "fix" timezone for rdates, to not get wrong times!
|
||||
$cleared_event['recur_rdates'] = array_map(static function ($rdate) use ($timezone)
|
||||
{
|
||||
return $rdate->setTimezone($timezone);
|
||||
}, $rdates);
|
||||
|
||||
if (isset($recur_date))
|
||||
{
|
||||
$cleared_event['recur_date'] = $recur_date->setTimezone($timezone);
|
||||
$details['recur_date'] = $recur_date->format($timeformat);
|
||||
}
|
||||
|
||||
// Current date doesn't need to go into the cleared event, just for details
|
||||
$date->setTimezone($timezone);
|
||||
$details['date'] = $date->format($timeformat);
|
||||
|
||||
if ($old_event != False)
|
||||
{
|
||||
$olddate->setTimezone($timezone);
|
||||
$details['olddate'] = $olddate->format($timeformat);
|
||||
}
|
||||
// generate a personal videoconference url, if we need one
|
||||
if (!empty($event['##videoconference']) && !calendar_hooks::isVideoconferenceDisabled())
|
||||
{
|
||||
$avatar = new Api\Contacts\Photo(is_numeric($userid) ? "account:$userid" :
|
||||
(isset($res_info) && $res_info['type'] === 'c' ? $res_info['res_id'] : $userid),
|
||||
// disable sharing links currently, as sharing links from a different EGroupware user destroy the session
|
||||
true);
|
||||
|
||||
$details['videoconference'] = EGroupware\Status\Videoconference\Call::genMeetingUrl($event['##videoconference'], [
|
||||
'title' => $event['title'],
|
||||
'name' => $fullname,
|
||||
'email' => is_numeric($userid) ? Api\Accounts::id2name($userid, 'account_email') : $userid,
|
||||
'avatar' => (string)$avatar,
|
||||
'account_id' => $userid,
|
||||
'cal_id' => $details['id'],
|
||||
'notify_only' => true
|
||||
], ['participants' =>array_filter($event['participants'], function($key){return is_numeric($key);}, ARRAY_FILTER_USE_KEY)], $startdate, $enddate);
|
||||
$event_arr['videoconference'] = [
|
||||
'field' => lang('Video Conference'),
|
||||
'data' => $details['videoconference'],
|
||||
];
|
||||
// hack to add videoconference-url to ical, only if description was NOT cleared
|
||||
if (isset($cleared_event['description']))
|
||||
// event is in user-time of current user, now we need to calculate the tz-difference to the notified user and take it into account
|
||||
if (!isset($part_prefs['common']['tz'])) $part_prefs['common']['tz'] = $GLOBALS['egw_info']['server']['server_timezone'];
|
||||
try
|
||||
{
|
||||
$cleared_event['description'] = lang('Video conference').': '.$details['videoconference']."\n\n".$event['description'];
|
||||
$timezone = new DateTimeZone($part_prefs['common']['tz']);
|
||||
} catch (Exception $e)
|
||||
{
|
||||
$timezone = new DateTimeZone($GLOBALS['egw_info']['server']['server_timezone']);
|
||||
}
|
||||
}
|
||||
//error_log(__METHOD__."() userid=$userid, timezone=".$timezone->getName().", startdate=$details[startdate], enddate=$details[enddate], updated=$details[updated], olddate=$details[olddate]");
|
||||
$timeformat = $part_prefs['common']['timeformat'];
|
||||
switch ($timeformat)
|
||||
{
|
||||
case '24':
|
||||
$timeformat = 'H:i';
|
||||
break;
|
||||
case '12':
|
||||
$timeformat = 'h:i a';
|
||||
break;
|
||||
}
|
||||
$timeformat = $part_prefs['common']['dateformat'] . ', ' . $timeformat;
|
||||
|
||||
list($subject,$notify_body) = explode("\n",$GLOBALS['egw']->preferences->parse_notify($notify_msg,$details),2);
|
||||
// alarm is NOT an iCal method, therefore we have to use extened (no iCal)
|
||||
switch($msg_type == MSG_ALARM ? 'extended' : $part_prefs['calendar']['update_format'])
|
||||
{
|
||||
case 'ical':
|
||||
if (is_null($ics) || $m_type != $msg_type || $event['##videoconference']) // need different ical for organizer notification or videoconference join urls
|
||||
// Set dates:
|
||||
// $details in "preference" format, $cleared_event as DateTime so calendar_ical->exportVCal() gets
|
||||
// the times right, since it assumes a timestamp is in server time
|
||||
$cleared_event['start'] = $startdate->setTimezone($timezone);
|
||||
$details['startdate'] = $startdate->format($timeformat);
|
||||
|
||||
$cleared_event['end'] = $enddate->setTimezone($timezone);
|
||||
$details['enddate'] = $enddate->format($timeformat);
|
||||
|
||||
$cleared_event['updated'] = $modified->setTimezone($timezone);
|
||||
$details['updated'] = $modified->format($timeformat) . ', ' . Api\Accounts::username($event['modifier']);
|
||||
|
||||
// we also need to "fix" timezone for rdates, to not get wrong times!
|
||||
$cleared_event['recur_rdates'] = array_map(static function ($rdate) use ($timezone) {
|
||||
return $rdate->setTimezone($timezone);
|
||||
}, $rdates);
|
||||
|
||||
if (isset($recur_date))
|
||||
{
|
||||
$cleared_event['recur_date'] = $recur_date->setTimezone($timezone);
|
||||
$details['recur_date'] = $recur_date->format($timeformat);
|
||||
}
|
||||
if (isset($recurrence))
|
||||
{
|
||||
$cleared_event['recurrence'] = $recurrence->setTimezone($timezone);
|
||||
}
|
||||
|
||||
// Current date doesn't need to go into the cleared event, just for details
|
||||
$date->setTimezone($timezone);
|
||||
$details['date'] = $date->format($timeformat);
|
||||
|
||||
if ($old_event != False)
|
||||
{
|
||||
$olddate->setTimezone($timezone);
|
||||
$details['olddate'] = $olddate->format($timeformat);
|
||||
}
|
||||
// generate a personal videoconference url, if we need one
|
||||
if (!empty($event['##videoconference']) && !calendar_hooks::isVideoconferenceDisabled())
|
||||
{
|
||||
$avatar = new Api\Contacts\Photo(is_numeric($userid) ? "account:$userid" :
|
||||
(isset($res_info) && $res_info['type'] === 'c' ? $res_info['res_id'] : $userid),
|
||||
// disable sharing links currently, as sharing links from a different EGroupware user destroy the session
|
||||
true);
|
||||
|
||||
$details['videoconference'] = EGroupware\Status\Videoconference\Call::genMeetingUrl($event['##videoconference'], [
|
||||
'title' => $event['title'],
|
||||
'name' => $fullname,
|
||||
'email' => is_numeric($userid) ? Api\Accounts::id2name($userid, 'account_email') : $userid,
|
||||
'avatar' => (string)$avatar,
|
||||
'account_id' => $userid,
|
||||
'cal_id' => $details['id'],
|
||||
'notify_only' => true
|
||||
], ['participants' => array_filter($event['participants'], function ($key) {
|
||||
return is_numeric($key);
|
||||
}, ARRAY_FILTER_USE_KEY)], $startdate, $enddate);
|
||||
$event_arr['videoconference'] = [
|
||||
'field' => lang('Video Conference'),
|
||||
'data' => $details['videoconference'],
|
||||
];
|
||||
// hack to add videoconference-url to ical, only if description was NOT cleared
|
||||
if (isset($cleared_event['description']))
|
||||
{
|
||||
$calendar_ical = new calendar_ical();
|
||||
$calendar_ical->setSupportedFields('full'); // full iCal fields+event TZ
|
||||
// we need to pass $event[id] so iCal class reads event again,
|
||||
// as event is in user TZ, but iCal class expects server TZ!
|
||||
$ics = $calendar_ical->exportVCal([$cleared_event],
|
||||
'2.0', $method, $cleared_event['recur_date'] ?? null,
|
||||
'', 'utf-8', $method == 'REPLY' ? $user : 0
|
||||
);
|
||||
unset($calendar_ical);
|
||||
$cleared_event['description'] = lang('Video conference') . ': ' . $details['videoconference'] . "\n\n" . $event['description'];
|
||||
}
|
||||
$attachment = array(
|
||||
'string' => $ics,
|
||||
'filename' => 'cal.ics',
|
||||
'encoding' => '8bit',
|
||||
'type' => 'text/calendar; method='.$method,
|
||||
);
|
||||
if ($m_type != $msg_type) unset($ics);
|
||||
$subject = isset($cleared_event) ? $cleared_event['title'] : $event['title'];
|
||||
// fall through
|
||||
case 'extended':
|
||||
}
|
||||
//error_log(__METHOD__."() userid=$userid, timezone=".$timezone->getName().", startdate=$details[startdate], enddate=$details[enddate], updated=$details[updated], olddate=$details[olddate]");
|
||||
|
||||
$details_body = lang('Event Details follow').":\n";
|
||||
foreach($event_arr as $key => $val)
|
||||
{
|
||||
if(!empty($details[$key]))
|
||||
list($subject, $notify_body) = explode("\n", $GLOBALS['egw']->preferences->parse_notify($notify_msg, $details), 2);
|
||||
// alarm is NOT an iCal method, therefore we have to use extened (no iCal)
|
||||
switch ($msg_type == MSG_ALARM ? 'extended' : $part_prefs['calendar']['update_format'])
|
||||
{
|
||||
case 'ical':
|
||||
if (is_null($ics) || $m_type != $msg_type || $event['##videoconference']) // need different ical for organizer notification or videoconference join urls
|
||||
{
|
||||
switch($key)
|
||||
{
|
||||
case 'access':
|
||||
case 'priority':
|
||||
case 'link':
|
||||
case 'description':
|
||||
case 'title':
|
||||
break;
|
||||
default:
|
||||
$details_body .= sprintf("%-20s %s\n",$val['field'].':',$details[$key]);
|
||||
break;
|
||||
}
|
||||
$calendar_ical = new calendar_ical();
|
||||
$calendar_ical->setSupportedFields('full'); // full iCal fields+event TZ
|
||||
// we need to pass $event[id] so iCal class reads event again,
|
||||
// as event is in user TZ, but iCal class expects server TZ!
|
||||
$ics = $calendar_ical->exportVCal([$cleared_event],
|
||||
'2.0', $method, $cleared_event['recur_date'] ?? null,
|
||||
'', 'utf-8', $method == 'REPLY' ? $user : 0
|
||||
);
|
||||
unset($calendar_ical);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
// send via notification_app
|
||||
if($GLOBALS['egw_info']['apps']['notifications']['enabled'])
|
||||
{
|
||||
try {
|
||||
//error_log(__METHOD__."() notifying $userid from $senderid: $subject");
|
||||
$notification = new notifications();
|
||||
$notification->set_receivers(array($userid));
|
||||
$notification->set_sender($senderid);
|
||||
$notification->set_subject($subject);
|
||||
// as we want ical body to be just description, we can NOT set links, as they get appended to body
|
||||
if ($part_prefs['calendar']['update_format'] != 'ical')
|
||||
{
|
||||
$notification->set_message($notify_body."\n\n".$details['description']."\n\n".$details_body);
|
||||
$notification->set_links(array($details['link_arr']));
|
||||
}
|
||||
else
|
||||
{
|
||||
// iCal: description need to be separated from body by fancy separator
|
||||
$notification->set_message($notify_body."\n\n".$details_body."\n*~*~*~*~*~*~*~*~*~*\n\n".$details['description']);
|
||||
}
|
||||
// popup notifiactions: set subject, different message (without separator) and (always) links
|
||||
$notification->set_popupsubject($subject);
|
||||
$attachment = array(
|
||||
'string' => $ics,
|
||||
'filename' => 'cal.ics',
|
||||
'encoding' => '8bit',
|
||||
'type' => 'text/calendar; method=' . $method,
|
||||
);
|
||||
if ($m_type != $msg_type) unset($ics);
|
||||
$subject = isset($cleared_event) ? $cleared_event['title'] : $event['title'];
|
||||
// fall through
|
||||
case 'extended':
|
||||
|
||||
if ($method == 'REQUEST')
|
||||
$details_body = lang('Event Details follow') . ":\n";
|
||||
foreach ($event_arr as $key => $val)
|
||||
{
|
||||
if (!empty($details[$key]))
|
||||
{
|
||||
switch ($key)
|
||||
{
|
||||
case 'access':
|
||||
case 'priority':
|
||||
case 'link':
|
||||
case 'description':
|
||||
case 'title':
|
||||
break;
|
||||
default:
|
||||
$details_body .= sprintf("%-20s %s\n", $val['field'] . ':', $details[$key]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
// send via notification_app
|
||||
if ($GLOBALS['egw_info']['apps']['notifications']['enabled'])
|
||||
{
|
||||
try
|
||||
{
|
||||
// Add ACCEPT|REJECT|TENTATIVE actions
|
||||
$notification->set_popupdata('calendar', array(
|
||||
'event_id' => $event['id'],
|
||||
'user_id' => $userid,
|
||||
'type' => $m_type,
|
||||
'id' => $event['id'],
|
||||
'app' => 'calendar',
|
||||
'videoconference' => $details['videoconference'],
|
||||
), $event['id']);
|
||||
}
|
||||
if ($m_type === MSG_ALARM)
|
||||
{
|
||||
$notification->set_popupdata('calendar',
|
||||
array('egw_pr_notify' => 1,
|
||||
//error_log(__METHOD__."() notifying $userid from $senderid: $subject");
|
||||
$notification = new notifications();
|
||||
$notification->set_receivers(array($userid));
|
||||
$notification->set_sender($senderid);
|
||||
$notification->set_subject($subject);
|
||||
// as we want ical body to be just description, we can NOT set links, as they get appended to body
|
||||
if ($part_prefs['calendar']['update_format'] != 'ical')
|
||||
{
|
||||
$notification->set_message($notify_body . "\n\n" . $details['description'] . "\n\n" . $details_body);
|
||||
$notification->set_links(array($details['link_arr']));
|
||||
}
|
||||
else
|
||||
{
|
||||
// iCal: description need to be separated from body by fancy separator
|
||||
$notification->set_message($notify_body . "\n\n" . $details_body . "\n*~*~*~*~*~*~*~*~*~*\n\n" . $details['description']);
|
||||
}
|
||||
// popup notifiactions: set subject, different message (without separator) and (always) links
|
||||
$notification->set_popupsubject($subject);
|
||||
|
||||
if ($method == 'REQUEST')
|
||||
{
|
||||
// Add ACCEPT|REJECT|TENTATIVE actions
|
||||
$notification->set_popupdata('calendar', array(
|
||||
'event_id' => $event['id'],
|
||||
'user_id' => $userid,
|
||||
'type' => $m_type,
|
||||
'id' => $event['id'],
|
||||
'app' => 'calendar',
|
||||
'videoconference' => $details['videoconference'],
|
||||
'account_id' => $senderid,
|
||||
'name' => Api\Accounts::username($senderid)
|
||||
)
|
||||
+ ($alarm ? ['alarm-offset' => (int)$alarm['offset']] : []), $event['id']);
|
||||
}
|
||||
$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')));
|
||||
), $event['id']);
|
||||
}
|
||||
if ($m_type === MSG_ALARM)
|
||||
{
|
||||
$notification->set_popupdata('calendar',
|
||||
array('egw_pr_notify' => 1,
|
||||
'type' => $m_type,
|
||||
'videoconference' => $details['videoconference'],
|
||||
'account_id' => $senderid,
|
||||
'name' => Api\Accounts::username($senderid)
|
||||
)
|
||||
+ ($alarm ? ['alarm-offset' => (int)$alarm['offset']] : []), $event['id']);
|
||||
}
|
||||
$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')));
|
||||
|
||||
if(!empty($attachment)) { $notification->set_attachments(array($attachment)); }
|
||||
$notification->send();
|
||||
$errors = notifications::errors(true);
|
||||
if (!empty($attachment))
|
||||
{
|
||||
$notification->set_attachments(array($attachment));
|
||||
}
|
||||
$notification->send();
|
||||
$errors = notifications::errors(true);
|
||||
} catch (Exception $exception)
|
||||
{
|
||||
$errors = [$exception->getMessage()];
|
||||
continue;
|
||||
}
|
||||
}
|
||||
catch (Exception $exception) {
|
||||
$errors = [$exception->getMessage()];
|
||||
continue;
|
||||
else
|
||||
{
|
||||
$errors = [lang('Can not send any notifications because notifications app is not installed!')];
|
||||
}
|
||||
foreach ($errors as $error)
|
||||
{
|
||||
error_log(__METHOD__ . "() Error notifying $userid from $senderid: $subject: $error");
|
||||
// send notification errors via push to current user (not session, as alarms send via async job have none!)
|
||||
(new Api\Json\Push($GLOBALS['egw_info']['user']['account_id']))->message(
|
||||
lang('Error notifying %1', !is_numeric($userid) ? $userid :
|
||||
Api\Accounts::id2name($userid, 'account_fullname') . ' <' . Api\Accounts::id2name($userid, 'account_email') . '>') .
|
||||
"\n" . $subject . "\n" . $error, 'error');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$errors = [lang('Can not send any notifications because notifications app is not installed!')];
|
||||
}
|
||||
foreach($errors as $error)
|
||||
{
|
||||
error_log(__METHOD__."() Error notifying $userid from $senderid: $subject: $error");
|
||||
// send notification errors via push to current user (not session, as alarms send via async job have none!)
|
||||
(new Api\Json\Push($GLOBALS['egw_info']['user']['account_id']))->message(
|
||||
lang('Error notifying %1', !is_numeric($userid) ? $userid :
|
||||
Api\Accounts::id2name($userid, 'account_fullname').' <'.Api\Accounts::id2name($userid, 'account_email').'>').
|
||||
"\n".$subject."\n".$error, 'error');
|
||||
}
|
||||
}
|
||||
// restore the enviroment (preferences->read_repository() sets the timezone!)
|
||||
$GLOBALS['egw_info']['user'] = $temp_user;
|
||||
if ($GLOBALS['egw']->preferences->account_id != $temp_user['account_id'])
|
||||
{
|
||||
$GLOBALS['egw']->preferences->__construct($temp_user['account_id']);
|
||||
$GLOBALS['egw_info']['user']['preferences'] = $GLOBALS['egw']->preferences->read_repository();
|
||||
//echo "<p>".__METHOD__."() restored enviroment of #$temp_user[account_id] $temp_user[account_fullname]: tz={$GLOBALS['egw_info']['user']['preferences']['common']['tz']}</p>\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Loading other user's preferences can change current user's tz
|
||||
$GLOBALS['egw']->preferences->check_set_tz_offset();
|
||||
}
|
||||
if ($lang !== $GLOBALS['egw_info']['user']['preferences']['common']['lang'])
|
||||
{
|
||||
Api\Translation::init();
|
||||
}
|
||||
// restore timezone, in case we had to reset it to server-timezone
|
||||
if (!empty($restore_tz)) date_default_timezone_set($restore_tz);
|
||||
}
|
||||
// restore the enviroment (preferences->read_repository() sets the timezone!)
|
||||
$GLOBALS['egw_info']['user'] = $temp_user;
|
||||
if ($GLOBALS['egw']->preferences->account_id != $temp_user['account_id'])
|
||||
{
|
||||
$GLOBALS['egw']->preferences->__construct($temp_user['account_id']);
|
||||
$GLOBALS['egw_info']['user']['preferences'] = $GLOBALS['egw']->preferences->read_repository();
|
||||
//echo "<p>".__METHOD__."() restored enviroment of #$temp_user[account_id] $temp_user[account_fullname]: tz={$GLOBALS['egw_info']['user']['preferences']['common']['tz']}</p>\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Loading other user's preferences can change current user's tz
|
||||
$GLOBALS['egw']->preferences->check_set_tz_offset();
|
||||
}
|
||||
if ($lang !== $GLOBALS['egw_info']['user']['preferences']['common']['lang'])
|
||||
{
|
||||
Api\Translation::init();
|
||||
}
|
||||
// restore timezone, in case we had to reset it to server-timezone
|
||||
if (!empty($restore_tz)) date_default_timezone_set($restore_tz);
|
||||
catch (Throwable $e) {
|
||||
// logging all exceptions and errors to the error_log AND pushing them to user
|
||||
$message = null;
|
||||
_egw_log_exception($e,$message);
|
||||
$response = new Api\Json\Push($GLOBALS['egw_info']['user']['account_id']);
|
||||
$message .= ($message ? "\n\n" : '').$e->getMessage();
|
||||
|
||||
$message .= "\n\n".$e->getFile().' ('.$e->getLine().')';
|
||||
// only show trace (incl. function arguments) if explicitly enabled, eg. on a development system
|
||||
if ($GLOBALS['egw_info']['server']['exception_show_trace'])
|
||||
{
|
||||
$message .= "\n\n".$e->getTraceAsString();
|
||||
}
|
||||
$response->message($message, 'error');
|
||||
|
||||
// under PHP 8 the destructor is called to late and the response is not send
|
||||
$GLOBALS['egw']->__destruct();
|
||||
exit;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -1844,9 +1872,10 @@ class calendar_boupdate extends calendar_bo
|
||||
* DEPRECATED: we always (have to) update timestamp, as they are required for sync!
|
||||
* @param boolean|"NOPUSH" $skip_notification =false true: send NO notifications, default false = send them,
|
||||
* "NOPUSH": also do NOT send push notifications / call Link::notifiy(), which still happens for true
|
||||
* @param ?string $comment Comment to send with notification to organizer (as iCal COMMENT)
|
||||
* @return int number of changed recurrences
|
||||
*/
|
||||
function set_status($event,$uid,$status,$recur_date=0,$ignore_acl=false,$updateTS=true,$skip_notification=false)
|
||||
function set_status($event,$uid,$status,$recur_date=0,$ignore_acl=false,$updateTS=true,$skip_notification=false, ?string $comment=null)
|
||||
{
|
||||
unset($updateTS);
|
||||
|
||||
@ -1893,6 +1922,7 @@ class calendar_boupdate extends calendar_bo
|
||||
{
|
||||
if (!is_array($event)) $event = $this->read($cal_id);
|
||||
if (isset($recur_date)) $event = $this->read($event['id'],$recur_date); //re-read the actually edited recurring event
|
||||
if (!empty($comment)) $event['comment'] = $comment;
|
||||
$user_id = is_numeric($uid) ? (int)$uid : $uid;
|
||||
$this->send_update($status2msg[$status],$event['participants'],$event, null, $user_id);
|
||||
}
|
||||
|
@ -231,6 +231,7 @@ class calendar_ical extends calendar_boupdate
|
||||
'SEQUENCE' => 'etag',
|
||||
'STATUS' => 'status',
|
||||
'ATTACH' => 'attachments',
|
||||
'COMMENT' => 'comment',
|
||||
);
|
||||
|
||||
if (!is_array($this->supportedFields)) $this->setSupportedFields();
|
||||
@ -2236,6 +2237,7 @@ class calendar_ical extends calendar_boupdate
|
||||
'recurrence' => 'recurrence',
|
||||
'etag' => 'etag',
|
||||
'status' => 'status',
|
||||
'comment' => 'comment',
|
||||
);
|
||||
|
||||
|
||||
@ -3119,6 +3121,10 @@ class calendar_ical extends calendar_boupdate
|
||||
$event['##videoconference'] = $attributes['value'];
|
||||
break;
|
||||
|
||||
case 'COMMENT': // used e.g. at mailbox.org as comment in REPLYs send to the organize
|
||||
$event['comment'] = $attributes['value'];
|
||||
break;
|
||||
|
||||
// ignore all PROPS, we dont want to store like X-properties or unsupported props
|
||||
case 'DTSTAMP':
|
||||
case 'SEQUENCE':
|
||||
|
@ -2247,6 +2247,10 @@ class calendar_uiforms extends calendar_ui
|
||||
{
|
||||
$event['sender_warning'] = lang('The sender "%1" is NOT the extern organizer "%2", proceed with caution!', $ical_sender, $organizer);
|
||||
}
|
||||
if ($event['recurrence'])
|
||||
{
|
||||
$master = $this->bo->read($event['uid']);
|
||||
}
|
||||
switch(strtolower($ical_method))
|
||||
{
|
||||
case 'reply':
|
||||
@ -2262,7 +2266,7 @@ class calendar_uiforms extends calendar_ui
|
||||
{
|
||||
$event['sender_warning'] = lang('The sender "%1" is NOT the participant replying "%2", proceed with caution!', $ical_sender, $participant);
|
||||
}
|
||||
if ($event['ical_sender_uid'] && $this->bo->check_status_perms($event['ical_sender_uid'], $existing_event))
|
||||
if ($event['ical_sender_uid'])
|
||||
{
|
||||
$existing_status = $existing_event['participants'][$event['ical_sender_uid']];
|
||||
// check if email matches, in case we have now something like "Name <email>"
|
||||
@ -2287,31 +2291,41 @@ class calendar_uiforms extends calendar_ui
|
||||
calendar_so::split_status($existing_status, $quantity, $role);
|
||||
if ($existing_status != $event['ical_sender_status'])
|
||||
{
|
||||
$readonlys['button[apply]'] = false;
|
||||
$readonlys['button[apply]'] = !$this->bo->check_status_perms($event['ical_sender_uid'], $existing_event);
|
||||
}
|
||||
else
|
||||
{
|
||||
$event['error'] = lang('Status already applied');
|
||||
$event['meeting-request-status'] = lang('Status already applied');
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 'request':
|
||||
case 'add':
|
||||
$status = $existing_event['participants'][$user];
|
||||
calendar_so::split_status($status, $quantity, $role);
|
||||
if (!empty($extern_organizer) && self::event_changed($event, $existing_event))
|
||||
if (($changed = self::event_changed($event, $existing_event)))
|
||||
{
|
||||
$event['error'] = lang('The extern organizer changed the event!',);
|
||||
$readonlys['button[apply]'] = false;
|
||||
if (!empty($extern_organizer))
|
||||
{
|
||||
$event['error'] = lang('The extern organizer changed the event!');
|
||||
$readonlys['button[apply]'] = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
$event['error'] = lang('The organizer changed the event!');
|
||||
}
|
||||
//$event['error'] .= ' '.json_encode($changed);
|
||||
$event['changed'] = array_combine(array_keys($changed), array_fill(0, count($changed), 'meetingRequestChanged'));
|
||||
}
|
||||
elseif (isset($existing_event['participants'][$user]) &&
|
||||
if (isset($existing_event['participants'][$user]) &&
|
||||
$status != 'U' && isset($this->bo->verbose_status[$status]))
|
||||
{
|
||||
$event['error'] = lang('You already replied to this invitation with').': '.lang($this->bo->verbose_status[$status]);
|
||||
$event['meeting-request-status'] = lang('You already replied to this invitation with').': '.lang($this->bo->verbose_status[$status]);
|
||||
}
|
||||
else
|
||||
{
|
||||
$event['error'] = lang('Using already existing event on server.');
|
||||
$event['meeting-request-status'] = lang('Using already existing event on server.');
|
||||
}
|
||||
$user_and_memberships = $GLOBALS['egw']->accounts->memberships($user, true);
|
||||
$user_and_memberships[] = $user;
|
||||
@ -2354,7 +2368,7 @@ class calendar_uiforms extends calendar_ui
|
||||
$status && $status !== 'X' ? $status : 'U'; // X --> no status given --> U = unknown
|
||||
}
|
||||
//error_log(__METHOD__."(...) parsed as ".array2string($event));
|
||||
$event['recure'] = $this->bo->recure2string($event);
|
||||
$event['recure'] = $this->bo->recure2string($master ?? null ?: $event);
|
||||
$event['all_participants'] = implode(",\n",$this->bo->participants($event, true));
|
||||
|
||||
// EGroupware event has been deleted, don't let user resurrect it by accepting again
|
||||
@ -2371,8 +2385,7 @@ class calendar_uiforms extends calendar_ui
|
||||
(!empty($event['sender_warning']) ? "\n\n".$event['sender_warning'] : '');
|
||||
}
|
||||
// ignore events in the past (for recurring events check enddate!)
|
||||
elseif ($this->bo->date2ts($event['start']) < $this->bo->now_su &&
|
||||
(!$event['recur_type'] || $event['recur_enddate'] && $event['recur_enddate'] < $this->bo->now_su))
|
||||
elseif (!$this->bo->eventInFuture($event))
|
||||
{
|
||||
$event['error'] = lang('Requested meeting is in the past!');
|
||||
$readonlys['button[accept]'] = $readonlys['button[tentativ]'] =
|
||||
@ -2440,10 +2453,10 @@ class calendar_uiforms extends calendar_ui
|
||||
switch($button)
|
||||
{
|
||||
case 'reject':
|
||||
if (!$event['id'])
|
||||
if (empty($event['id']))
|
||||
{
|
||||
// send reply to organizer
|
||||
$this->bo->send_update(MSG_REJECTED,array('e'.$event['organizer'] => 'DCHAIR'),$event);
|
||||
$this->bo->send_update(MSG_REJECTED,array('e'.$event['organizer'] => 'DCHAIR'), $event);
|
||||
break; // no need to store rejected event
|
||||
}
|
||||
// fall-through
|
||||
@ -2476,7 +2489,7 @@ class calendar_uiforms extends calendar_ui
|
||||
$event['id'] = $event['old']['id'];
|
||||
}
|
||||
// set status and send notification / meeting response
|
||||
if ($this->bo->set_status($event['id'], $user, $status, $event['recurrence']))
|
||||
if ($this->bo->set_status($event['id'], $user, $status, $event['recurrence'], false, true, false, $event['comment']))
|
||||
{
|
||||
$msg[] = lang('Status changed');
|
||||
}
|
||||
@ -2520,6 +2533,9 @@ class calendar_uiforms extends calendar_ui
|
||||
case 'cancel':
|
||||
$event['ics_method_label'] = lang('Meeting canceled');
|
||||
break;
|
||||
case 'add':
|
||||
$event['ics_method_label'] = lang('Meeting request additional recurrence(s)');
|
||||
break;
|
||||
case 'request':
|
||||
default:
|
||||
$event['ics_method_label'] = lang('Meeting request');
|
||||
@ -2540,12 +2556,12 @@ class calendar_uiforms extends calendar_ui
|
||||
*
|
||||
* @param array& $_event invitation, on return user status changed to the one from old $old
|
||||
* @param array $_old existing event on server
|
||||
* @return boolean true if there are some changes, false if not
|
||||
* @return array with changed event attributes plus "recur" with any "recur_*" changed, empty array if nothing changed
|
||||
*/
|
||||
function event_changed(array &$_event, array $_old)
|
||||
{
|
||||
static $keys_to_check = array('start', 'end', 'title', 'description', 'location', 'participants',
|
||||
'recur_type', 'recur_data', 'recur_interval', 'recur_exception');
|
||||
'recur_type', 'recur_data', 'recur_interval', 'recur_exception', 'recur_rdates');
|
||||
|
||||
// only compare certain fields, taking account unset, null or '' values
|
||||
$event = array_intersect_key(array_diff($_event, [null, ''])+array('recur_exception'=>array()), array_flip($keys_to_check));
|
||||
@ -2560,9 +2576,17 @@ class calendar_uiforms extends calendar_ui
|
||||
}
|
||||
}
|
||||
|
||||
$ret = (bool)array_diff_assoc($event, $old);
|
||||
//error_log(__METHOD__."() returning ".array2string($ret)." diff=".array2string(array_udiff_assoc($event, $old, function($a, $b) { return (int)($a != $b); })));
|
||||
return $ret;
|
||||
$changes = array_diff_assoc($event, $old);
|
||||
|
||||
if (!($changes['recure'] = array_values(array_filter(array_keys($changes), static function($name)
|
||||
{
|
||||
return substr($name, 0, 6) === 'recur_';
|
||||
}))))
|
||||
{
|
||||
unset($changes['recure']);
|
||||
}
|
||||
|
||||
return $changes;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -373,6 +373,7 @@ max. number of entries to show (leave empty for no restriction) calendar de Max.
|
||||
maximum available quantity of %1 exceeded! calendar de Maximale Anzahl von %1 erreicht!
|
||||
meeting canceled calendar de Termin abgesagt
|
||||
meeting request calendar de Terminanfrage
|
||||
meeting request additional recurrence(s) calendar de Terminanfrage zusätzliche Wiederholung(en)
|
||||
meetingrequest to all participants calendar de Terminanforderung an alle Teilnehmer
|
||||
merge document... calendar de Dokument einfügen ...
|
||||
midnight calendar de Mitternacht
|
||||
@ -601,6 +602,7 @@ the document can contain placeholder like {{%3}}, to be replaced with the data (
|
||||
the exceptions are deleted together with the series. calendar de Die Ausnahmen werden mit der Terminserie gelöscht
|
||||
the extern organizer changed the event! calendar de Der externe Organisator hat den Termin geändert!
|
||||
the following document-types are supported: calendar de Im Moment werden die folgenden Dokumenttypen unterstützt:
|
||||
the organizer changed the event! calendar de Der Organisator hat den Termin geändert!
|
||||
the original series will be terminated today and a new series be created. calendar de Der bestehende Serientermin wird heute beendet und eine neue Terminserie erstellt.
|
||||
the resource you selected is already overbooked: calendar de Die Ressource, die Sie buchen möchten, ist bereits überbucht
|
||||
the sender "%1" is not the extern organizer "%2", proceed with caution! calendar de Der Absender "%1" ist nicht der externe Organisator "%2", seien Sie vorsichtig!
|
||||
@ -613,6 +615,7 @@ this entry is opened by user: calendar de Dieser Eintrag ist von einem anderen B
|
||||
this event is part of a series calendar de Dieser Termin ist Teil einer Serie
|
||||
this group that is preselected when you enter the planner. you can change it in the planner anytime you want. calendar de Diese Gruppe wird als Vorauswahl ausgewählt wenn Sie den Planer öffnen. Sie können die Gruppe jederzeit wechseln wenn Sie möchten.
|
||||
this is a recurring event. do you want to delete just this recurrence or the whole series? calendar de Das ist ein wiederholender Termin. Wollen Sie nur diese Wiederholung oder die ganze Serie löschen?
|
||||
this is a single recurrence of a recurring event. calendar de Das ist eine einzelne Wiederholung einer Terminserie.
|
||||
this mail cancels a meeting calendar de Diese Nachricht sagt einen Termin ab
|
||||
this mail contains a meeting request calendar de Diese Nachricht enthält eine Terminanfrage
|
||||
this mail contains a reply to a meeting request calendar de Diese Nachricht enthält eine Antwort auf eine Terminanfrage
|
||||
|
@ -373,6 +373,7 @@ max. number of entries to show (leave empty for no restriction) calendar en Max.
|
||||
maximum available quantity of %1 exceeded! calendar en Maximum available quantity of %1 exceeded!
|
||||
meeting canceled calendar en Meeting canceled
|
||||
meeting request calendar en Meeting request
|
||||
meeting request additional recurrence(s) calendar en Meeting request additional recurrence(s)
|
||||
meetingrequest to all participants calendar en Meetingrequest to all participants
|
||||
merge document... calendar en Merge document ...
|
||||
midnight calendar en Midnight
|
||||
@ -601,6 +602,7 @@ the document can contain placeholder like {{%3}}, to be replaced with the data (
|
||||
the exceptions are deleted together with the series. calendar en The exceptions are deleted together with the series.
|
||||
the extern organizer changed the event! calendar en The extern organizer changed the event!
|
||||
the following document-types are supported: calendar en The following document-types are supported:
|
||||
the organizer changed the event! calendar en The organizer changed the event!
|
||||
the original series will be terminated today and a new series be created. calendar en The original series will be terminated today and a new series be created.
|
||||
the resource you selected is already overbooked: calendar en The resource you selected is already over-booked:
|
||||
the sender "%1" is not the extern organizer "%2", proceed with caution! calendar en The sender "%1" is NOT the extern organizer "%2", proceed with caution!
|
||||
@ -613,6 +615,7 @@ this entry is opened by user: calendar en This entry was opened within the confi
|
||||
this event is part of a series calendar en This event is part of a series
|
||||
this group that is preselected when you enter the planner. you can change it in the planner anytime you want. calendar en This group that is preselected when you enter the planner. You can change it in the planner at any time.
|
||||
this is a recurring event. do you want to delete just this recurrence or the whole series? calendar en This is a recurring event. Do you want to delete just this recurrence or the whole series?
|
||||
this is a single recurrence of a recurring event. calendar en This is a single recurrence of a recurring event.
|
||||
this mail cancels a meeting calendar en This mail cancels a meeting
|
||||
this mail contains a meeting request calendar en This mail contains a meeting request
|
||||
this mail contains a reply to a meeting request calendar en This mail contains a reply to a meeting request
|
||||
|
@ -27,6 +27,7 @@
|
||||
<row disabled="!@ics_method=request">
|
||||
<et2-vbox>
|
||||
<et2-description value="This mail contains a meeting request" class="meetingRequestMessage"></et2-description>
|
||||
<et2-textbox id="comment" placeholder="Comment" statustext="Add a comment to send to organizer with your response"></et2-textbox>
|
||||
<et2-hbox class="buttonRow">
|
||||
<et2-button label="Apply" id="button[apply]" hideOnReadonly="true"></et2-button>
|
||||
<et2-button label="Accept" id="button[accept]" image="calendar/accepted"></et2-button>
|
||||
@ -35,6 +36,7 @@
|
||||
<et2-button statustext="Edit event in calendar" label="Edit" id="button[edit]" image="edit" hideOnReadonly="true"
|
||||
onclick="window.open(egw::link('/index.php','menuaction=calendar.calendar_uiforms.edit&cal_id=$cont[id]'),'_blank','dependent=yes,width=750,height=410,scrollbars=yes,status=yes'); return false;" noSubmit="true"></et2-button>
|
||||
</et2-hbox>
|
||||
<et2-description id="meeting-request-status" class="meetingRequestError" span="all"></et2-description>
|
||||
</et2-vbox>
|
||||
</row>
|
||||
<row disabled="!@ics_method=reply">
|
||||
@ -43,6 +45,7 @@
|
||||
<et2-hbox class="buttonRow">
|
||||
<et2-button label="Apply" id="button[apply]"></et2-button>
|
||||
</et2-hbox>
|
||||
<et2-description id="meeting-request-status" class="meetingRequestError" span="all"></et2-description>
|
||||
</et2-vbox>
|
||||
</row>
|
||||
<row disabled="!@ics_method=cancel">
|
||||
@ -73,12 +76,12 @@
|
||||
</row>
|
||||
<row class="row">
|
||||
<et2-description value="Title"></et2-description>
|
||||
<et2-description id="title" noLang="1"></et2-description>
|
||||
<et2-description id="title" noLang="1" class="@changed[title]"></et2-description>
|
||||
</row>
|
||||
<row class="row">
|
||||
<et2-description value="Location"></et2-description>
|
||||
<et2-hbox>
|
||||
<et2-description id="location" noLang="1"></et2-description>
|
||||
<et2-description id="location" noLang="1" class="@changed[location]"></et2-description>
|
||||
<et2-description class="et2_link" value="Videoconference" disabled="!@##videoconference" onclick="app.calendar.joinVideoConference(widget.getArrayMgr('content').getEntry('##videoconference'), widget.getArrayMgr('content').data);"></et2-description>
|
||||
<et2-image src="videoconference" disabled="!@##videoconference"></et2-image>
|
||||
</et2-hbox>
|
||||
@ -86,25 +89,33 @@
|
||||
<row class="row">
|
||||
<et2-description value="Date"></et2-description>
|
||||
<et2-hbox class="dates">
|
||||
<et2-date-time id="start" readonly="true"></et2-date-time>
|
||||
<et2-date-time label="-" id="end" readonly="true"></et2-date-time>
|
||||
<et2-date-time id="start" readonly="true" class="@changed[start]"></et2-date-time>
|
||||
<et2-date-time label="-" id="end" readonly="true" class="@changed[end]"></et2-date-time>
|
||||
</et2-hbox>
|
||||
</row>
|
||||
<row class="row" disabled="!@recure">
|
||||
<row class="row" disabled="!@recurrence">
|
||||
<et2-description></et2-description>
|
||||
<et2-description class="meetingRequestError" value="This is a single recurrence of a recurring event."></et2-description>
|
||||
</row>
|
||||
<row class="row" valign="top" disabled="!@recure">
|
||||
<et2-description value="Recurrence"></et2-description>
|
||||
<et2-description id="recure" noLang="1"></et2-description>
|
||||
<et2-description id="recure" noLang="1" class="@changed[recure]"></et2-description>
|
||||
</row>
|
||||
<row class="row">
|
||||
<et2-description value="Organizer"></et2-description>
|
||||
<et2-url-email id="organizer" readonly="true"></et2-url-email>
|
||||
</row>
|
||||
<row class="row" valign="top">
|
||||
<row class="row" valign="top" disabled="!@description">
|
||||
<et2-description value="Description"></et2-description>
|
||||
<et2-description id="description" noLang="ture" activateLinks="true"></et2-description>
|
||||
<et2-description id="description" noLang="true" activateLinks="true" class="@changed[description]"></et2-description>
|
||||
</row>
|
||||
<row class="row" valign="top">
|
||||
<et2-description value="Participants"></et2-description>
|
||||
<et2-description id="all_participants"></et2-description>
|
||||
<et2-description id="all_participants" class="@changed[participants]"></et2-description>
|
||||
</row>
|
||||
<row class="row" valign="top" disabled="!@comment">
|
||||
<et2-description value="Comment"></et2-description>
|
||||
<et2-description id="comment"></et2-description>
|
||||
</row>
|
||||
</rows>
|
||||
</grid>
|
||||
@ -118,9 +129,11 @@
|
||||
}
|
||||
.meetingRequestMessage {
|
||||
font-size: 150%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
table.meetingRequest {
|
||||
margin-top: 10px;
|
||||
border: 2px solid black;
|
||||
}
|
||||
.meetingRequest tr.th {
|
||||
@ -150,14 +163,10 @@
|
||||
.meetingWarning tr.row td {
|
||||
font-size: 120% !important;
|
||||
}
|
||||
.meetingRequestError {
|
||||
.meetingRequestError, .meetingRequestChanged {
|
||||
font-style: italic;
|
||||
color: red;
|
||||
}
|
||||
.buttonRow {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
</styles>
|
||||
</template>
|
||||
</overlay>
|
Loading…
Reference in New Issue
Block a user