Skip to content

Commit 8be4559

Browse files
authored
feat: Allow or disallow party crasher (#8222)
Signed-off-by: Daniel Kesselberg <mail@danielkesselberg.de>
1 parent 809a848 commit 8be4559

5 files changed

Lines changed: 133 additions & 2 deletions

File tree

src/mixins/PropertyMixin.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
* See inline for more documentation
1111
*/
1212

13+
import AccountPlusOutline from 'vue-material-design-icons/AccountPlusOutline.vue'
1314
import Bell from 'vue-material-design-icons/BellOutline.vue'
1415
import Briefcase from 'vue-material-design-icons/BriefcaseOutline.vue'
1516
import Check from 'vue-material-design-icons/Check.vue'
@@ -21,6 +22,7 @@ import TextBoxOutline from 'vue-material-design-icons/TextBoxOutline.vue'
2122

2223
export default {
2324
components: {
25+
AccountPlusOutline,
2426
Briefcase,
2527
Check,
2628
Eye,

src/models/event.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ function getDefaultEventObject(props = {}) {
7171
categories: [],
7272
// Attachments of this event
7373
attachments: [],
74+
// Invitation forwarding
75+
invitationForwarding: 'TRUE',
7476
...props,
7577
}
7678
}
@@ -172,6 +174,10 @@ function mapEventComponentToEventObject(eventComponent) {
172174
}
173175
}
174176

177+
if (eventComponent.hasProperty('X-NC-INVITATION-FORWARDING')) {
178+
eventObject.invitationForwarding = eventComponent.getFirstPropertyFirstValue('X-NC-INVITATION-FORWARDING')
179+
}
180+
175181
return eventObject
176182
}
177183

@@ -221,6 +227,10 @@ function copyCalendarObjectInstanceIntoEventComponent(eventObject, eventComponen
221227
eventComponent.addProperty(rule)
222228
}
223229

230+
if (eventObject.eventComponent.hasProperty('X-NC-INVITATION-FORWARDING')) {
231+
eventComponent.updatePropertyWithValue('X-NC-INVITATION-FORWARDING', eventObject.invitationForwarding)
232+
}
233+
224234
if (eventObject.customColor) {
225235
eventComponent.color = getClosestCSS3ColorNameForHex(eventObject.customColor)
226236
}

src/store/calendarObjectInstance.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,18 @@ export default defineStore('calendarObjectInstance', {
385385
calendarObjectInstance.timeTransparency = timeTransparency
386386
},
387387

388+
/**
389+
* Change the invitation-forwarding property of an event
390+
*
391+
* @param {object} data The destructuring object
392+
* @param {object} data.calendarObjectInstance The calendarObjectInstance object
393+
* @param {string} data.invitationForwarding Invitation forwarding value
394+
*/
395+
changeInvitationForwarding({ calendarObjectInstance, invitationForwarding }) {
396+
calendarObjectInstance.eventComponent.updatePropertyWithValue('X-NC-INVITATION-FORWARDING', invitationForwarding)
397+
calendarObjectInstance.invitationForwarding = invitationForwarding
398+
},
399+
388400
/**
389401
* Change the customized color of an event
390402
*

src/views/EditFull.vue

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,12 @@
243243
:propModel="rfcProps.color"
244244
:value="color"
245245
@update:value="updateColor" />
246+
<PropertySelect
247+
v-if="showInvitationForwarding"
248+
:isReadOnly="isReadOnly || isViewedByOrganizer === false"
249+
:propModel="propInvitationForwarding"
250+
:value="invitationForwarding"
251+
@update:value="updateInvitationForwarding" />
246252
</div>
247253
</div>
248254

@@ -333,6 +339,7 @@
333339
import IconCancel from '@mdi/svg/svg/cancel.svg?raw'
334340
import IconDelete from '@mdi/svg/svg/delete.svg?raw'
335341
import { Parameter } from '@nextcloud/calendar-js'
342+
import { translate as t } from '@nextcloud/l10n'
336343
import moment from '@nextcloud/moment'
337344
import { generateUrl } from '@nextcloud/router'
338345
import {
@@ -378,6 +385,7 @@ import useCalendarObjectInstanceStore from '../store/calendarObjectInstance.js'
378385
import usePrincipalsStore from '../store/principals.js'
379386
import useSettingsStore from '../store/settings.js'
380387
import logger from '../utils/logger.js'
388+
import { isAfterVersion } from '../utils/nextcloudVersion.ts'
381389
382390
export default {
383391
name: 'EditFull',
@@ -447,6 +455,19 @@ export default {
447455
448456
showCancelDialog: false,
449457
showFullModal: true,
458+
459+
propInvitationForwarding: {
460+
readableName: t('calendar', 'Allow forwarding'),
461+
icon: 'AccountPlusOutline',
462+
options: [
463+
{ value: 'TRUE', label: t('calendar', 'Anyone with the invitation can respond') },
464+
{ value: 'FALSE', label: t('calendar', 'Only invited attendees can respond') },
465+
],
466+
467+
multiple: false,
468+
info: t('calendar', 'Choose "Only invited attendees can respond" to prevent attendees from forwarding the invitation to others.'),
469+
defaultValue: 'TRUE',
470+
},
450471
}
451472
},
452473
@@ -476,6 +497,10 @@ export default {
476497
return this.calendarObjectInstance?.timeTransparency || null
477498
},
478499
500+
invitationForwarding() {
501+
return this.calendarObjectInstance?.invitationForwarding ?? null
502+
},
503+
479504
subTitle() {
480505
if (!this.calendarObjectInstance) {
481506
return ''
@@ -506,6 +531,10 @@ export default {
506531
return ['ROOM', 'RESOURCE'].includes(attendee.attendeeProperty.userType)
507532
})
508533
},
534+
535+
showInvitationForwarding() {
536+
return isAfterVersion(34)
537+
},
509538
},
510539
511540
mounted() {
@@ -585,6 +614,18 @@ export default {
585614
})
586615
},
587616
617+
/**
618+
* Allow or disallow forwarding of this invitation
619+
*
620+
* @param {string} invitationForwarding Invitation forwarding value
621+
*/
622+
updateInvitationForwarding(invitationForwarding) {
623+
this.calendarObjectInstanceStore.changeInvitationForwarding({
624+
calendarObjectInstance: this.calendarObjectInstance,
625+
invitationForwarding,
626+
})
627+
},
628+
588629
/**
589630
* Adds a category to the event
590631
*

tests/javascript/unit/models/event.test.js

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* SPDX-License-Identifier: AGPL-3.0-or-later
44
*/
55

6-
import { getDefaultEventObject, mapEventComponentToEventObject } from "../../../../src/models/event.js";
6+
import { copyCalendarObjectInstanceIntoEventComponent, getDefaultEventObject, mapEventComponentToEventObject } from '../../../../src/models/event.js'
77
import { getDateFromDateTimeValue } from '../../../../src/utils/date.js'
88
import { getHexForColorName } from '../../../../src/utils/color.js'
99
import { mapAlarmComponentToAlarmObject } from '../../../../src/models/alarm.js'
@@ -62,6 +62,7 @@ describe('Test suite: Event model (models/event.js)', () => {
6262
customColor: null,
6363
categories: [],
6464
attachments: [],
65+
invitationForwarding: 'TRUE',
6566
})
6667

6768
expect(getDefaultRecurrenceRuleObject).toHaveBeenCalledTimes(1)
@@ -104,6 +105,7 @@ describe('Test suite: Event model (models/event.js)', () => {
104105
customColor: null,
105106
categories: [],
106107
attachments: [],
108+
invitationForwarding: 'TRUE',
107109
otherProp: 'foo',
108110
})
109111

@@ -154,6 +156,7 @@ describe('Test suite: Event model (models/event.js)', () => {
154156
customColor: null,
155157
categories: [],
156158
attachments: [],
159+
invitationForwarding: 'TRUE',
157160
})
158161

159162
expect(getDateFromDateTimeValue).toHaveBeenCalledTimes(2)
@@ -221,7 +224,8 @@ describe('Test suite: Event model (models/event.js)', () => {
221224
alarms: [],
222225
customColor: null,
223226
categories: [],
224-
attachments: []
227+
attachments: [],
228+
invitationForwarding: 'TRUE',
225229
})
226230

227231
expect(getDateFromDateTimeValue).toHaveBeenCalledTimes(2)
@@ -289,6 +293,7 @@ describe('Test suite: Event model (models/event.js)', () => {
289293
customColor: null,
290294
categories: [],
291295
attachments: [],
296+
invitationForwarding: 'TRUE',
292297
})
293298

294299
const alarms = eventComponent.getAlarmList()
@@ -343,6 +348,7 @@ describe('Test suite: Event model (models/event.js)', () => {
343348
customColor: null,
344349
categories: ['BUSINESS', 'HUMAN RESOURCES'],
345350
attachments: [],
351+
invitationForwarding: 'TRUE',
346352
})
347353

348354
expect(getDateFromDateTimeValue).toHaveBeenCalledTimes(2)
@@ -399,6 +405,7 @@ describe('Test suite: Event model (models/event.js)', () => {
399405
customColor: '#eeffee',
400406
categories: [],
401407
attachments: [],
408+
invitationForwarding: 'TRUE',
402409
})
403410

404411
expect(getDateFromDateTimeValue).toHaveBeenCalledTimes(2)
@@ -458,6 +465,7 @@ describe('Test suite: Event model (models/event.js)', () => {
458465
customColor: null,
459466
categories: [],
460467
attachments: [],
468+
invitationForwarding: 'TRUE',
461469
})
462470

463471
expect(getDateFromDateTimeValue).toHaveBeenCalledTimes(2)
@@ -514,6 +522,7 @@ describe('Test suite: Event model (models/event.js)', () => {
514522
customColor: null,
515523
categories: [],
516524
attachments: [],
525+
invitationForwarding: 'TRUE',
517526
})
518527

519528
expect(getDateFromDateTimeValue).toHaveBeenCalledTimes(2)
@@ -567,6 +576,7 @@ describe('Test suite: Event model (models/event.js)', () => {
567576
customColor: null,
568577
categories: [],
569578
attachments: [],
579+
invitationForwarding: 'TRUE',
570580
})
571581

572582
expect(getDateFromDateTimeValue).toHaveBeenCalledTimes(2)
@@ -620,6 +630,7 @@ describe('Test suite: Event model (models/event.js)', () => {
620630
customColor: null,
621631
categories: [],
622632
attachments: [],
633+
invitationForwarding: 'TRUE',
623634
})
624635

625636
expect(getDateFromDateTimeValue).toHaveBeenCalledTimes(2)
@@ -677,6 +688,7 @@ describe('Test suite: Event model (models/event.js)', () => {
677688
customColor: null,
678689
categories: [],
679690
attachments: [],
691+
invitationForwarding: 'TRUE',
680692
})
681693

682694
expect(getDateFromDateTimeValue).toHaveBeenCalledTimes(2)
@@ -733,6 +745,7 @@ describe('Test suite: Event model (models/event.js)', () => {
733745
customColor: null,
734746
categories: [],
735747
attachments: [],
748+
invitationForwarding: 'TRUE',
736749
})
737750

738751
expect(getDateFromDateTimeValue).toHaveBeenCalledTimes(2)
@@ -787,6 +800,7 @@ describe('Test suite: Event model (models/event.js)', () => {
787800
customColor: null,
788801
categories: [],
789802
attachments: [],
803+
invitationForwarding: 'TRUE',
790804
})
791805

792806
expect(getDateFromDateTimeValue).toHaveBeenCalledTimes(2)
@@ -844,6 +858,7 @@ describe('Test suite: Event model (models/event.js)', () => {
844858
customColor: null,
845859
categories: [],
846860
attachments: [],
861+
invitationForwarding: 'TRUE',
847862
})
848863

849864
expect(getDateFromDateTimeValue).toHaveBeenCalledTimes(2)
@@ -858,4 +873,55 @@ describe('Test suite: Event model (models/event.js)', () => {
858873

859874
expect(getDefaultRecurrenceRuleObject).toHaveBeenCalledTimes(1)
860875
})
876+
877+
it('should default invitation forwarding to TRUE', () => {
878+
getDefaultRecurrenceRuleObject
879+
.mockReturnValueOnce({
880+
defaultRecurrenceObject: true,
881+
})
882+
883+
expect(getDefaultEventObject().invitationForwarding).toEqual('TRUE')
884+
})
885+
886+
it('should map an event component custom invitation forwarding property', () => {
887+
const recurrenceId = DateTimeValue.fromJSDate(new Date(Date.UTC(2016, 7, 16, 7, 0, 0)), true)
888+
const eventComponent = getEventComponentFromAsset('vcalendars/vcalendar-event-timed', recurrenceId)
889+
eventComponent.updatePropertyWithValue('X-NC-INVITATION-FORWARDING', 'FALSE')
890+
891+
const mockDate1 = new Date()
892+
const mockDate2 = new Date()
893+
getDateFromDateTimeValue
894+
.mockReturnValueOnce(mockDate1)
895+
.mockReturnValueOnce(mockDate2)
896+
897+
getDefaultRecurrenceRuleObject
898+
.mockReturnValueOnce({
899+
defaultRecurrenceObject: true,
900+
})
901+
902+
expect(mapEventComponentToEventObject(eventComponent).invitationForwarding).toEqual('FALSE')
903+
})
904+
905+
it('should copy the custom invitation forwarding property into a new event component', () => {
906+
const recurrenceId = DateTimeValue.fromJSDate(new Date(Date.UTC(2016, 7, 16, 7, 0, 0)), true)
907+
const sourceEventComponent = getEventComponentFromAsset('vcalendars/vcalendar-event-timed', recurrenceId)
908+
const targetEventComponent = getEventComponentFromAsset('vcalendars/vcalendar-event-timed', recurrenceId)
909+
sourceEventComponent.updatePropertyWithValue('X-NC-INVITATION-FORWARDING', 'FALSE')
910+
911+
const eventObject = getDefaultEventObject({
912+
eventComponent: sourceEventComponent,
913+
title: sourceEventComponent.title,
914+
location: sourceEventComponent.location,
915+
description: sourceEventComponent.description,
916+
accessClass: sourceEventComponent.accessClass,
917+
status: sourceEventComponent.status,
918+
timeTransparency: sourceEventComponent.timeTransparency,
919+
invitationForwarding: 'FALSE',
920+
})
921+
922+
copyCalendarObjectInstanceIntoEventComponent(eventObject, targetEventComponent)
923+
924+
expect(targetEventComponent.hasProperty('X-NC-INVITATION-FORWARDING')).toBe(true)
925+
expect(targetEventComponent.getFirstPropertyFirstValue('X-NC-INVITATION-FORWARDING')).toBe('FALSE')
926+
})
861927
})

0 commit comments

Comments
 (0)