Skip to content

Commit

Permalink
Merge pull request #6782 from nextcloud/enh/imip-invitations-responses
Browse files Browse the repository at this point in the history
Support iMIP requests
  • Loading branch information
miaulalala authored Jul 14, 2022
2 parents fe5eb8c + d0c4181 commit 775df21
Show file tree
Hide file tree
Showing 5 changed files with 490 additions and 14 deletions.
54 changes: 40 additions & 14 deletions lib/Model/IMAPMessage.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ public function __construct($conn,
public $plainMessage = '';
public $attachments = [];
public $inlineAttachments = [];
public $scheduling = [];
private $loadHtmlMessage = false;
private $hasHtmlMessage = false;

Expand Down Expand Up @@ -398,6 +399,44 @@ private function loadMessageBodies(): void {
* @return void
*/
private function getPart(Horde_Mime_Part $p, $partNo): void {
// iMIP messages
// Handle text/calendar parts first because they might be attachments at the same time.
// Otherwise, some of the following if-conditions might break the handling and treat iMIP
// data like regular attachments.
$allContentTypeParameters = $p->getAllContentTypeParameters();
if ($p->getType() === 'text/calendar') {
// Handle event data like a regular attachment
// Outlook doesn't set a content disposition
// We work around this by checking for the name only
if ($p->getName() !== null) {
$this->attachments[] = [
'id' => $p->getMimeId(),
'messageId' => $this->messageId,
'fileName' => $p->getName(),
'mime' => $p->getType(),
'size' => $p->getBytes(),
'cid' => $p->getContentId(),
'disposition' => $p->getDisposition()
];
}

// return if this is an event attachment only
// the method parameter determines if this is a iMIP message
if (!isset($allContentTypeParameters['method'])) {
return;
}

if (in_array(strtoupper($allContentTypeParameters['method']), ['REQUEST', 'REPLY', 'CANCEL'])) {
$this->scheduling[] = [
'id' => $p->getMimeId(),
'messageId' => $this->messageId,
'method' => strtoupper($allContentTypeParameters['method']),
'contents' => $this->loadBodyData($p, $partNo),
];
return;
}
}

// Regular attachments
if ($p->isAttachment() || $p->getType() === 'message/rfc822') {
$this->attachments[] = [
Expand Down Expand Up @@ -442,19 +481,6 @@ private function getPart(Horde_Mime_Part $p, $partNo): void {
return;
}

if ($p->getType() === 'text/calendar') {
$this->attachments[] = [
'id' => $p->getMimeId(),
'messageId' => $this->messageId,
'fileName' => $p->getName() ?? 'calendar.ics',
'mime' => $p->getType(),
'size' => $p->getBytes(),
'cid' => $p->getContentId(),
'disposition' => $p->getDisposition()
];
return;
}

if ($p->getType() === 'text/html') {
$this->handleHtmlMessage($p, $partNo);
return;
Expand All @@ -478,7 +504,6 @@ private function getPart(Horde_Mime_Part $p, $partNo): void {
*/
public function getFullMessage(int $id): array {
$mailBody = $this->plainMessage;

$data = $this->jsonSerialize();
if ($this->hasHtmlMessage) {
$data['hasHtmlBody'] = true;
Expand Down Expand Up @@ -509,6 +534,7 @@ public function jsonSerialize() {
'flags' => $this->getFlags(),
'hasHtmlBody' => $this->hasHtmlMessage,
'dispositionNotificationTo' => $this->getDispositionNotificationTo(),
'scheduling' => $this->scheduling,
];
}

Expand Down
181 changes: 181 additions & 0 deletions src/components/Imip.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
<!--
- @copyright Copyright (c) 2022 Richard Steinmetz <[email protected]>
-
- @author Richard Steinmetz <[email protected]>
-
- @license AGPL-3.0-or-later
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as
- published by the Free Software Foundation, either version 3 of the
- License, or (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
-
-->

<template>
<div class="imip">
<div
v-if="isRequest"
class="imip__type">
<span v-if="wasUpdated">{{ t('mail', 'An event you have been invited to was updated') }}</span>
<span v-else>{{ t('mail', 'You have been invited to an event') }}</span>
</div>
<div
v-else-if="isReply"
class="imip__type">
<CalendarIcon :size="20" />
<span>{{ t('mail', 'This event was updated') }}</span>
</div>
<div
v-else-if="isCancel"
class="imip__type">
<CloseIcon :size="20" fill-color="red" />
<span>{{ t('mail', 'This event was cancelled') }}</span>
</div>

<EventData :event="parsedEvent" />

<!-- TODO: actually implement buttons (https://github.com/nextcloud/mail/issues/6803) -->
<!-- TODO: "More options" needs more specification -->
<!--
<div class="imip__actions">
<template v-if="isRequest && eventIsInFuture">
<Button
type="tertiary">
{{ t('mail', 'Accept') }}
</Button>
<Button type="tertiary">
{{ t('mail', 'Decline') }}
</Button>
</template>
<Actions :menu-title="t('mail', 'More options')" />
</div>
-->
</div>
</template>

<script>
import EventData from './imip/EventData'
// import Button from '@nextcloud/vue/dist/Components/Button'
// import Actions from '@nextcloud/vue/dist/Components/Actions'
import CloseIcon from 'vue-material-design-icons/Close'
import CalendarIcon from 'vue-material-design-icons/Calendar'
import { getParserManager } from '@nextcloud/calendar-js'
const REQUEST = 'REQUEST'
const REPLY = 'REPLY'
const CANCEL = 'CANCEL'
export default {
name: 'Imip',
components: {
EventData,
// Button,
// Actions,
CloseIcon,
CalendarIcon,
},
props: {
scheduling: {
type: Object,
required: true,
},
},
data() {
return {
REQUEST,
CANCEL,
REPLY,
}
},
computed: {
/**
* @returns {string}
*/
method() {
return this.scheduling.method
},
/**
* @returns {boolean}
*/
isRequest() {
return this.method === REQUEST
},
/**
* @returns {boolean}
*/
isReply() {
return this.method === REPLY
},
/**
* @returns {boolean}
*/
isCancel() {
return this.method === CANCEL
},
/**
* @returns {boolean}
*/
wasUpdated() {
// TODO: ask backend whether invitation is new or was updated
return false
},
/**
* @returns {EventComponent|undefined}
*/
parsedEvent() {
const parserManager = getParserManager()
const parser = parserManager.getParserForFileType('text/calendar')
parser.parse(this.scheduling.contents)
const vCalendar = parser.getItemIterator().next().value
if (!vCalendar) {
return undefined
}
const vEvent = vCalendar.getEventIterator().next().value
return vEvent ?? undefined
},
/**
* @returns {boolean}
*/
eventIsInFuture() {
return this.parsedEvent.startDate.jsDate.getTime() > new Date().getTime()
},
},
}
</script>
<style lang="scss" scoped>
.imip {
display: flex;
flex-direction: column;
border: solid 2px var(--color-border);
border-radius: var(--border-radius-large);
padding: 10px;
&__type {
display: flex;
gap: 5px;
}
&__actions {
display: flex;
margin-top: 15px;
}
}
</style>
12 changes: 12 additions & 0 deletions src/components/Message.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@
<div v-if="itineraries.length > 0" class="message-itinerary">
<Itinerary :entries="itineraries" :message-id="message.messageId" />
</div>
<div v-if="message.scheduling.length > 0" class="message-imip">
<Imip
v-for="scheduling in message.scheduling"
:key="scheduling.id"
:scheduling="scheduling" />
</div>
<MessageHTMLBody v-if="message.hasHtmlBody"
:url="htmlUrl"
:message="message"
Expand Down Expand Up @@ -54,6 +60,7 @@ import MessageAttachments from './MessageAttachments'
import MessageEncryptedBody from './MessageEncryptedBody'
import MessageHTMLBody from './MessageHTMLBody'
import MessagePlainTextBody from './MessagePlainTextBody'
import Imip from './Imip'
export default {
name: 'Message',
Expand All @@ -63,6 +70,7 @@ export default {
MessageEncryptedBody,
MessageHTMLBody,
MessagePlainTextBody,
Imip,
},
props: {
envelope: {
Expand Down Expand Up @@ -103,4 +111,8 @@ export default {
border-radius: 22px;
background-color: var(--color-background-darker);
}
.message-imip {
padding: 5px 10px;
}
</style>
Loading

0 comments on commit 775df21

Please sign in to comment.