-
-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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,8 +11,11 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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\Property\Boolean; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| use Sabre\VObject\Recur\EventIterator; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| class TipBroker extends Broker { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -78,6 +81,154 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return $existingObject; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Processes incoming REPLY messages. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * The message is a reply. This is for example an attendee telling | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * an organizer he accepted the invite, or declined it. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param VCalendar $existingObject | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @return VCalendar|null | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $instances = []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $requestStatus = '2.0'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Finding all the instances the attendee replied to. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| foreach ($itipMessage->message->VEVENT 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 = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getDateTime()->getTimestamp() : 'master'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $attendee = $vevent->ATTENDEE; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $instances[$recurId] = $attendee['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 = null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $allowPartyCrasher = true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| foreach ($existingObject->VEVENT as $vevent) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!isset($vevent->{'RECURRENCE-ID'})) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $masterObject = $vevent; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $allowPartyCrasher = $this->partyCrasher($vevent); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+102
to
+124
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Finding all the instances the attendee replied to. | |
| foreach ($itipMessage->message->VEVENT 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 = isset($vevent->{'RECURRENCE-ID'}) ? $vevent->{'RECURRENCE-ID'}->getDateTime()->getTimestamp() : 'master'; | |
| $attendee = $vevent->ATTENDEE; | |
| $instances[$recurId] = $attendee['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 = null; | |
| $allowPartyCrasher = true; | |
| foreach ($existingObject->VEVENT as $vevent) { | |
| if (!isset($vevent->{'RECURRENCE-ID'})) { | |
| $masterObject = $vevent; | |
| $allowPartyCrasher = $this->partyCrasher($vevent); | |
| $componentType = $itipMessage->component; | |
| // Finding all the instances the attendee replied to. | |
| foreach (isset($itipMessage->message->{$componentType}) ? $itipMessage->message->{$componentType} : [] as $component) { | |
| // 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 = isset($component->{'RECURRENCE-ID'}) ? $component->{'RECURRENCE-ID'}->getDateTime()->getTimestamp() : 'master'; | |
| $attendee = $component->ATTENDEE; | |
| $instances[$recurId] = $attendee['PARTSTAT']->getValue(); | |
| if (isset($component->{'REQUEST-STATUS'})) { | |
| $requestStatus = $component->{'REQUEST-STATUS'}->getValue(); | |
| [$requestStatus] = explode(';', $requestStatus); | |
| } | |
| } | |
| // Now we need to loop through the original organizer component, to find | |
| // all the instances where we have a reply for. | |
| $masterObject = null; | |
| $allowPartyCrasher = true; | |
| foreach (isset($existingObject->{$componentType}) ? $existingObject->{$componentType} : [] as $component) { | |
| if (!isset($component->{'RECURRENCE-ID'})) { | |
| $masterObject = $component; | |
| if ($component instanceof VEvent) { | |
| $allowPartyCrasher = $this->partyCrasher($component); | |
| } |
Copilot
AI
Apr 28, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
processMessageReply() constructs new EventIterator($existingObject, $itipMessage->uid) without handling exceptions. EventIterator can throw (e.g. NoInstancesException is handled elsewhere in the codebase), so a malformed or unexpected recurring definition could bubble up as an unhandled exception and break scheduling processing. Wrap the iterator creation/iteration in a try/catch and treat failures as an invalid recurrence (ignore the reply / skip instance) instead of hard-failing.
| $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); | |
| $found = false; | |
| $iterations = 1000; | |
| $newObject = null; | |
| try { | |
| $recurrenceIterator = new EventIterator($existingObject, $itipMessage->uid); | |
| 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); | |
| } catch (\Throwable $e) { | |
| // Invalid or non-expandable recurrence. Skip this reply instance. | |
| continue; | |
| } |
Copilot
AI
Apr 28, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The loop that tries to match missing recurrences re-creates an EventIterator for each $instances entry and then scans up to 1000 iterations from the start. If a reply contains many RECURRENCE-ID values, this becomes expensive (O(n * 1000)) and can be abused to cause high CPU usage. Consider enforcing a reasonable cap on the number of instances processed from a single reply and/or reusing a single iterator (or fast-forwarding to the target) to avoid repeated full scans.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The 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?
Copilot
AI
Apr 28, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For generated exceptions ($newObject), when the attendee already exists you only update PARTSTAT, but for existing components earlier in the method you also set SCHEDULE-STATUS and unset RSVP. This inconsistency means generated instances may keep stale RSVP flags / miss schedule status tracking. Consider applying the same updates (SCHEDULE-STATUS, unset(RSVP), etc.) in the generated-instance path as well.
Check failure on line 213 in apps/dav/lib/CalDAV/TipBroker.php
GitHub Actions / static-code-analysis
InvalidArgument
apps/dav/lib/CalDAV/TipBroker.php:213:16: InvalidArgument: Argument 1 of Sabre\VObject\Node::offsetGet expects int, but 'CN' provided (see https://psalm.dev/004)
Check failure on line 213 in apps/dav/lib/CalDAV/TipBroker.php
GitHub Actions / static-code-analysis
InvalidArgument
apps/dav/lib/CalDAV/TipBroker.php:213:16: InvalidArgument: Argument 1 of Sabre\VObject\Node::offsetSet expects int, but 'CN' provided (see https://psalm.dev/004)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: naming. Could this be allowPartyCrashers?
Copilot
AI
Apr 28, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
partyCrasher() only checks properties that are instances of Sabre\VObject\Property\Boolean. If the event contains X-NC-PARTY-CRASHER:FALSE but it gets parsed as a generic/unknown property (e.g. if the property map wasn’t initialized before parsing), this method will fall back to true and still allow party crashers. To make this robust, consider also checking the property's string value (case-insensitive) whenever the property exists, not only when it’s a Boolean instance.
| } | |
| } | |
| $value = strtoupper(trim((string)$property->getValue())); | |
| if ($value === 'TRUE' || $value === '1' || $value === 'YES') { | |
| return true; | |
| } | |
| if ($value === 'FALSE' || $value === '0' || $value === 'NO') { | |
| return false; | |
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,7 +8,9 @@ | |
| namespace OCA\DAV\Tests\unit\CalDAV; | ||
|
|
||
| use OCA\DAV\CalDAV\TipBroker; | ||
| use Sabre\VObject; | ||
| use Sabre\VObject\Component\VCalendar; | ||
| use Sabre\VObject\ITip\Message; | ||
| use Test\TestCase; | ||
|
|
||
| class TipBrokerTest extends TestCase { | ||
|
|
@@ -21,6 +23,8 @@ class TipBrokerTest extends TestCase { | |
| protected function setUp(): void { | ||
| parent::setUp(); | ||
|
|
||
| VCalendar::$propertyMap['X-NC-PARTY-CRASHER'] = VObject\Property\Boolean::class; | ||
|
|
||
|
Comment on lines
+26
to
+27
|
||
| $this->broker = new TipBroker(); | ||
|
|
||
| $this->templateEventInfo = [ | ||
|
|
@@ -579,4 +583,164 @@ public function testParseEventForOrganizerScheduleForceSend(): void { | |
| $this->assertFalse(isset($messages[0]->message->VEVENT->ATTENDEE['SCHEDULE-FORCE-SEND'])); | ||
| } | ||
|
|
||
| public function testProcessMessageReplyDisallowsPartyCrasher(): void { | ||
| $existingCalendar = clone $this->vCalendar1a; | ||
| $existingCalendar->VEVENT->add('X-NC-PARTY-CRASHER', 'FALSE'); | ||
| $reply = new Message(); | ||
| $reply->uid = $existingCalendar->VEVENT->UID->getValue(); | ||
| $reply->component = 'VEVENT'; | ||
| $reply->sender = 'mailto:attendee2@testing.com'; | ||
| $reply->senderName = 'Attendee Two'; | ||
| $reply->sequence = 1; | ||
| $reply->message = new VCalendar(); | ||
| /** @var \Sabre\VObject\Component\VEvent $replyEvent */ | ||
| $replyEvent = $reply->message->add('VEVENT', []); | ||
| $replyEvent->add('UID', $reply->uid); | ||
| $replyEvent->add('ATTENDEE', $reply->sender, [ | ||
| 'PARTSTAT' => 'ACCEPTED', | ||
| ]); | ||
|
|
||
| $result = $this->invokePrivate($this->broker, 'processMessageReply', [$reply, $existingCalendar]); | ||
|
|
||
| $this->assertSame($existingCalendar, $result); | ||
| $this->assertCount(1, $result->VEVENT->ATTENDEE); | ||
| $this->assertEquals('mailto:attendee1@testing.com', $result->VEVENT->ATTENDEE[0]->getValue()); | ||
| } | ||
|
|
||
| public function testProcessMessageReplyAllowsPartyCrasher(): void { | ||
| $existingCalendar = clone $this->vCalendar1a; | ||
| $existingCalendar->VEVENT->add('X-NC-PARTY-CRASHER', 'TRUE'); | ||
| $reply = new Message(); | ||
| $reply->uid = $existingCalendar->VEVENT->UID->getValue(); | ||
| $reply->component = 'VEVENT'; | ||
| $reply->sender = 'mailto:attendee2@testing.com'; | ||
| $reply->senderName = 'Attendee Two'; | ||
| $reply->sequence = 1; | ||
| $reply->message = new VCalendar(); | ||
| /** @var \Sabre\VObject\Component\VEvent $replyEvent */ | ||
| $replyEvent = $reply->message->add('VEVENT', []); | ||
| $replyEvent->add('UID', $reply->uid); | ||
| $replyEvent->add('ATTENDEE', $reply->sender, [ | ||
| 'PARTSTAT' => 'ACCEPTED', | ||
| ]); | ||
|
|
||
| $result = $this->invokePrivate($this->broker, 'processMessageReply', [$reply, $existingCalendar]); | ||
|
|
||
| $this->assertSame($existingCalendar, $result); | ||
| $this->assertCount(2, $result->VEVENT->ATTENDEE); | ||
| $this->assertEquals('mailto:attendee2@testing.com', $result->VEVENT->ATTENDEE[1]->getValue()); | ||
| $this->assertEquals('ACCEPTED', $result->VEVENT->ATTENDEE[1]['PARTSTAT']->getValue()); | ||
| $this->assertEquals('Attendee Two', $result->VEVENT->ATTENDEE[1]['CN']->getValue()); | ||
| } | ||
|
|
||
| public function testProcessMessageReplyAllowsPartyCrasherByDefault(): void { | ||
| $existingCalendar = clone $this->vCalendar1a; | ||
| $reply = new Message(); | ||
| $reply->uid = $existingCalendar->VEVENT->UID->getValue(); | ||
| $reply->component = 'VEVENT'; | ||
| $reply->sender = 'mailto:attendee2@testing.com'; | ||
| $reply->senderName = 'Attendee Two'; | ||
| $reply->sequence = 1; | ||
| $reply->message = new VCalendar(); | ||
| /** @var \Sabre\VObject\Component\VEvent $replyEvent */ | ||
| $replyEvent = $reply->message->add('VEVENT', []); | ||
| $replyEvent->add('UID', $reply->uid); | ||
| $replyEvent->add('ATTENDEE', $reply->sender, [ | ||
| 'PARTSTAT' => 'ACCEPTED', | ||
| ]); | ||
|
|
||
| $result = $this->invokePrivate($this->broker, 'processMessageReply', [$reply, $existingCalendar]); | ||
|
|
||
| $this->assertSame($existingCalendar, $result); | ||
| $this->assertCount(2, $result->VEVENT->ATTENDEE); | ||
| $this->assertEquals('mailto:attendee2@testing.com', $result->VEVENT->ATTENDEE[1]->getValue()); | ||
| $this->assertEquals('ACCEPTED', $result->VEVENT->ATTENDEE[1]['PARTSTAT']->getValue()); | ||
| $this->assertEquals('Attendee Two', $result->VEVENT->ATTENDEE[1]['CN']->getValue()); | ||
| } | ||
|
|
||
| public function testProcessMessageReplyDisallowsPartyCrasherForGeneratedRecurringInstance(): void { | ||
| $existingCalendar = clone $this->vCalendar2a; | ||
| $existingCalendar->VEVENT->add('X-NC-PARTY-CRASHER', 'FALSE'); | ||
| $reply = new Message(); | ||
| $reply->uid = $existingCalendar->VEVENT->UID->getValue(); | ||
| $reply->component = 'VEVENT'; | ||
| $reply->sender = 'mailto:attendee2@testing.com'; | ||
| $reply->senderName = 'Attendee Two'; | ||
| $reply->sequence = 1; | ||
| $reply->message = new VCalendar(); | ||
| /** @var \Sabre\VObject\Component\VEvent $replyEvent */ | ||
| $replyEvent = $reply->message->add('VEVENT', []); | ||
| $replyEvent->add('UID', $reply->uid); | ||
| $replyEvent->add('RECURRENCE-ID', '20240715T080000', ['TZID' => 'America/Toronto']); | ||
| $replyEvent->add('ATTENDEE', $reply->sender, [ | ||
| 'PARTSTAT' => 'ACCEPTED', | ||
| ]); | ||
|
|
||
| $result = $this->invokePrivate($this->broker, 'processMessageReply', [$reply, $existingCalendar]); | ||
|
|
||
| $this->assertSame($existingCalendar, $result); | ||
| $this->assertCount(1, $result->VEVENT); | ||
| $this->assertCount(1, $result->VEVENT->ATTENDEE); | ||
| $this->assertEquals('mailto:attendee1@testing.com', $result->VEVENT->ATTENDEE[0]->getValue()); | ||
| } | ||
|
|
||
| public function testProcessMessageReplyAllowsPartyCrasherForGeneratedRecurringInstance(): void { | ||
| $existingCalendar = clone $this->vCalendar2a; | ||
| $existingCalendar->VEVENT->add('X-NC-PARTY-CRASHER', 'TRUE'); | ||
| $reply = new Message(); | ||
| $reply->uid = $existingCalendar->VEVENT->UID->getValue(); | ||
| $reply->component = 'VEVENT'; | ||
| $reply->sender = 'mailto:attendee2@testing.com'; | ||
| $reply->senderName = 'Attendee Two'; | ||
| $reply->sequence = 1; | ||
| $reply->message = new VCalendar(); | ||
| /** @var \Sabre\VObject\Component\VEvent $replyEvent */ | ||
| $replyEvent = $reply->message->add('VEVENT', []); | ||
| $replyEvent->add('UID', $reply->uid); | ||
| $replyEvent->add('RECURRENCE-ID', '20240715T080000', ['TZID' => 'America/Toronto']); | ||
| $replyEvent->add('ATTENDEE', $reply->sender, [ | ||
| 'PARTSTAT' => 'ACCEPTED', | ||
| ]); | ||
|
|
||
| $result = $this->invokePrivate($this->broker, 'processMessageReply', [$reply, $existingCalendar]); | ||
|
|
||
| $this->assertSame($existingCalendar, $result); | ||
| $this->assertCount(2, $result->VEVENT); | ||
| $this->assertEquals('20240715T080000', $result->VEVENT[1]->{'RECURRENCE-ID'}->getValue()); | ||
| $this->assertFalse(isset($result->VEVENT[1]->RRULE)); | ||
| $this->assertCount(2, $result->VEVENT[1]->ATTENDEE); | ||
| $this->assertEquals('mailto:attendee2@testing.com', $result->VEVENT[1]->ATTENDEE[1]->getValue()); | ||
| $this->assertEquals('ACCEPTED', $result->VEVENT[1]->ATTENDEE[1]['PARTSTAT']->getValue()); | ||
| $this->assertEquals('Attendee Two', $result->VEVENT[1]->ATTENDEE[1]['CN']->getValue()); | ||
| } | ||
|
|
||
| public function testProcessMessageReplyAllowsPartyCrasherByDefaultForGeneratedRecurringInstance(): void { | ||
| $existingCalendar = clone $this->vCalendar2a; | ||
| $reply = new Message(); | ||
| $reply->uid = $existingCalendar->VEVENT->UID->getValue(); | ||
| $reply->component = 'VEVENT'; | ||
| $reply->sender = 'mailto:attendee2@testing.com'; | ||
| $reply->senderName = 'Attendee Two'; | ||
| $reply->sequence = 1; | ||
| $reply->message = new VCalendar(); | ||
| /** @var \Sabre\VObject\Component\VEvent $replyEvent */ | ||
| $replyEvent = $reply->message->add('VEVENT', []); | ||
| $replyEvent->add('UID', $reply->uid); | ||
| $replyEvent->add('RECURRENCE-ID', '20240715T080000', ['TZID' => 'America/Toronto']); | ||
| $replyEvent->add('ATTENDEE', $reply->sender, [ | ||
| 'PARTSTAT' => 'ACCEPTED', | ||
| ]); | ||
|
|
||
| $result = $this->invokePrivate($this->broker, 'processMessageReply', [$reply, $existingCalendar]); | ||
|
|
||
| $this->assertSame($existingCalendar, $result); | ||
| $this->assertCount(2, $result->VEVENT); | ||
| $this->assertEquals('20240715T080000', $result->VEVENT[1]->{'RECURRENCE-ID'}->getValue()); | ||
| $this->assertFalse(isset($result->VEVENT[1]->RRULE)); | ||
| $this->assertCount(2, $result->VEVENT[1]->ATTENDEE); | ||
| $this->assertEquals('mailto:attendee2@testing.com', $result->VEVENT[1]->ATTENDEE[1]->getValue()); | ||
| $this->assertEquals('ACCEPTED', $result->VEVENT[1]->ATTENDEE[1]['PARTSTAT']->getValue()); | ||
| $this->assertEquals('Attendee Two', $result->VEVENT[1]->ATTENDEE[1]['CN']->getValue()); | ||
| } | ||
|
|
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: neutral language