-
-
Notifications
You must be signed in to change notification settings - Fork 4.9k
caldav party crasher #59988
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
caldav party crasher #59988
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -11,10 +11,18 @@ | |||||||||||||||||||||
|
|
||||||||||||||||||||||
| use Sabre\VObject\Component; | ||||||||||||||||||||||
| use Sabre\VObject\Component\VCalendar; | ||||||||||||||||||||||
| use Sabre\VObject\Component\VEvent; | ||||||||||||||||||||||
| use Sabre\VObject\ITip\Broker; | ||||||||||||||||||||||
| use Sabre\VObject\ITip\Message; | ||||||||||||||||||||||
| use Sabre\VObject\Parameter; | ||||||||||||||||||||||
| use Sabre\VObject\Property; | ||||||||||||||||||||||
| use Sabre\VObject\Property\Boolean; | ||||||||||||||||||||||
| use Sabre\VObject\Property\ICalendar\CalAddress; | ||||||||||||||||||||||
| use Sabre\VObject\Property\ICalendar\DateTime; | ||||||||||||||||||||||
| use Sabre\VObject\Recur\EventIterator; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| class TipBroker extends Broker { | ||||||||||||||||||||||
| public const INVITATION_FORWARDING_PROPERTY = 'X-NC-INVITATION-FORWARDING'; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| public $significantChangeProperties = [ | ||||||||||||||||||||||
| 'DTSTART', | ||||||||||||||||||||||
|
|
@@ -79,6 +87,191 @@ protected function processMessageCancel(Message $itipMessage, ?VCalendar $existi | |||||||||||||||||||||
| return $existingObject; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| #[\Override] | ||||||||||||||||||||||
| protected function processMessageReply(Message $itipMessage, ?VCalendar $existingObject = null) { | ||||||||||||||||||||||
| // A reply can only be processed based on an existing object. | ||||||||||||||||||||||
| // If the object is not available, the reply is ignored. | ||||||||||||||||||||||
| if ($existingObject === null) { | ||||||||||||||||||||||
| return null; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| $instances = []; | ||||||||||||||||||||||
| $requestStatus = '2.0'; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /** @var list<VEvent> $vevents */ | ||||||||||||||||||||||
| $vevents = $itipMessage->message->select('VEVENT'); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Finding all the instances the attendee replied to. | ||||||||||||||||||||||
| foreach ($vevents as $vevent) { | ||||||||||||||||||||||
| // Use the Unix timestamp returned by getTimestamp as a unique identifier for the recurrence. | ||||||||||||||||||||||
| // The Unix timestamp will be the same for an event, even if the reply from the attendee | ||||||||||||||||||||||
| // used a different format/timezone to express the event date-time. | ||||||||||||||||||||||
| $recurId = $this->getRecurrenceKey($vevent); | ||||||||||||||||||||||
| $attendee = $this->getFirstAttendee($vevent); | ||||||||||||||||||||||
| if ($attendee === null) { | ||||||||||||||||||||||
| continue; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| $partstat = $attendee->offsetGet('PARTSTAT'); | ||||||||||||||||||||||
| if (!$partstat instanceof Parameter) { | ||||||||||||||||||||||
| continue; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| $instances[$recurId] = $partstat->getValue(); | ||||||||||||||||||||||
| if (isset($vevent->{'REQUEST-STATUS'})) { | ||||||||||||||||||||||
| $requestStatus = $vevent->{'REQUEST-STATUS'}->getValue(); | ||||||||||||||||||||||
| [$requestStatus] = explode(';', $requestStatus); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Now we need to loop through the original organizer event, to find | ||||||||||||||||||||||
| // all the instances where we have a reply for. | ||||||||||||||||||||||
| $masterObject = $this->getMasterEvent($existingObject); | ||||||||||||||||||||||
| $masterAllowInvitationForwarding = $masterObject === null || $this->allowInvitationForwarding($masterObject); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /** @var list<VEvent> $vevents */ | ||||||||||||||||||||||
| $vevents = $existingObject->select('VEVENT'); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| foreach ($vevents as $vevent) { | ||||||||||||||||||||||
| // Use the Unix timestamp returned by getTimestamp as a unique identifier for the recurrence. | ||||||||||||||||||||||
| $recurId = $this->getRecurrenceKey($vevent); | ||||||||||||||||||||||
| if (isset($instances[$recurId])) { | ||||||||||||||||||||||
| $allowInvitationForwarding = $this->allowInvitationForwarding($vevent); | ||||||||||||||||||||||
| $attendeeFound = false; | ||||||||||||||||||||||
| if (isset($vevent->ATTENDEE)) { | ||||||||||||||||||||||
| foreach ($vevent->ATTENDEE as $attendee) { | ||||||||||||||||||||||
| if ($attendee->getValue() === $itipMessage->sender) { | ||||||||||||||||||||||
| $attendeeFound = true; | ||||||||||||||||||||||
| $attendee['PARTSTAT'] = $instances[$recurId]; | ||||||||||||||||||||||
| $attendee['SCHEDULE-STATUS'] = $requestStatus; | ||||||||||||||||||||||
| // Un-setting the RSVP status, because we now know | ||||||||||||||||||||||
| // that the attendee already replied. | ||||||||||||||||||||||
| unset($attendee['RSVP']); | ||||||||||||||||||||||
| break; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| if (!$attendeeFound && $allowInvitationForwarding) { | ||||||||||||||||||||||
| // Adding a new attendee. The iTip documentation calls this | ||||||||||||||||||||||
| // a party crasher. | ||||||||||||||||||||||
| $parameters = [ | ||||||||||||||||||||||
| 'PARTSTAT' => $instances[$recurId], | ||||||||||||||||||||||
| ]; | ||||||||||||||||||||||
| if ($itipMessage->senderName) { | ||||||||||||||||||||||
| $parameters['CN'] = $itipMessage->senderName; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| $vevent->add('ATTENDEE', $itipMessage->sender, $parameters); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| unset($instances[$recurId]); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if ($masterObject === null) { | ||||||||||||||||||||||
| // No master object, we can't add new instances. | ||||||||||||||||||||||
| return null; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| // If we got replies to instances that did not exist in the | ||||||||||||||||||||||
| // original list, it means that new exceptions must be created. | ||||||||||||||||||||||
| foreach ($instances as $recurId => $partstat) { | ||||||||||||||||||||||
| $recurrenceIterator = new EventIterator($existingObject, $itipMessage->uid); | ||||||||||||||||||||||
| $found = false; | ||||||||||||||||||||||
| $iterations = 1000; | ||||||||||||||||||||||
| do { | ||||||||||||||||||||||
| $newObject = $recurrenceIterator->getEventObject(); | ||||||||||||||||||||||
| $recurrenceIterator->next(); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Compare the Unix timestamp returned by getTimestamp with the previously calculated timestamp. | ||||||||||||||||||||||
| // If they are the same, then this is a matching recurrence, even though its date-time may have | ||||||||||||||||||||||
| // been expressed in a different format/timezone. | ||||||||||||||||||||||
| if (isset($newObject->{'RECURRENCE-ID'}) && $newObject->{'RECURRENCE-ID'}->getDateTime()->getTimestamp() === $recurId) { | ||||||||||||||||||||||
| $found = true; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| --$iterations; | ||||||||||||||||||||||
| } while ($recurrenceIterator->valid() && !$found && $iterations); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
kesselb marked this conversation as resolved.
|
||||||||||||||||||||||
| // Invalid recurrence id. Skipping this object. | ||||||||||||||||||||||
| if (!$found) { | ||||||||||||||||||||||
| continue; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| $newObject->remove('RRULE'); | ||||||||||||||||||||||
| $newObject->remove('EXDATE'); | ||||||||||||||||||||||
| $newObject->remove('RDATE'); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| $attendeeFound = false; | ||||||||||||||||||||||
| if (isset($newObject->ATTENDEE)) { | ||||||||||||||||||||||
| foreach ($newObject->ATTENDEE as $attendee) { | ||||||||||||||||||||||
| if ($attendee->getValue() === $itipMessage->sender) { | ||||||||||||||||||||||
| $attendeeFound = true; | ||||||||||||||||||||||
| $attendee['PARTSTAT'] = $partstat; | ||||||||||||||||||||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is matching the upstream: https://github.com/sabre-io/vobject/blob/2104a3ea37e248262617a8acbfe7648d8e2fd8bd/lib/ITip/Broker.php#L437 @SebastianKrupinski do you know why we don't unset RSVP here like for the existing events in the loop before?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hey, Just saw this comment. According to the RFC Parameter Name So I would say once we have a reply there is no need for the RSVP |
||||||||||||||||||||||
| $attendee['SCHEDULE-STATUS'] = $requestStatus; | ||||||||||||||||||||||
| unset($attendee['RSVP']); | ||||||||||||||||||||||
| break; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
kesselb marked this conversation as resolved.
|
||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| if (!$attendeeFound && !$masterAllowInvitationForwarding) { | ||||||||||||||||||||||
| continue; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| if (!$attendeeFound) { | ||||||||||||||||||||||
| // Adding a new attendee | ||||||||||||||||||||||
| $parameters = [ | ||||||||||||||||||||||
| 'PARTSTAT' => $partstat, | ||||||||||||||||||||||
| ]; | ||||||||||||||||||||||
| if ($itipMessage->senderName) { | ||||||||||||||||||||||
| $parameters['CN'] = $itipMessage->senderName; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| $newObject->add('ATTENDEE', $itipMessage->sender, $parameters); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| $existingObject->add($newObject); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return $existingObject; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| protected function getMasterEvent(VCalendar $calendar): ?VEvent { | ||||||||||||||||||||||
| /** @var list<VEvent> $vevents */ | ||||||||||||||||||||||
| $vevents = $calendar->select('VEVENT'); | ||||||||||||||||||||||
| foreach ($vevents as $vevent) { | ||||||||||||||||||||||
| if (!isset($vevent->{'RECURRENCE-ID'})) { | ||||||||||||||||||||||
| return $vevent; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| return null; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /** | ||||||||||||||||||||||
| * @return int|'master' | ||||||||||||||||||||||
| */ | ||||||||||||||||||||||
| protected function getRecurrenceKey(VEvent $vevent): int|string { | ||||||||||||||||||||||
| /** @var list<Property> $recurrenceIds */ | ||||||||||||||||||||||
| $recurrenceIds = $vevent->select('RECURRENCE-ID'); | ||||||||||||||||||||||
| foreach ($recurrenceIds as $recurrenceId) { | ||||||||||||||||||||||
| if ($recurrenceId instanceof DateTime) { | ||||||||||||||||||||||
| return $recurrenceId->getDateTime()->getTimestamp(); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| return 'master'; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| protected function getFirstAttendee(VEvent $vevent): ?CalAddress { | ||||||||||||||||||||||
| /** @var list<Property> $attendees */ | ||||||||||||||||||||||
| $attendees = $vevent->select('ATTENDEE'); | ||||||||||||||||||||||
| foreach ($attendees as $attendee) { | ||||||||||||||||||||||
| if ($attendee instanceof CalAddress) { | ||||||||||||||||||||||
| return $attendee; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| return null; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| protected function allowInvitationForwarding(VEvent $vevent): bool { | ||||||||||||||||||||||
| $properties = $vevent->select(self::INVITATION_FORWARDING_PROPERTY); | ||||||||||||||||||||||
| foreach ($properties as $property) { | ||||||||||||||||||||||
| if ($property instanceof Boolean) { | ||||||||||||||||||||||
| return $property->getValue() === 'TRUE'; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
||||||||||||||||||||||
| } | |
| } | |
| $value = strtoupper(trim((string)$property->getValue())); | |
| if ($value === 'TRUE' || $value === '1' || $value === 'YES') { | |
| return true; | |
| } | |
| if ($value === 'FALSE' || $value === '0' || $value === 'NO') { | |
| return false; | |
| } |
Uh oh!
There was an error while loading. Please reload this page.