diff --git a/calendar/inc/class.calendar_boupdate.inc.php b/calendar/inc/class.calendar_boupdate.inc.php index 6ab684ec09..5443416ee8 100644 --- a/calendar/inc/class.calendar_boupdate.inc.php +++ b/calendar/inc/class.calendar_boupdate.inc.php @@ -2223,10 +2223,10 @@ class calendar_boupdate extends calendar_bo * * @param array $event * @param array $old_event - * @param Api\DateTime|int|null $instance_date For recurring events, this is the date we - * are dealing with + * @param Api\DateTime|int|null $instance_date For recurring events, this is the date we are dealing with + * @param boolean $ignore_acl=false true: no acl check */ - function check_move_alarms(Array &$event, Array $old_event = null, $instance_date = null) + function check_move_alarms(Array &$event, Array $old_event = null, $instance_date = null, $ignore_acl=false) { if ($old_event !== null && $event['start'] == $old_event['start']) return; @@ -2254,7 +2254,7 @@ class calendar_boupdate extends calendar_bo else if ($alarm['time'] !== $time->format('ts') - $alarm['offset']) { $alarm['time'] = $time->format('ts') - $alarm['offset']; - $this->save_alarm($event['id'], $alarm); + $this->save_alarm($event['id'], $alarm, true, $ignore_acl); } } } @@ -2265,11 +2265,12 @@ class calendar_boupdate extends calendar_bo * @param int $cal_id Id of the calendar-entry * @param array $alarm array with fields: text, owner, enabled, .. * @param boolean $update_modified =true call update modified, default true + * @param boolean $ignore_acl=false true: no acl check * @return string id of the alarm, or false on error (eg. no perms) */ - function save_alarm($cal_id, $alarm, $update_modified=true) + function save_alarm($cal_id, $alarm, $update_modified=true, $ignore_acl=false) { - if (!$cal_id || !$this->check_perms(Acl::EDIT,$alarm['all'] ? $cal_id : 0,!$alarm['all'] ? $alarm['owner'] : 0)) + if (!$cal_id || !$ignore_acl && !$this->check_perms(Acl::EDIT,$alarm['all'] ? $cal_id : 0,!$alarm['all'] ? $alarm['owner'] : 0)) { //echo "

no rights to save the alarm=".print_r($alarm,true)." to event($cal_id)

"; return false; // no rights to add the alarm @@ -2283,13 +2284,14 @@ class calendar_boupdate extends calendar_bo * delete one alarms identified by its id * * @param string $id alarm-id is a string of 'cal:'.$cal_id.':'.$alarm_nr, it is used as the job-id too + * @param boolean $ignore_acl=false true: no acl check * @return int number of alarms deleted, false on error (eg. no perms) */ - function delete_alarm($id) + function delete_alarm($id, $ignore_acl=false) { list(,$cal_id) = explode(':',$id); - if (!($alarm = $this->so->read_alarm($id)) || !$cal_id || !$this->check_perms(Acl::EDIT,$alarm['all'] ? $cal_id : 0,!$alarm['all'] ? $alarm['owner'] : 0)) + if (!($alarm = $this->so->read_alarm($id)) || !$cal_id || !$ignore_acl && !$this->check_perms(Acl::EDIT,$alarm['all'] ? $cal_id : 0,!$alarm['all'] ? $alarm['owner'] : 0)) { return false; // no rights to delete the alarm } diff --git a/calendar/inc/class.calendar_uiforms.inc.php b/calendar/inc/class.calendar_uiforms.inc.php index 1665cebf79..bda9655b64 100644 --- a/calendar/inc/class.calendar_uiforms.inc.php +++ b/calendar/inc/class.calendar_uiforms.inc.php @@ -2066,7 +2066,6 @@ class calendar_uiforms extends calendar_ui } } - /** * Remove (shared) lock via ajax, when edit popup get's closed * @@ -2084,6 +2083,34 @@ class calendar_uiforms extends calendar_ui } } + /** + * Get email of participant + * + * @param string $uid + * @return string|null + * @throws Exception + */ + public static function participantEmail($uid) + { + if (is_numeric($uid)) + { + $email = Api\Accounts::id2name($uid, 'account_email') ?: null; + } + elseif ($uid[0] === 'e') + { + $email = substr($uid, 1); + } + elseif ($uid[0] === 'c' && ($contact = (new Api\Contacts)->read(substr($uid, 1)))) + { + $email = $contact['email'] ?? $contact['email_home']; + } + if (!empty($email) && preg_match('/<([^>]+?)>$/', $email, $matches)) + { + $email = $matches[1]; + } + return $email; + } + /** * Display iCal meeting request for EMail app and allow to accept, tentative or reject it or a reply and allow to apply it * @@ -2114,6 +2141,7 @@ class calendar_uiforms extends calendar_ui $ical_string = $session_data['attachment']; $ical_charset = $session_data['charset']; $ical_method = $session_data['method']; + $ical_sender = $session_data['sender']; unset($session_data); } $ical = new calendar_ical(); @@ -2140,6 +2168,18 @@ class calendar_uiforms extends calendar_ui if (($existing_event = $this->bo->read($event['uid'], $event['recurrence'], false, 'ts', null, true)) && // true = read the exception !$existing_event['deleted']) { + // check if mail is from extern organizer + $from_extern_organizer = false; + if (strtolower($ical_method) !== 'reply' && + ($extern_organizer = !empty($ical_sender) ? array_filter($existing_event['participants'], static function($status, $user) + { + calendar_so::split_status($status, $quantity, $role); + return $role === 'CHAIR' && is_string($user) && in_array($user[0], ['e', 'c']); + }, ARRAY_FILTER_USE_BOTH) : []) && + !($from_extern_organizer = $ical_sender === strtolower($organizer=self::participantEmail(key($extern_organizer))))) + { + $event['sender_warning'] = lang('The sender "%1" is NOT the extern organizer "%2", proceed with caution!', $ical_sender, $organizer); + } switch(strtolower($ical_method)) { case 'reply': @@ -2150,7 +2190,11 @@ class calendar_uiforms extends calendar_ui $event['ical_sender_status'] = current($parts); $quantity = $role = null; calendar_so::split_status($event['ical_sender_status'], $quantity, $role); - + // let user know, that sender is not the participant + if ($ical_sender !== strtolower($participant=self::participantEmail($event['ical_sender_uid']))) + { + $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)) { $existing_status = $existing_event['participants'][$event['ical_sender_uid']]; @@ -2169,7 +2213,12 @@ class calendar_uiforms extends calendar_ui case 'request': $status = $existing_event['participants'][$user]; calendar_so::split_status($status, $quantity, $role); - if (strtolower($ical_method) == 'response' && isset($existing_event['participants'][$user]) && + if (!empty($extern_organizer) && self::event_changed($event, $existing_event)) + { + $event['error'] = lang('The extern organizer changed the event!',); + $readonlys['button[apply]'] = false; + } + elseif (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]); @@ -2185,7 +2234,7 @@ class calendar_uiforms extends calendar_ui $event['error'] .= ($event['error'] ? "\n" : '').lang('You are not invited to that event!'); if ($event['id']) { - $readonlys['button[accept]'] = $readonlys['button[tentativ]'] = + $readonlys['button[accept]'] = $readonlys['button[tentativ]'] = $readonlys['button[apply]'] = $readonlys['button[reject]'] = $readonlys['button[cancel]'] = true; } } @@ -2257,6 +2306,54 @@ class calendar_uiforms extends calendar_ui // clear notification errors notifications::errors(true); + $msg = []; + // do we need to update the event itself (user-status is reset to old in event_changed!) + if ($button !== 'delete' && !empty($event['old']) && self::event_changed($event, $event['old'])) + { + // check if we are allowed to update the event + if($this->bo->check_perms(Acl::EDIT, $event['old']) || $event['extern_organizer']) + { + 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'], !empty($event['extern_organizer'])); + 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, !empty($event['extern_organizer']), 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['organizer']); + $event = array_merge($event['old'], $event); + } + unset($event['old']); + + if (($event['id'] = $this->bo->update($event, $ignore_conflicts=true, true, !empty($event['extern_organizer']), true, $msg, true))) + { + $msg[] = lang('Changed event-data applied'); + } + else + { + $msg[] = lang('Error saving the event!'); + $button = false; + } + } + else + { + $event['id'] = $event['old']['id']; + // disable "warning" that we have no rights to store any modifications + // as that confuses our users, who only want to accept or reject + //$msg[] = lang('Not enough rights to update the event!'); + } + } switch($button) { case 'reject': @@ -2291,53 +2388,6 @@ class calendar_uiforms extends calendar_ui break; } } - // do we need to update the event itself (user-status is reset to old in event_changed!) - elseif (self::event_changed($event, $event['old'])) - { - // 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']; - // disable "warning" that we have no rights to store any modifications - // as that confuses our users, who only want to accept or reject - //$msg[] = lang('Not enough rights to update the event!'); - } - } else { $event['id'] = $event['old']['id']; @@ -2351,9 +2401,9 @@ class calendar_uiforms extends calendar_ui case 'apply': // set status and send notification / meeting response - if ($this->bo->set_status($event['id'], $event['ical_sender_uid'], $event['ical_sender_status'], $event['recurrence'])) + if (strtolower($event['ics_method']) === 'reply' && $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; @@ -2361,7 +2411,7 @@ class calendar_uiforms extends calendar_ui if ($event['id'] && $this->bo->set_status($event['id'], $user, 'R', $event['recurrence'], false, true, true)) // no reply to organizer { - $msg = lang('Status changed'); + $msg[] = lang('Status changed'); } break; @@ -2369,7 +2419,7 @@ class calendar_uiforms extends calendar_ui if ($event['id'] && $this->bo->delete($event['id'], $event['recurrence'], false, [$event['ical_sender_uid']])) // no reply to organizer { - $msg = lang('Event deleted.'); + $msg[] = lang('Event deleted.'); } break; } @@ -2378,7 +2428,7 @@ class calendar_uiforms extends calendar_ui } Framework::message(implode("\n", (array)$msg)); $readonlys['button[edit]'] = !$event['id']; - $event['ics_method'] = $readonlys['ics_method'] = strtolower($ical_method); + $event['ics_method'] = strtolower($ical_method); switch(strtolower($ical_method)) { case 'reply': @@ -2395,6 +2445,8 @@ class calendar_uiforms extends calendar_ui $tpl = new Etemplate('calendar.meeting'); $tpl->exec('calendar.calendar_uiforms.meeting', $event, array(), $readonlys, $event+array( 'old' => $existing_event, + 'extern_organizer' => $extern_organizer ?? [], + 'from_extern_organizer' => $from_extern_organizer ?? false, ), 2); } @@ -2413,8 +2465,8 @@ class calendar_uiforms extends calendar_ui 'recur_type', 'recur_data', 'recur_interval', 'recur_exception'); // only compare certain fields, taking account unset, null or '' values - $event = array_intersect_key($_event+array('recur_exception'=>array()), array_flip($keys_to_check)); - $old = array_intersect_key(array_diff($_old, array(null, '')), array_flip($keys_to_check)); + $event = array_intersect_key(array_diff($_event, [null, ''])+array('recur_exception'=>array()), array_flip($keys_to_check)); + $old = array_intersect_key(array_diff($_old, [null, '']), array_flip($keys_to_check)); // keep the status of existing participants (users) foreach($old['participants'] as $uid => $status) @@ -2425,7 +2477,7 @@ class calendar_uiforms extends calendar_ui } } - $ret = $event != $old; + $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; } @@ -3539,4 +3591,4 @@ class calendar_uiforms extends calendar_ui $etpl->exec('calendar.calendar_uiforms.notify', $content, $sel_options, $readonlys, $preserve,2); } -} +} \ No newline at end of file diff --git a/calendar/lang/egw_de.lang b/calendar/lang/egw_de.lang index b600811c7b..e2b90d143d 100644 --- a/calendar/lang/egw_de.lang +++ b/calendar/lang/egw_de.lang @@ -29,6 +29,7 @@ add alarm calendar de Alarm zufügen add appointments via shortened dialog or complete edit window calendar de Termine hinzufügen über verkürzten Dialog oder komplettes Bearbeiten-Fenster add current view as favorite calendar de Ansicht als Favorit zufügen add new alarm calendar de Neuen Alarm erstellen +add new event calendar de Einen neuen Termin hinzufügen add new participants or resource calendar de Neue(n) Teilnehmer oder Ressource auswählen add timesheet entry calendar de Stundenzettel hinzufügen added calendar de Neuer Termin @@ -62,6 +63,7 @@ apply the changes calendar de Übernimmt die Änderungen appointment settings calendar de Einstellungen der Terminverwaltung as an alternative you can %1download a mysql dump%2 and import it manually into egw_cal_timezones table. calendar de Als Alternative können Sie auch einen %1MySQL Dump herunterladen%2 und diesen von Hand in die Datenbank Tabelle egw_cal_timezones importieren. at start of the event calendar de am Beginn des Termins +attention calendar de Achtung automatically purge old events after admin de Bereinigt bzw. löscht alte Termine automatisch nach available for the first entry inside each day of week or daily table inside the selected range: calendar de Verfügbar für den ersten Eintrag innerhalb eines jeden Tages oder für die Liste innerhalb des ausgewählten Bereichs: back half a month calendar de einen halben Monat zurück @@ -578,9 +580,12 @@ the apple ical apps use this color to display events from this calendar. calenda the document can contain placeholder like {{%1}}, to be replaced with the data. calendar de Das Dokument kann Platzhalter wie {{%1}} enthalten, die durch die Daten ersetzt werden sollen. the document can contain placeholder like {{%3}}, to be replaced with the data (%1full list of placeholder names%2). calendar de Das Dokument kann Platzhalter wie {{%3}} enthalten, die mit den Daten ersetzt werden (%1komplette Liste der Platzhalter%2) 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 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! +the sender "%1" is not the participant replying "%2", proceed with caution! calendar de Der Absender "%1" ist nicht der antwortende Teilnehmer "%2", seien Sie vorsichtig! this day is shown as first day in the week or month view. calendar de Dieser Tag wird als erster in der Wochen- oder Monatsansicht angezeigt this defines the end of your dayview. events after this time, are shown below the dayview. calendar de Diese Zeit definiert das Ende des Arbeitstags in der Tagesansicht. Alle späteren Einträge werden darunter dargestellt this defines the start of your dayview. events before this time, are shown above the dayview.
this time is also used as a default starttime for new events. calendar de Diese Zeit definiert den Anfang des Arbeitstags in der Tagesansicht. Alle früheren Einträge werden darüber dargestellt diff --git a/calendar/lang/egw_en.lang b/calendar/lang/egw_en.lang index d83dfd4d95..841d08293d 100644 --- a/calendar/lang/egw_en.lang +++ b/calendar/lang/egw_en.lang @@ -63,6 +63,7 @@ apply the changes calendar en Apply the changes appointment settings calendar en Appointment settings as an alternative you can %1download a mysql dump%2 and import it manually into egw_cal_timezones table. calendar en As an alternative you can %1download a MySQL dump%2 and import it manually into egw_cal_timezones table. at start of the event calendar en at start of the event +attention calendar en Attention automatically purge old events after admin en Automatically purge old events after available for the first entry inside each day of week or daily table inside the selected range: calendar en Available for the first entry inside each day of week or daily table inside the selected range: back half a month calendar en Back half a month @@ -579,9 +580,12 @@ the apple ical apps use this color to display events from this calendar. calenda the document can contain placeholder like {{%1}}, to be replaced with the data. calendar en The document can contain placeholder like {{%1}}, to be replaced with the data. the document can contain placeholder like {{%3}}, to be replaced with the data (%1full list of placeholder names%2). calendar en The document can contain placeholder like {{%3}}, to be replaced with the data (%1full list of placeholder names%2). 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 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! +the sender "%1" is not the participant replying "%2", proceed with caution! calendar en The sender "%1" is NOT the participant replying "%2", proceed with caution! this day is shown as first day in the week or month view. calendar en This day is shown as first day in the week or month view. this defines the end of your dayview. events after this time, are shown below the dayview. calendar en This defines the end of your day view. Events after this time, are shown below the day view. this defines the start of your dayview. events before this time, are shown above the dayview.
this time is also used as a default starttime for new events. calendar en This defines the start of your day view. Events before this time, are shown above the day view.
This time is also used as a default start time for new events. diff --git a/calendar/templates/default/meeting.xet b/calendar/templates/default/meeting.xet index 8d3bacb01b..104bf15dc9 100644 --- a/calendar/templates/default/meeting.xet +++ b/calendar/templates/default/meeting.xet @@ -7,36 +7,49 @@ - - - + + + + + + + + + + + + + + + -