diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php --- a/plugins/calendar/calendar.php +++ b/plugins/calendar/calendar.php @@ -3437,39 +3437,38 @@ // only update attendee status if ($event['_method'] == 'REPLY') { - // try to identify the attendee using the email sender address - $existing_attendee = -1; - $existing_attendee_emails = []; - - foreach ($existing['attendees'] as $i => $attendee) { - $existing_attendee_emails[] = $attendee['email']; - if ($this->itip->compare_email($attendee['email'], $event['_sender'], $event['_sender_utf'])) { - $existing_attendee = $i; - } - } + $existing_attendee_index = -1; $event_attendee = null; $update_attendees = []; - foreach ($event['attendees'] as $attendee) { - if ($this->itip->compare_email($attendee['email'], $event['_sender'], $event['_sender_utf'])) { - $event_attendee = $attendee; - $update_attendees[] = $attendee; - $metadata['fallback'] = $attendee['status']; - $metadata['attendee'] = $attendee['email']; - $metadata['rsvp'] = !empty($attendee['rsvp']) || $attendee['role'] != 'NON-PARTICIPANT'; + if ($attendee = $this->itip->find_reply_attendee($event)) { + $event_attendee = $attendee; + $update_attendees[] = $attendee; + $metadata['fallback'] = $attendee['status']; + $metadata['attendee'] = $attendee['email']; + $metadata['rsvp'] = !empty($attendee['rsvp']) || $attendee['role'] != 'NON-PARTICIPANT'; - if ($attendee['status'] != 'DELEGATED') { - break; + $existing_attendee_emails = []; + + // Find the attendee to update + foreach ($existing['attendees'] as $i => $existing_attendee) { + $existing_attendee_emails[] = $existing_attendee['email']; + if ($this->itip->compare_email($existing_attendee['email'], $attendee['email'])) { + $existing_attendee_index = $i; } } - // also copy delegate attendee - else if (!empty($attendee['delegated-from']) - && $this->itip->compare_email($attendee['delegated-from'], $event['_sender'], $event['_sender_utf']) - ) { - $update_attendees[] = $attendee; - if (!in_array_nocase($attendee['email'], $existing_attendee_emails)) { - $existing['attendees'][] = $attendee; + + if ($attendee['status'] == 'DELEGATED') { + //Also find and copy the delegatee + $delegatee_email = $attendee['email']; + $delegatees = array_filter($event['attendees'], function($attendee) use ($delegatee_email){ return $attendee['role'] != 'ORGANIZER' && $this->itip->compare_email($attendee['delegated-from'], $delegatee_email); }); + + if ($delegatee = $this->itip->find_attendee_by_email($event['attendees'], 'delegated-from', $attendee['email'])) { + $update_attendees[] = $delegatee; + if (!in_array_nocase($delegatee['email'], $existing_attendee_emails)) { + $existing['attendees'][] = $delegated_attendee; + } } } } @@ -3487,29 +3486,9 @@ } } - // Accept sender as a new participant (different email in From: and the iTip) - // Use ATTENDEE entry from the iTip with replaced email address - if (!$event_attendee) { - // remove the organizer - $itip_attendees = array_filter( - $event['attendees'], - function($item) { return $item['role'] != 'ORGANIZER'; } - ); - - // there must be only one attendee - if (is_array($itip_attendees) && count($itip_attendees) == 1) { - $event_attendee = $itip_attendees[key($itip_attendees)]; - $event_attendee['email'] = $event['_sender']; - $update_attendees[] = $event_attendee; - $metadata['fallback'] = $event_attendee['status']; - $metadata['attendee'] = $event_attendee['email']; - $metadata['rsvp'] = !empty($event_attendee['rsvp']) || $event_attendee['role'] != 'NON-PARTICIPANT'; - } - } - // found matching attendee entry in both existing and new events - if ($existing_attendee >= 0 && $event_attendee) { - $existing['attendees'][$existing_attendee] = $event_attendee; + if ($existing_attendee_index >= 0 && $event_attendee) { + $existing['attendees'][$existing_attendee_index] = $event_attendee; $success = $this->driver->update_attendees($existing, $update_attendees); } // update the entire attendees block diff --git a/plugins/libcalendaring/lib/libcalendaring_itip.php b/plugins/libcalendaring/lib/libcalendaring_itip.php --- a/plugins/libcalendaring/lib/libcalendaring_itip.php +++ b/plugins/libcalendaring/lib/libcalendaring_itip.php @@ -633,30 +633,13 @@ if ($method == 'REPLY') { $title = $this->gettext('itipreply'); - foreach ($event['attendees'] as $attendee) { - if (!empty($attendee['email']) && $attendee['role'] != 'ORGANIZER') { - if (empty($event['_sender']) || self::compare_email($attendee['email'], $event['_sender'], $event['_sender_utf'])) { - $metadata['attendee'] = $attendee['email']; - $rsvp_status = strtoupper($attendee['status']); - if ($attendee['delegated-to']) { - $metadata['delegated-to'] = $attendee['delegated-to']; - } - break; - } - } - } - - // It may happen that sender's address is different in From: and the attached iTip - // In such case use the ATTENDEE entry with the address from From: header - if (empty($metadata['attendee']) && !empty($event['_sender'])) { - // remove the organizer - $itip_attendees = array_filter($event['attendees'], function($item) { return $item['role'] != 'ORGANIZER'; }); + $attendee = self::find_reply_attendee($event); - // there must be only one attendee - if (is_array($itip_attendees) && count($itip_attendees) == 1) { - $event_attendee = $itip_attendees[key($itip_attendees)]; - $metadata['attendee'] = $event['_sender']; - $rsvp_status = strtoupper($event_attendee['status']); + if ($attendee) { + $metadata['attendee'] = $attendee['email']; + $rsvp_status = strtoupper($attendee['status']); + if ($attendee['delegated-to']) { + $metadata['delegated-to'] = $attendee['delegated-to']; } } @@ -1013,4 +996,50 @@ return $v1 || $v2; } + + /** + * Find an attendee that is not the organizer and has an email matching $email_field + */ + public function find_attendee_by_email($attendees, $email_field, $email, $email_utf = null) { + foreach ($attendees as $_attendee) { + if ($attendee['role'] == 'ORGANIZER') { + continue; + } + if (!empty($attendee[$email_field]) && self::compare_email($attendee[$email_field], $email, $email_utf)) { + return $attendee; + } + } + return null; + } + + /** + * Find the replying attendee in a REPLY + */ + public static function find_reply_attendee($event) { + // remove the organizer + $itip_attendees = array_filter($event['attendees'], function($item) { return $item['role'] != 'ORGANIZER' && !empty($item['email']); }); + $attendee = null; + + // According to rfc there should only be one attendee for a REPLY + if (count($itip_attendees) == 1) { + return array_pop($itip_attendees); + } + + // If we don't have anything to match by, pick the first and hope for the best. + if (empty($event['_sender'])) { + return array_shift($itip_attendees); + } + + // try to match by sent-by + if ($attendee = self::find_attendee_by_email($itip_attendees, 'sent-by', $event['_sender'], $event['_sender_utf'])) { + return $attendee; + } + + // try to match by email + if ($attendee = self::find_attendee_by_email($itip_attendees, 'email', $event['_sender'], $event['_sender_utf'])) { + return $attendee; + } + + return null; + } }