From 70965a0acecb8e773594bdf742723e671ef5d265 Mon Sep 17 00:00:00 2001 From: taiebot <13300652+taiebot@users.noreply.github.com> Date: Sun, 26 Jul 2026 09:55:06 +0200 Subject: [PATCH 1/2] Fix Add notification manager to ProposalServiceTest Signed-off-by: taiebot <13300652+taiebot@users.noreply.github.com> --- .../Service/Proposal/ProposalServiceTest.php | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/tests/php/unit/Service/Proposal/ProposalServiceTest.php b/tests/php/unit/Service/Proposal/ProposalServiceTest.php index c62d245a8b..6e81c41199 100644 --- a/tests/php/unit/Service/Proposal/ProposalServiceTest.php +++ b/tests/php/unit/Service/Proposal/ProposalServiceTest.php @@ -40,6 +40,7 @@ use OCP\IUserManager; use OCP\Mail\IMailer; use OCP\Mail\Provider\IManager as IMailManager; +use OCP\Notification\IManager as INotificationManager; use PHPUnit\Framework\MockObject\MockObject; use Psr\Log\LoggerInterface; @@ -58,6 +59,7 @@ class ProposalServiceTest extends TestCase { protected IMailer|MockObject $systemMailManager; protected IMailManager|MockObject $userMailManager; protected IManager|MockObject $calendarManager; + protected INotificationManager|MockObject $notificationManager; protected ProposalService $service; protected IUser|MockObject $user; @@ -77,6 +79,7 @@ protected function setUp(): void { $this->systemMailManager = $this->createMock(IMailer::class); $this->userMailManager = $this->createMock(IMailManager::class); $this->calendarManager = $this->createMock(Manager::class); + $this->notificationManager = $this->createMock(INotificationManager::class); $this->user = $this->createMock(IUser::class); $this->user->method('getUID')->willReturn('testuser'); @@ -96,7 +99,8 @@ protected function setUp(): void { $this->userManager, $this->systemMailManager, $this->userMailManager, - $this->calendarManager + $this->calendarManager, + $this->notificationManager ); } @@ -484,6 +488,17 @@ public function testStoreResponseSuccess(): void { ->method('update') ->with($participantEntry); + $notification = $this->createMock(\OCP\Notification\INotification::class); + $notification->method('setApp')->willReturn($notification); + $notification->method('setUser')->willReturn($notification); + $notification->method('setDateTime')->willReturn($notification); + $notification->method('setObject')->willReturn($notification); + $notification->method('setSubject')->willReturn($notification); + $this->notificationManager->method('createNotification')->willReturn($notification); + $this->notificationManager->expects($this->once()) + ->method('notify') + ->with($notification); + $this->service->storeResponse($response); } From 9301119107a62ae18ca7c025adff5d750324b371 Mon Sep 17 00:00:00 2001 From: taiebot <13300652+taiebot@users.noreply.github.com> Date: Fri, 31 Jul 2026 21:58:13 +0200 Subject: [PATCH 2/2] Fix: Proposalservice notify organizer Fix proposalService notify organizer Added notification to the organizer for confirmed proposals --- lib/Notification/Notifier.php | 31 ++++++--- lib/Service/Proposal/ProposalService.php | 17 +++++ tests/php/unit/Notification/NotifierTest.php | 73 ++++++++++++++++++++ 3 files changed, 113 insertions(+), 8 deletions(-) diff --git a/lib/Notification/Notifier.php b/lib/Notification/Notifier.php index bd1b676423..bea63aef08 100644 --- a/lib/Notification/Notifier.php +++ b/lib/Notification/Notifier.php @@ -18,18 +18,15 @@ class Notifier implements INotifier { private IFactory $factory; private IURLGenerator $url; - public function __construct(IFactory $factory, IURLGenerator $url) { $this->factory = $factory; $this->url = $url; } - #[\Override] public function getID(): string { return Application::APP_ID; } - /** * Human-readable name describing the notifier * @return string @@ -38,17 +35,14 @@ public function getID(): string { public function getName(): string { return $this->factory->get(Application::APP_ID)->t('Calendar'); } - #[\Override] public function prepare(INotification $notification, string $languageCode): INotification { if ($notification->getApp() !== Application::APP_ID) { // Not my app => throw throw new UnknownNotificationException(); } - // Read the language from the notification $l = $this->factory->get(Application::APP_ID, $languageCode); - switch ($notification->getSubject()) { // Deal with known subjects case 'booking_accepted': @@ -61,7 +55,6 @@ public function prepare(INotification $notification, string $languageCode): INot 'link' => $this->url->linkToRouteAbsolute('calendar.view.index') ] ]); - $messageParameters = $notification->getMessageParameters(); $notification->setRichMessage($l->t('{display_name} ({email}) booked the appointment "{config_display_name}" on {date_time}.'), [ 'display_name' => [ @@ -86,10 +79,32 @@ public function prepare(INotification $notification, string $languageCode): INot ] ]); break; + case 'proposal_response': + $parameters = $notification->getSubjectParameters(); + $link = $this->url->linkToRouteAbsolute('calendar.view.index'); + $richParams = [ + 'proposal' => [ + 'type' => 'highlight', + 'id' => (string)$parameters['id'], + 'name' => (string)$parameters['name'], + 'link' => $link, + ], + 'participant' => [ + 'type' => 'highlight', + 'id' => (string)$parameters['participantId'], + 'name' => (string)$parameters['participantName'], + ], + ]; + $notification->setIcon($this->url->getAbsoluteURL($this->url->imagePath('calendar', 'calendar.png'))); + $notification->setParsedSubject( + $l->t('%1$s responded to your meeting proposal "%2$s"', [(string)$parameters['participantName'], (string)$parameters['name']]) + ); + $notification->setRichSubject($l->t('{participant} responded to your meeting proposal {proposal}'), $richParams); + $notification->setLink($link); + break; default: throw new UnknownNotificationException(); } - return $notification; } } diff --git a/lib/Service/Proposal/ProposalService.php b/lib/Service/Proposal/ProposalService.php index f4b49beb48..080b4c883c 100644 --- a/lib/Service/Proposal/ProposalService.php +++ b/lib/Service/Proposal/ProposalService.php @@ -11,6 +11,7 @@ use DateTimeZone; use Exception; +use OCA\Calendar\AppInfo\Application; use OCA\Calendar\Db\ProposalDateMapper; use OCA\Calendar\Db\ProposalMapper; use OCA\Calendar\Db\ProposalParticipantMapper; @@ -41,6 +42,7 @@ use OCP\Mail\Provider\Address; use OCP\Mail\Provider\IManager as IMailManager; use OCP\Mail\Provider\IMessageSend; +use OCP\Notification\IManager as INotificationManager; use Psr\Log\LoggerInterface; use Sabre\VObject\Component\VCalendar; use Sabre\VObject\Component\VEvent; @@ -62,6 +64,7 @@ public function __construct( private IMailer $systemMailManager, private IMailManager $userMailManager, private IManager $calendarManager, + private INotificationManager $notificationManager, ) { } @@ -449,6 +452,20 @@ public function storeResponse(ProposalResponseObject $response): void { // update participant status to responded $participantEntry->setStatus(ProposalParticipantStatus::Responded->value); $this->proposalParticipantMapper->update($participantEntry); + + // notify the organiser with a bell notification + $notification = $this->notificationManager->createNotification(); + $notification->setApp(Application::APP_ID) + ->setUser($participantEntry->getUid()) + ->setDateTime(new \DateTime()) + ->setObject('proposal', (string)$proposalEntry->getId()) + ->setSubject('proposal_response', [ + 'id' => $proposalEntry->getId(), + 'name' => $proposalEntry->getTitle(), + 'participantId' => $participantEntry->getId(), + 'participantName' => $participantEntry->getName() ?? $participantEntry->getAddress(), + ]); + $this->notificationManager->notify($notification); } private function generateNotifications(IUser $user, ProposalObject $proposal, string $reason): void { diff --git a/tests/php/unit/Notification/NotifierTest.php b/tests/php/unit/Notification/NotifierTest.php index 94ce54c5c0..4995b3bf81 100644 --- a/tests/php/unit/Notification/NotifierTest.php +++ b/tests/php/unit/Notification/NotifierTest.php @@ -168,4 +168,77 @@ public function testPrepare(): void { $return = $this->notifier->prepare($notification, 'de'); $this->assertEquals($notification, $return); } + + public function testPrepareProposalResponse(): void { + /** @var INotification|MockObject $notification */ + $notification = $this->createMock(INotification::class); + + $parameters = [ + 'id' => 123, + 'name' => 'Team Sync', + 'participantId' => 456, + 'participantName' => 'Alice', + ]; + + $richParams = [ + 'proposal' => [ + 'type' => 'highlight', + 'id' => '123', + 'name' => 'Team Sync', + 'link' => 'link/to/calendar', + ], + 'participant' => [ + 'type' => 'highlight', + 'id' => '456', + 'name' => 'Alice', + ], + ]; + + $this->url->expects($this->once()) + ->method('linkToRouteAbsolute') + ->with('calendar.view.index') + ->willReturn('link/to/calendar'); + $this->url->expects($this->once()) + ->method('imagePath') + ->with('calendar', 'calendar.png') + ->willReturn('img/calendar/calendar.png'); + $this->url->expects($this->once()) + ->method('getAbsoluteURL') + ->with('img/calendar/calendar.png') + ->willReturn('https://cloud.example.com/img/calendar/calendar.png'); + + $notification->expects($this->once()) + ->method('getApp') + ->willReturn('calendar'); + $notification->expects($this->once()) + ->method('getSubject') + ->willReturn('proposal_response'); + $notification->expects($this->once()) + ->method('getSubjectParameters') + ->willReturn($parameters); + $this->factory->expects($this->once()) + ->method('get') + ->with('calendar', 'fr') + ->willReturn($this->l); + + $notification->expects($this->once()) + ->method('setIcon') + ->with('https://cloud.example.com/img/calendar/calendar.png') + ->willReturnSelf(); + $notification->expects($this->once()) + ->method('setParsedSubject') + ->with('Alice responded to your meeting proposal "Team Sync"') + ->willReturnSelf(); + $notification->expects($this->once()) + ->method('setRichSubject') + ->with('{participant} responded to your meeting proposal {proposal}', $richParams) + ->willReturnSelf(); + $notification->expects($this->once()) + ->method('setLink') + ->with('link/to/calendar') + ->willReturnSelf(); + + $return = $this->notifier->prepare($notification, 'fr'); + $this->assertEquals($notification, $return); + } }