Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 40 additions & 1 deletion lib/Notification/Notifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,19 @@
use OCP\Notification\INotification;
use OCP\Notification\INotifier;
use OCP\Notification\UnknownNotificationException;
use Psr\Log\LoggerInterface;

class Notifier implements INotifier {
private IFactory $factory;
private IURLGenerator $url;
private LoggerInterface $logger;

public function __construct(IFactory $factory,
IURLGenerator $url) {
IURLGenerator $url,
LoggerInterface $logger) {
$this->factory = $factory;
$this->url = $url;
$this->logger = $logger;
}

#[\Override]
Expand Down Expand Up @@ -72,6 +76,41 @@ public function prepare(INotification $notification, string $languageCode): INot
]
]);
break;
case 'imip_processing_failed':
$parameters = $notification->getSubjectParameters();
$subject = $parameters['subject'] ?? $l->t('No subject');
$sender = $parameters['sender'] ?? $l->t('Unknown sender');
$mailboxId = (int)$parameters['mailboxId'];
$messageId = (int)$parameters['messageId'];
$subjectParam = [
'type' => 'highlight',
'id' => (string)$messageId,
'name' => $subject,
];
$senderParam = [
'type' => 'highlight',
'id' => 'sender',
'name' => $sender,
];
$notification->setRichSubject(
$l->t('Calendar invitation processing failed for "{subject}"'),
[
'subject' => $subjectParam,
]
);
$link = $this->url->linkToRouteAbsolute('mail.page.thread', [
'mailboxId' => $mailboxId,
'id' => $messageId,
]);
$notification->setLink($link);
$notification->setRichMessage(
$l->t('The invitation "{subject}" from {sender} could not be processed automatically. Please add the event manually.'),
[
'subject' => $subjectParam,
'sender' => $senderParam,
]
);
break;
default:
throw new UnknownNotificationException();
}
Expand Down
42 changes: 42 additions & 0 deletions lib/Service/IMipService.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use OCA\Mail\Util\ServerVersion;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\Calendar\IManager;
use OCP\Notification\IManager as INotificationManager;
use Psr\Log\LoggerInterface;
use Throwable;

Expand All @@ -32,6 +33,7 @@ class IMipService {
private MailManager $mailManager;
private MessageMapper $messageMapper;
private ServerVersion $serverVersion;
private INotificationManager $notificationManager;

public function __construct(
AccountService $accountService,
Expand All @@ -41,6 +43,7 @@ public function __construct(
MailManager $mailManager,
MessageMapper $messageMapper,
ServerVersion $serverVersion,
INotificationManager $notificationManager,
) {
$this->accountService = $accountService;
$this->calendarManager = $manager;
Expand All @@ -49,6 +52,7 @@ public function __construct(
$this->mailManager = $mailManager;
$this->messageMapper = $messageMapper;
$this->serverVersion = $serverVersion;
$this->notificationManager = $notificationManager;
}

public function process(): void {
Expand Down Expand Up @@ -145,6 +149,7 @@ public function process(): void {
// an IMAP message could contain more than one iMIP object
foreach ($imapMessage->scheduling as $schedulingInfo) {
$processed = false;

if ($systemVersion < 33) {
$principalUri = 'principals/users/' . $userId;
if ($schedulingInfo['method'] === 'REQUEST') {
Expand Down Expand Up @@ -182,9 +187,46 @@ public function process(): void {
]);
$message->setImipProcessed(true);
$message->setImipError(true);

try {
$this->sendErrorNotification($account, $mailbox, $message, $imapMessage);
} catch (Throwable $notificationException) {
$this->logger->error('Failed to send error notification', [
'exception' => $notificationException,
'messageId' => $message->getId(),
]);
}
}
}
$this->messageMapper->updateImipData(...$filteredMessages);
}
}

/**
* Send error notification when iMIP processing fails
* Uses Nextcloud Notifications app
*/
private function sendErrorNotification(
Account $account,
Mailbox $mailbox,
Message $message,
IMAPMessage $imapMessage,
): void {

$notification = $this->notificationManager->createNotification();

$notification
->setApp('mail')
->setUser($account->getUserId())
->setObject('imip_error', (string)$message->getId())
->setSubject('imip_processing_failed', [
'subject' => $imapMessage->getSubject(),
'sender' => $imapMessage->getFrom()->first()?->getEmail(),
'mailboxId' => $mailbox->getId(),
'messageId' => $message->getId(),
])
->setDateTime(new \DateTime());

$this->notificationManager->notify($notification);
}
}
62 changes: 51 additions & 11 deletions tests/Unit/Service/IMipServiceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
use OCA\Mail\Service\MailManager;
use OCA\Mail\Util\ServerVersion;
use OCP\Calendar\IManager;
use OCP\Notification\IManager as INotificationManager;
use OCP\ServerVersion as OCPServerVersion;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Log\LoggerInterface;
Expand All @@ -44,6 +45,8 @@ class IMipServiceTest extends TestCase {
/** @var MailManager|MockObject */
private $mailManager;

private INotificationManager $notificationManager;

/** @var MockObject|LoggerInterface */
private $logger;

Expand All @@ -64,6 +67,7 @@ protected function setUp(): void {
$this->messageMapper = $this->createMock(MessageMapper::class);
$this->serverVersion = $this->createMock(ServerVersion::class);
$this->OCPServerVersion = new OCPServerVersion();
$this->notificationManager = $this->createMock(INotificationManager::class);

$this->service = new IMipService(
$this->accountService,
Expand All @@ -72,7 +76,8 @@ protected function setUp(): void {
$this->mailboxMapper,
$this->mailManager,
$this->messageMapper,
$this->serverVersion
$this->serverVersion,
$this->notificationManager
);
}

Expand Down Expand Up @@ -298,7 +303,7 @@ public function testIsRequest(): void {
$addressList->expects(self::once())
->method('first')
->willReturn($address);
$address->expects(self::once())
$address->expects(self::any())
->method('getEmail')
->willReturn('pam@stardew-bus-service.com');
$this->logger->expects(self::never())
Expand Down Expand Up @@ -667,26 +672,61 @@ public function testHandleImipRequestThrowsException(): void {
$imapMessage->expects(self::once())
->method('getUid')
->willReturn(1);
$imapMessage->expects(self::once())
$imapMessage->expects(self::any())
->method('getFrom')
->willReturn($addressList);
$addressList->expects(self::once())
$addressList->expects(self::any())
->method('first')
->willReturn($address);
$address->expects(self::once())
$address->expects(self::any())
->method('getEmail')
->willReturn('pam@stardew-bus-service.com');
$this->calendarManager->expects(self::once())
->method('handleIMipRequest')
->willThrowException(new \Exception('Calendar error'));
$this->logger->expects(self::once())
$this->logger->expects(self::any())
->method('error')
->with(
'iMIP message processing failed',
self::callback(fn ($context) => isset($context['exception'])
&& $context['messageId'] === $message->getId()
&& $context['mailboxId'] === $mailbox->getId())
->withConsecutive(
[
'iMIP message processing failed',
self::callback(fn ($context) => isset($context['exception'])
&& $context['messageId'] === $message->getId()
&& $context['mailboxId'] === $mailbox->getId()),
],
[
'Failed to send error notification',
self::callback(fn ($context) => isset($context['exception'])
&& $context['messageId'] === $message->getId()),
]
);

$notification = $this->createMock(\OCP\Notification\INotification::class);
$this->notificationManager->expects(self::once())
->method('createNotification')
->willReturn($notification);
$notification->expects(self::once())
->method('setApp')
->with('mail')
->willReturn($notification);
$notification->expects(self::once())
->method('setUser')
->with('vincent')
->willReturn($notification);
$notification->expects(self::once())
->method('setObject')
->with('imip_error', (string)$message->getId())
->willReturn($notification);
$notification->expects(self::once())
->method('setSubject')
->with('imip_processing_failed', self::anything())
->willReturn($notification);
$notification->expects(self::once())
->method('setDateTime')
->willReturn($notification);
$this->notificationManager->expects(self::any())
->method('notify')
->with($notification);

$this->messageMapper->expects(self::once())
->method('updateImipData')
->with(self::callback(fn (Message $msg) => $msg->isImipProcessed() === true && $msg->isImipError() === true));
Expand Down