@@ -1237,4 +1237,271 @@ public function testExternalAttendeesDisabledForSystemUser(): void {
12371237 $ this ->plugin ->schedule ($ message );
12381238 $ this ->assertEquals ('1.1 ' , $ message ->getScheduleStatus ());
12391239 }
1240+
1241+ public function testRequestWithOnlyCancelledOccurrenceSendsNoEmail (): void {
1242+ $ message = new Message ();
1243+ $ message ->method = 'REQUEST ' ;
1244+ $ newVCalendar = new VCalendar ();
1245+ $ cancelledOverride = new VEvent ($ newVCalendar , 'one ' , [
1246+ 'UID ' => 'uid-1234 ' ,
1247+ 'RECURRENCE-ID ' => new \DateTime ('2016-01-08 00:00:00 ' ),
1248+ 'SEQUENCE ' => 1 ,
1249+ 'STATUS ' => 'CANCELLED ' ,
1250+ 'SUMMARY ' => 'Fellowship meeting ' ,
1251+ 'DTSTART ' => new \DateTime ('2016-01-08 00:00:00 ' ),
1252+ ]);
1253+ $ cancelledOverride ->add ('ORGANIZER ' , 'mailto:gandalf@wiz.ard ' );
1254+ $ cancelledOverride ->add ('ATTENDEE ' , 'mailto: ' . 'frodo@hobb.it ' , ['RSVP ' => 'TRUE ' ]);
1255+ $ message ->message = $ newVCalendar ;
1256+ $ message ->sender = 'mailto:gandalf@wiz.ard ' ;
1257+ $ message ->senderName = 'Mr. Wizard ' ;
1258+ $ message ->recipient = 'mailto: ' . 'frodo@hobb.it ' ;
1259+ $ message ->significantChange = true ;
1260+ // stored old copy: the series without the override, so the cancelled
1261+ // override is newly created in this write
1262+ $ oldVCalendar = new VCalendar ();
1263+ $ oldVCalendar ->add (new VEvent ($ oldVCalendar , 'one ' , [
1264+ 'UID ' => 'uid-1234 ' ,
1265+ 'SEQUENCE ' => 0 ,
1266+ 'SUMMARY ' => 'Fellowship meeting ' ,
1267+ 'DTSTART ' => new \DateTime ('2016-01-01 00:00:00 ' ),
1268+ ]));
1269+ $ this ->plugin ->setVCalendar ($ oldVCalendar );
1270+
1271+ $ this ->service ->expects (self ::once ())
1272+ ->method ('getLastOccurrence ' )
1273+ ->willReturn (1496912700 );
1274+ $ this ->config ->expects (self ::once ())
1275+ ->method ('getValueBool ' )
1276+ ->with ('dav ' , 'caldav_external_attendees_disabled ' , false )
1277+ ->willReturn (false );
1278+ // The REQUEST carries only the cancelled override, kept so the attendee's
1279+ // stored copy stays cancelled through Sabre's full component replace. The
1280+ // occurrence cancellation is announced by the accompanying CANCEL message,
1281+ // so this REQUEST must not additionally send an invitation email.
1282+ $ this ->eventComparisonService ->expects (self ::once ())
1283+ ->method ('findModified ' )
1284+ ->willReturn (['new ' => [$ cancelledOverride ], 'old ' => []]);
1285+ $ this ->service ->expects (self ::never ())
1286+ ->method ('getCurrentAttendee ' );
1287+ $ this ->service ->expects (self ::never ())
1288+ ->method ('buildBodyData ' );
1289+ $ this ->mailer ->expects (self ::never ())
1290+ ->method ('send ' );
1291+ // deliberate suppression, not the "significant but nothing changed" anomaly
1292+ $ this ->logger ->expects (self ::never ())
1293+ ->method ('warning ' );
1294+
1295+ $ this ->plugin ->schedule ($ message );
1296+ $ this ->assertEquals ('1.0 ' , $ message ->getScheduleStatus ());
1297+ }
1298+
1299+ public function testRequestForCancelledExistingOccurrenceStillSendsEmail (): void {
1300+ $ message = new Message ();
1301+ $ message ->method = 'REQUEST ' ;
1302+ $ newVCalendar = new VCalendar ();
1303+ $ cancelledOverride = new VEvent ($ newVCalendar , 'one ' , [
1304+ 'UID ' => 'uid-1234 ' ,
1305+ 'RECURRENCE-ID ' => new \DateTime ('2016-01-08 00:00:00 ' ),
1306+ 'SEQUENCE ' => 2 ,
1307+ 'STATUS ' => 'CANCELLED ' ,
1308+ 'SUMMARY ' => 'Fellowship meeting ' ,
1309+ 'DTSTART ' => new \DateTime ('2016-01-08 00:00:00 ' ),
1310+ ]);
1311+ $ cancelledOverride ->add ('ORGANIZER ' , 'mailto:gandalf@wiz.ard ' );
1312+ $ cancelledOverride ->add ('ATTENDEE ' , 'mailto: ' . 'frodo@hobb.it ' , ['RSVP ' => 'TRUE ' ]);
1313+ $ message ->message = $ newVCalendar ;
1314+ $ message ->sender = 'mailto:gandalf@wiz.ard ' ;
1315+ $ message ->senderName = 'Mr. Wizard ' ;
1316+ $ message ->recipient = 'mailto: ' . 'frodo@hobb.it ' ;
1317+ $ message ->significantChange = true ;
1318+ // stored old copy already contains a live override for this occurrence:
1319+ // no CANCEL message is generated for it, this REQUEST is the only notice
1320+ $ oldVCalendar = new VCalendar ();
1321+ $ oldVCalendar ->add (new VEvent ($ oldVCalendar , 'one ' , [
1322+ 'UID ' => 'uid-1234 ' ,
1323+ 'SEQUENCE ' => 0 ,
1324+ 'SUMMARY ' => 'Fellowship meeting ' ,
1325+ 'DTSTART ' => new \DateTime ('2016-01-01 00:00:00 ' ),
1326+ ]));
1327+ $ oldOverride = new VEvent ($ oldVCalendar , 'one ' , [
1328+ 'UID ' => 'uid-1234 ' ,
1329+ 'RECURRENCE-ID ' => new \DateTime ('2016-01-08 00:00:00 ' ),
1330+ 'SEQUENCE ' => 1 ,
1331+ 'SUMMARY ' => 'Fellowship meeting ' ,
1332+ 'DTSTART ' => new \DateTime ('2016-01-08 00:00:00 ' ),
1333+ ]);
1334+ $ oldOverride ->add ('ORGANIZER ' , 'mailto:gandalf@wiz.ard ' );
1335+ $ oldOverride ->add ('ATTENDEE ' , 'mailto: ' . 'frodo@hobb.it ' , ['RSVP ' => 'TRUE ' ]);
1336+ $ oldVCalendar ->add ($ oldOverride );
1337+ $ this ->plugin ->setVCalendar ($ oldVCalendar );
1338+ $ data = [
1339+ 'invitee_name ' => 'Mr. Wizard ' ,
1340+ 'meeting_title ' => 'Fellowship meeting ' ,
1341+ 'attendee_name ' => 'frodo@hobb.it ' ,
1342+ ];
1343+ $ attendees = $ cancelledOverride ->select ('ATTENDEE ' );
1344+ $ atnd = '' ;
1345+ foreach ($ attendees as $ attendee ) {
1346+ if (strcasecmp ($ attendee ->getValue (), $ message ->recipient ) === 0 ) {
1347+ $ atnd = $ attendee ;
1348+ }
1349+ }
1350+ $ this ->service ->expects (self ::once ())
1351+ ->method ('getLastOccurrence ' )
1352+ ->willReturn (1496912700 );
1353+ $ this ->config ->expects (self ::exactly (2 ))
1354+ ->method ('getValueBool ' )
1355+ ->willReturnMap ([
1356+ ['dav ' , 'caldav_external_attendees_disabled ' , false , false ],
1357+ ['core ' , 'mail_providers_enabled ' , true , false ],
1358+ ]);
1359+ $ this ->eventComparisonService ->expects (self ::once ())
1360+ ->method ('findModified ' )
1361+ ->willReturn (['new ' => [$ cancelledOverride ], 'old ' => [$ oldOverride ]]);
1362+ $ this ->service ->expects (self ::once ())
1363+ ->method ('getCurrentAttendee ' )
1364+ ->with ($ message )
1365+ ->willReturn ($ atnd );
1366+ $ this ->service ->expects (self ::once ())
1367+ ->method ('isRoomOrResource ' )
1368+ ->with ($ atnd )
1369+ ->willReturn (false );
1370+ $ this ->service ->expects (self ::once ())
1371+ ->method ('isCircle ' )
1372+ ->with ($ atnd )
1373+ ->willReturn (false );
1374+ $ this ->service ->expects (self ::once ())
1375+ ->method ('buildBodyData ' )
1376+ ->with ($ cancelledOverride , $ oldOverride )
1377+ ->willReturn ($ data );
1378+ $ this ->service ->expects (self ::once ())
1379+ ->method ('getFrom ' );
1380+ $ this ->service ->expects (self ::once ())
1381+ ->method ('addSubjectAndHeading ' )
1382+ ->with ($ this ->emailTemplate , 'request ' , 'Mr. Wizard ' , 'Fellowship meeting ' , true );
1383+ $ this ->service ->expects (self ::once ())
1384+ ->method ('addBulletList ' )
1385+ ->with ($ this ->emailTemplate , $ cancelledOverride , $ data );
1386+ $ this ->service ->expects (self ::once ())
1387+ ->method ('getAttendeeRsvpOrReqForParticipant ' )
1388+ ->willReturn (false );
1389+ $ this ->mailer ->expects (self ::once ())
1390+ ->method ('send ' )
1391+ ->willReturn ([]);
1392+ $ this ->plugin ->schedule ($ message );
1393+ $ this ->assertEquals ('1.1 ' , $ message ->getScheduleStatus ());
1394+ }
1395+
1396+ public function testRequestKeepsChangeWhenCancelledOccurrenceIsAlsoPresent (): void {
1397+ $ message = new Message ();
1398+ $ message ->method = 'REQUEST ' ;
1399+ $ newVCalendar = new VCalendar ();
1400+ $ newVevent = new VEvent ($ newVCalendar , 'one ' , [
1401+ 'UID ' => 'uid-1234 ' ,
1402+ 'SEQUENCE ' => 1 ,
1403+ 'SUMMARY ' => 'Fellowship meeting without (!) Boromir ' ,
1404+ 'DTSTART ' => new \DateTime ('2016-01-01 00:00:00 ' ),
1405+ ]);
1406+ $ newVevent ->add ('ORGANIZER ' , 'mailto:gandalf@wiz.ard ' );
1407+ $ newVevent ->add ('ATTENDEE ' , 'mailto: ' . 'frodo@hobb.it ' , ['RSVP ' => 'TRUE ' , 'CN ' => 'Frodo ' ]);
1408+ $ cancelledOverride = new VEvent ($ newVCalendar , 'one ' , [
1409+ 'UID ' => 'uid-1234 ' ,
1410+ 'RECURRENCE-ID ' => new \DateTime ('2016-01-08 00:00:00 ' ),
1411+ 'SEQUENCE ' => 1 ,
1412+ 'STATUS ' => 'CANCELLED ' ,
1413+ 'SUMMARY ' => 'Fellowship meeting without (!) Boromir ' ,
1414+ 'DTSTART ' => new \DateTime ('2016-01-08 00:00:00 ' ),
1415+ ]);
1416+ $ cancelledOverride ->add ('ORGANIZER ' , 'mailto:gandalf@wiz.ard ' );
1417+ $ cancelledOverride ->add ('ATTENDEE ' , 'mailto: ' . 'frodo@hobb.it ' , ['RSVP ' => 'TRUE ' ]);
1418+ $ message ->message = $ newVCalendar ;
1419+ $ message ->sender = 'mailto:gandalf@wiz.ard ' ;
1420+ $ message ->senderName = 'Mr. Wizard ' ;
1421+ $ message ->recipient = 'mailto: ' . 'frodo@hobb.it ' ;
1422+ $ message ->significantChange = true ;
1423+ $ oldVCalendar = new VCalendar ();
1424+ $ oldVEvent = new VEvent ($ oldVCalendar , 'one ' , [
1425+ 'UID ' => 'uid-1234 ' ,
1426+ 'SEQUENCE ' => 0 ,
1427+ 'SUMMARY ' => 'Fellowship meeting ' ,
1428+ 'DTSTART ' => new \DateTime ('2016-01-01 00:00:00 ' ),
1429+ ]);
1430+ $ oldVEvent ->add ('ORGANIZER ' , 'mailto:gandalf@wiz.ard ' );
1431+ $ oldVEvent ->add ('ATTENDEE ' , 'mailto: ' . 'frodo@hobb.it ' , ['RSVP ' => 'TRUE ' , 'CN ' => 'Frodo ' ]);
1432+ $ oldVCalendar ->add ($ oldVEvent );
1433+ $ data = [
1434+ 'invitee_name ' => 'Mr. Wizard ' ,
1435+ 'meeting_title ' => 'Fellowship meeting without (!) Boromir ' ,
1436+ 'attendee_name ' => 'frodo@hobb.it ' ,
1437+ ];
1438+ $ attendees = $ newVevent ->select ('ATTENDEE ' );
1439+ $ atnd = '' ;
1440+ foreach ($ attendees as $ attendee ) {
1441+ if (strcasecmp ($ attendee ->getValue (), $ message ->recipient ) === 0 ) {
1442+ $ atnd = $ attendee ;
1443+ }
1444+ }
1445+ $ this ->plugin ->setVCalendar ($ oldVCalendar );
1446+ $ this ->service ->expects (self ::once ())
1447+ ->method ('getLastOccurrence ' )
1448+ ->willReturn (1496912700 );
1449+ $ this ->config ->expects (self ::exactly (2 ))
1450+ ->method ('getValueBool ' )
1451+ ->willReturnMap ([
1452+ ['dav ' , 'caldav_external_attendees_disabled ' , false , false ],
1453+ ['core ' , 'mail_providers_enabled ' , true , false ],
1454+ ]);
1455+ // The cancelled override rides along in the REQUEST but must not become the
1456+ // event the email describes: it is dropped, leaving the real change.
1457+ $ this ->eventComparisonService ->expects (self ::once ())
1458+ ->method ('findModified ' )
1459+ ->willReturn (['new ' => [$ newVevent , $ cancelledOverride ], 'old ' => [$ oldVEvent ]]);
1460+ $ this ->service ->expects (self ::once ())
1461+ ->method ('getCurrentAttendee ' )
1462+ ->with ($ message )
1463+ ->willReturn ($ atnd );
1464+ $ this ->service ->expects (self ::once ())
1465+ ->method ('isRoomOrResource ' )
1466+ ->with ($ atnd )
1467+ ->willReturn (false );
1468+ $ this ->service ->expects (self ::once ())
1469+ ->method ('isCircle ' )
1470+ ->with ($ atnd )
1471+ ->willReturn (false );
1472+ $ this ->service ->expects (self ::once ())
1473+ ->method ('buildBodyData ' )
1474+ ->with ($ newVevent , $ oldVEvent )
1475+ ->willReturn ($ data );
1476+ $ this ->service ->expects (self ::once ())
1477+ ->method ('getFrom ' );
1478+ $ this ->service ->expects (self ::once ())
1479+ ->method ('addSubjectAndHeading ' )
1480+ ->with ($ this ->emailTemplate , 'request ' , 'Mr. Wizard ' , 'Fellowship meeting without (!) Boromir ' , true );
1481+ $ this ->service ->expects (self ::once ())
1482+ ->method ('addBulletList ' )
1483+ ->with ($ this ->emailTemplate , $ newVevent , $ data );
1484+ $ this ->service ->expects (self ::once ())
1485+ ->method ('getAttendeeRsvpOrReqForParticipant ' )
1486+ ->willReturn (true );
1487+ $ this ->config ->expects (self ::once ())
1488+ ->method ('getValueString ' )
1489+ ->with ('dav ' , 'invitation_link_recipients ' , 'yes ' )
1490+ ->willReturn ('yes ' );
1491+ $ this ->service ->expects (self ::once ())
1492+ ->method ('createInvitationToken ' )
1493+ ->with ($ message , $ newVevent , 1496912700 )
1494+ ->willReturn ('token ' );
1495+ $ this ->service ->expects (self ::once ())
1496+ ->method ('addResponseButtons ' )
1497+ ->with ($ this ->emailTemplate , 'token ' );
1498+ $ this ->service ->expects (self ::once ())
1499+ ->method ('addMoreOptionsButton ' )
1500+ ->with ($ this ->emailTemplate , 'token ' );
1501+ $ this ->mailer ->expects (self ::once ())
1502+ ->method ('send ' )
1503+ ->willReturn ([]);
1504+ $ this ->plugin ->schedule ($ message );
1505+ $ this ->assertEquals ('1.1 ' , $ message ->getScheduleStatus ());
1506+ }
12401507}
0 commit comments