@@ -458,4 +458,125 @@ public function testParseEventForOrganizerModifyInstanceRemoveAttendee(): void {
458458
459459 }
460460
461+ /**
462+ * Tests user deleting master instance of recurring event
463+ */
464+ public function testParseEventForOrganizerDeleteMasterInstance (): void {
465+ // construct calendar with recurring event
466+ $ originalCalendar = clone $ this ->vCalendar2a ;
467+ $ originalEventInfo = $ this ->invokePrivate ($ this ->broker , 'parseEventInfo ' , [$ originalCalendar ]);
468+ // delete the master instance (convert to non-scheduling)
469+ $ mutatedCalendar = clone $ this ->vCalendar2a ;
470+ $ mutatedCalendar ->VEVENT ->{'LAST-MODIFIED ' }->setValue ('20240701T020000Z ' );
471+ $ mutatedCalendar ->VEVENT ->SEQUENCE ->setValue (2 );
472+ $ mutatedCalendar ->VEVENT ->remove ('ORGANIZER ' );
473+ $ mutatedCalendar ->VEVENT ->remove ('ATTENDEE ' );
474+ $ mutatedEventInfo = $ this ->invokePrivate ($ this ->broker , 'parseEventInfo ' , [$ mutatedCalendar ]);
475+ // test iTip generation
476+ $ messages = $ this ->invokePrivate ($ this ->broker , 'parseEventForOrganizer ' , [$ mutatedCalendar , $ mutatedEventInfo , $ originalEventInfo ]);
477+ $ this ->assertCount (1 , $ messages );
478+ $ this ->assertEquals ('CANCEL ' , $ messages [0 ]->method );
479+ $ this ->assertEquals (2 , $ messages [0 ]->sequence );
480+ $ this ->assertEquals ($ originalCalendar ->VEVENT ->ORGANIZER ->getValue (), $ messages [0 ]->sender );
481+ $ this ->assertEquals ($ originalCalendar ->VEVENT ->ATTENDEE [0 ]->getValue (), $ messages [0 ]->recipient );
482+ }
483+
484+ /**
485+ * Tests user adding EXDATE to master instance
486+ */
487+ public function testParseEventForOrganizerAddExdate (): void {
488+ // construct calendar with recurring event
489+ $ originalCalendar = clone $ this ->vCalendar2a ;
490+ $ originalEventInfo = $ this ->invokePrivate ($ this ->broker , 'parseEventInfo ' , [$ originalCalendar ]);
491+ // add EXDATE to exclude specific occurrences
492+ $ mutatedCalendar = clone $ this ->vCalendar2a ;
493+ $ mutatedCalendar ->VEVENT ->{'LAST-MODIFIED ' }->setValue ('20240701T020000Z ' );
494+ $ mutatedCalendar ->VEVENT ->SEQUENCE ->setValue (2 );
495+ $ mutatedCalendar ->VEVENT ->add ('EXDATE ' , ['20240715T080000 ' , '20240722T080000 ' ], ['TZID ' => 'America/Toronto ' ]);
496+ $ mutatedEventInfo = $ this ->invokePrivate ($ this ->broker , 'parseEventInfo ' , [$ mutatedCalendar ]);
497+ // test iTip generation
498+ $ messages = $ this ->invokePrivate ($ this ->broker , 'parseEventForOrganizer ' , [$ mutatedCalendar , $ mutatedEventInfo , $ originalEventInfo ]);
499+ $ this ->assertCount (1 , $ messages );
500+ $ this ->assertEquals ('REQUEST ' , $ messages [0 ]->method );
501+ $ this ->assertEquals (2 , $ messages [0 ]->sequence );
502+ $ this ->assertEquals ($ mutatedCalendar ->VEVENT ->ORGANIZER ->getValue (), $ messages [0 ]->sender );
503+ $ this ->assertEquals ($ mutatedCalendar ->VEVENT ->ATTENDEE [0 ]->getValue (), $ messages [0 ]->recipient );
504+ // verify EXDATE is present in the message
505+ $ this ->assertTrue (isset ($ messages [0 ]->message ->VEVENT ->EXDATE ));
506+ $ exdates = $ messages [0 ]->message ->VEVENT ->EXDATE ->getParts ();
507+ $ this ->assertContains ('20240715T080000 ' , $ exdates );
508+ $ this ->assertContains ('20240722T080000 ' , $ exdates );
509+ }
510+
511+ /**
512+ * Tests user removing EXDATE from master instance
513+ */
514+ public function testParseEventForOrganizerRemoveExdate (): void {
515+ // construct calendar with recurring event that has EXDATE
516+ $ originalCalendar = clone $ this ->vCalendar2a ;
517+ $ originalCalendar ->VEVENT ->add ('EXDATE ' , ['20240715T080000 ' , '20240722T080000 ' ], ['TZID ' => 'America/Toronto ' ]);
518+ $ originalEventInfo = $ this ->invokePrivate ($ this ->broker , 'parseEventInfo ' , [$ originalCalendar ]);
519+ // remove EXDATE to restore excluded occurrences
520+ $ mutatedCalendar = clone $ this ->vCalendar2a ;
521+ $ mutatedCalendar ->VEVENT ->{'LAST-MODIFIED ' }->setValue ('20240701T020000Z ' );
522+ $ mutatedCalendar ->VEVENT ->SEQUENCE ->setValue (2 );
523+ $ mutatedEventInfo = $ this ->invokePrivate ($ this ->broker , 'parseEventInfo ' , [$ mutatedCalendar ]);
524+ // test iTip generation
525+ $ messages = $ this ->invokePrivate ($ this ->broker , 'parseEventForOrganizer ' , [$ mutatedCalendar , $ mutatedEventInfo , $ originalEventInfo ]);
526+ $ this ->assertCount (1 , $ messages );
527+ $ this ->assertEquals ('REQUEST ' , $ messages [0 ]->method );
528+ $ this ->assertEquals (2 , $ messages [0 ]->sequence );
529+ $ this ->assertEquals ($ mutatedCalendar ->VEVENT ->ORGANIZER ->getValue (), $ messages [0 ]->sender );
530+ $ this ->assertEquals ($ mutatedCalendar ->VEVENT ->ATTENDEE [0 ]->getValue (), $ messages [0 ]->recipient );
531+ // verify EXDATE is not present in the message
532+ $ this ->assertFalse (isset ($ messages [0 ]->message ->VEVENT ->EXDATE ));
533+ }
534+
535+ /**
536+ * Tests user converting recurring event to non-scheduling
537+ */
538+ public function testParseEventForOrganizerConvertRecurringToNonScheduling (): void {
539+ // construct calendar with recurring event
540+ $ originalCalendar = clone $ this ->vCalendar2a ;
541+ $ originalEventInfo = $ this ->invokePrivate ($ this ->broker , 'parseEventInfo ' , [$ originalCalendar ]);
542+ // remove ORGANIZER and ATTENDEE properties to convert to non-scheduling
543+ $ mutatedCalendar = clone $ this ->vCalendar2a ;
544+ $ mutatedCalendar ->VEVENT ->{'LAST-MODIFIED ' }->setValue ('20240701T020000Z ' );
545+ $ mutatedCalendar ->VEVENT ->SEQUENCE ->setValue (2 );
546+ $ mutatedCalendar ->VEVENT ->remove ('ORGANIZER ' );
547+ $ mutatedCalendar ->VEVENT ->remove ('ATTENDEE ' );
548+ $ mutatedEventInfo = $ this ->invokePrivate ($ this ->broker , 'parseEventInfo ' , [$ mutatedCalendar ]);
549+ // test iTip generation
550+ $ messages = $ this ->invokePrivate ($ this ->broker , 'parseEventForOrganizer ' , [$ mutatedCalendar , $ mutatedEventInfo , $ originalEventInfo ]);
551+ $ this ->assertCount (1 , $ messages );
552+ $ this ->assertEquals ('CANCEL ' , $ messages [0 ]->method );
553+ $ this ->assertEquals (2 , $ messages [0 ]->sequence );
554+ $ this ->assertEquals ($ originalCalendar ->VEVENT ->ORGANIZER ->getValue (), $ messages [0 ]->sender );
555+ $ this ->assertEquals ($ originalCalendar ->VEVENT ->ATTENDEE [0 ]->getValue (), $ messages [0 ]->recipient );
556+ }
557+
558+ /**
559+ * Tests SCHEDULE-FORCE-SEND parameter handling
560+ */
561+ public function testParseEventForOrganizerScheduleForceSend (): void {
562+ // construct calendar with event
563+ $ originalCalendar = clone $ this ->vCalendar1a ;
564+ $ originalEventInfo = $ this ->invokePrivate ($ this ->broker , 'parseEventInfo ' , [$ originalCalendar ]);
565+ // add SCHEDULE-FORCE-SEND parameter to ATTENDEE
566+ $ mutatedCalendar = clone $ this ->vCalendar1a ;
567+ $ mutatedCalendar ->VEVENT ->{'LAST-MODIFIED ' }->setValue ('20240701T020000Z ' );
568+ $ mutatedCalendar ->VEVENT ->SEQUENCE ->setValue (2 );
569+ $ mutatedCalendar ->VEVENT ->ATTENDEE ->add ('SCHEDULE-FORCE-SEND ' , 'REQUEST ' );
570+ $ mutatedEventInfo = $ this ->invokePrivate ($ this ->broker , 'parseEventInfo ' , [$ mutatedCalendar ]);
571+ // test iTip generation
572+ $ messages = $ this ->invokePrivate ($ this ->broker , 'parseEventForOrganizer ' , [$ mutatedCalendar , $ mutatedEventInfo , $ originalEventInfo ]);
573+ $ this ->assertCount (1 , $ messages );
574+ $ this ->assertEquals ('REQUEST ' , $ messages [0 ]->method );
575+ $ this ->assertEquals (2 , $ messages [0 ]->sequence );
576+ $ this ->assertEquals ($ mutatedCalendar ->VEVENT ->ORGANIZER ->getValue (), $ messages [0 ]->sender );
577+ $ this ->assertEquals ($ mutatedCalendar ->VEVENT ->ATTENDEE ->getValue (), $ messages [0 ]->recipient );
578+ // verify SCHEDULE-FORCE-SEND is removed from the message (sanitized)
579+ $ this ->assertFalse (isset ($ messages [0 ]->message ->VEVENT ->ATTENDEE ['SCHEDULE-FORCE-SEND ' ]));
580+ }
581+
461582}
0 commit comments