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
2 changes: 2 additions & 0 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@
'requirements' => ['apiVersion' => '(1.0)', 'collectiveId' => '\d+']],
['name' => 'collectiveUserSettings#setFavoritePages', 'url' => '/api/v{apiVersion}/collectives/{collectiveId}/userSettings/favoritePages', 'verb' => 'PUT',
'requirements' => ['apiVersion' => '(1.0)', 'collectiveId' => '\d+']],
['name' => 'collectiveUserSettings#setNotify', 'url' => '/api/v{apiVersion}/collectives/{collectiveId}/userSettings/notify', 'verb' => 'PUT',
'requirements' => ['apiVersion' => '(1.0)', 'collectiveId' => '\d+']],

// Session API
['name' => 'session#create', 'url' => '/api/v{apiVersion}/collectives/{collectiveId}/sessions', 'verb' => 'POST',
Expand Down
3 changes: 3 additions & 0 deletions lib/AppInfo/Application.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
use OCA\Collectives\Middleware\PublicOCSMiddleware;
use OCA\Collectives\Mount\CollectiveFolderManager;
use OCA\Collectives\Mount\MountProvider;
use OCA\Collectives\Notification\Notifier;
use OCA\Collectives\Reference\SearchablePageReferenceProvider;
use OCA\Collectives\Search\CollectiveProvider;
use OCA\Collectives\Search\PageContentProvider;
Expand Down Expand Up @@ -82,6 +83,8 @@ public function register(IRegistrationContext $context): void {
$context->registerEventListener(RenderReferenceEvent::class, CollectivesReferenceListener::class);
$context->registerEventListener(MentionEvent::class, TextMentionListener::class);

$context->registerNotifierService(Notifier::class);

$context->registerMiddleware(PublicOCSMiddleware::class);

$context->registerService(MountProvider::class, fn (ContainerInterface $c) => new MountProvider(
Expand Down
24 changes: 24 additions & 0 deletions lib/Controller/CollectiveUserSettingsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -131,4 +131,28 @@ public function setFavoritePages(int $collectiveId, string $favoritePages): Data
}, $this->logger);
return new DataResponse([]);
}

/**
* Set whether the user wants notifications about changes in this collective
*
* @param int $collectiveId ID of the collective
* @param int $notify Notification level (0=off, 1=mentions only, 2=all changes)
*
* @return DataResponse<Http::STATUS_OK, list<empty>, array{}>
* @throws OCSNotFoundException Collective not found
* @throws OCSForbiddenException Not permitted
*
* 200: notify level was set
*/
#[NoAdminRequired]
public function setNotify(int $collectiveId, int $notify): DataResponse {
$this->handleErrorResponse(function () use ($collectiveId, $notify): void {
$this->service->setNotify(
$collectiveId,
$this->getUid(),
$notify
);
}, $this->logger);
return new DataResponse([]);
}
}
10 changes: 10 additions & 0 deletions lib/Db/Collective.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ class Collective extends Entity implements JsonSerializable {
protected bool $userShowMembers = Collective::defaultShowMembers;
protected bool $userShowRecentPages = Collective::defaultShowRecentPages;
protected array $userFavoritePages = [];
protected int $userNotify = CollectiveUserSettings::NOTIFY_MENTION;
protected bool $canLeave = false;

public function getCircleId(): string {
Expand Down Expand Up @@ -233,6 +234,14 @@ public function setUserFavoritePages(array $userFavoritePages): void {
$this->userFavoritePages = $userFavoritePages;
}

public function getUserNotify(): int {
return $this->userNotify;
}

public function setUserNotify(int $userNotify): void {
$this->userNotify = $userNotify;
}

public function getUserPermissions(bool $isShare = false): int {
// Public shares always get permissions of a simple member plus sharing permission of owner
if ($isShare) {
Expand Down Expand Up @@ -309,6 +318,7 @@ public function jsonSerialize(): array {
'userShowMembers' => $this->userShowMembers,
'userShowRecentPages' => $this->userShowRecentPages,
'userFavoritePages' => $this->userFavoritePages,
'userNotify' => $this->userNotify,
'canLeave' => $this->getCanLeave(),
];
}
Expand Down
17 changes: 17 additions & 0 deletions lib/Db/CollectiveUserSettings.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,14 @@ class CollectiveUserSettings extends Entity implements JsonSerializable {
'show_members',
'show_recent_pages',
'favorite_pages',
'notify',
];

public const NOTIFY_OFF = 0;
public const NOTIFY_MENTION = 1;
public const NOTIFY_ALL = 2;
private const NOTIFY_LEVELS = [self::NOTIFY_OFF, self::NOTIFY_MENTION, self::NOTIFY_ALL];

protected ?int $collectiveId = null;
protected ?string $userId = null;
protected int $pageOrder = Collective::defaultPageOrder;
Expand Down Expand Up @@ -106,6 +112,17 @@ public function setFavoritePages(array $favoritePages): void {
$this->setSetting('favorite_pages', $favoritePages);
}

/**
* @throws NotPermittedException
* @throws JsonException
*/
public function setNotify(int $notify): void {
if (!in_array($notify, self::NOTIFY_LEVELS, true)) {
throw new NotPermittedException('Invalid notify value: ' . $notify);
}
$this->setSetting('notify', $notify);
}

public function jsonSerialize(): array {
return [
'id' => $this->id,
Expand Down
15 changes: 12 additions & 3 deletions lib/Listeners/NodeWrittenListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,22 @@
use OCA\Collectives\Db\PageLinkMapper;
use OCA\Collectives\Fs\MarkdownHelper;
use OCA\Collectives\Mount\CollectiveStorage;
use OCA\Collectives\Service\PageService;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\Files\Events\Node\NodeWrittenEvent;
use OCP\Files\File;
use OCP\IConfig;
use OCP\IUserSession;

/** @template-implements IEventListener<Event|NodeWrittenEvent> */
class NodeWrittenListener implements IEventListener {
public function __construct(
private IConfig $config,
private PageLinkMapper $pageLinkMapper,
private CollectiveMapper $collectiveMapper,
private readonly IConfig $config,
private readonly PageLinkMapper $pageLinkMapper,
private readonly CollectiveMapper $collectiveMapper,
private readonly IUserSession $userSession,
private readonly PageService $pageService,
) {
}

Expand All @@ -46,5 +50,10 @@ public function handle(Event $event): void {

$linkedPageIds = MarkdownHelper::getLinkedPageIds($collective, $node->getContent(), $this->config->getSystemValue('trusted_domains', []));
$this->pageLinkMapper->updateByPageId($node->getId(), $linkedPageIds);

$userId = $this->userSession->getUser()?->getUID();
if ($userId) {
$this->pageService->notifyContentChange($node, $collective, $userId);
}
}
}
13 changes: 13 additions & 0 deletions lib/Listeners/TextMentionListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

namespace OCA\Collectives\Listeners;

use OCA\Collectives\Db\CollectiveUserSettings;
use OCA\Collectives\Db\CollectiveUserSettingsMapper;
use OCA\Collectives\Mount\CollectiveMountPoint;
use OCA\Collectives\Service\CollectiveService;
use OCA\Collectives\Service\PageService;
Expand All @@ -17,6 +19,7 @@
use OCP\EventDispatcher\IEventListener;
use OCP\IL10N;
use OCP\IURLGenerator;
use OCP\Notification\AlreadyProcessedException;

/** @template-implements IEventListener<Event|MentionEvent> */
class TextMentionListener implements IEventListener {
Expand All @@ -25,6 +28,7 @@ public function __construct(
private IURLGenerator $urlGenerator,
private CollectiveService $collectiveService,
private PageService $pageService,
private CollectiveUserSettingsMapper $settingsMapper,
private ?string $userId,
) {
}
Expand All @@ -44,6 +48,15 @@ public function handle(Event $event): void {
}

$collective = $this->collectiveService->getCollective($mountPoint->getFolderId(), $this->userId);

// Skip notification if mentioned user has notifications turned off
$mentionedUserId = $event->getNotification()->getUser();
$settings = $this->settingsMapper->findByCollectiveAndUser($collective->getId(), $mentionedUserId);
$notifyLevel = ($settings?->getSetting('notify')) ?? CollectiveUserSettings::NOTIFY_MENTION;
if ($notifyLevel < CollectiveUserSettings::NOTIFY_MENTION) {
throw new AlreadyProcessedException();
}

$pageInfo = $this->pageService->findByFile($mountPoint->getFolderId(), $event->getFile(), $this->userId);

$collectiveLink = $this->urlGenerator->linkToRouteAbsolute('collectives.start.index') . rawurlencode($collective->getName());
Expand Down
121 changes: 121 additions & 0 deletions lib/Notification/Notifier.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<?php

declare(strict_types=1);

/**
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

namespace OCA\Collectives\Notification;

use OCA\Collectives\AppInfo\Application;
use OCP\IURLGenerator;
use OCP\IUserManager;
use OCP\L10N\IFactory;
use OCP\Notification\INotification;
use OCP\Notification\INotifier;
use OCP\Notification\UnknownNotificationException;

class Notifier implements INotifier {
public const SUBJECT_PAGE_UPDATED = 'page_updated';
public const SUBJECT_PAGE_DELETED = 'page_deleted';

public function __construct(
private IFactory $factory,
private IURLGenerator $urlGenerator,
private IUserManager $userManager,
) {
}

public function getId(): string {
return Application::APP_NAME;
}

public function getName(): string {
return $this->factory->get(Application::APP_NAME)->t('Collectives');
}

private function setParsedSubjectFromRichSubject(INotification $notification): void {
$placeholders = $replacements = [];
foreach ($notification->getRichSubjectParameters() as $key => $value) {
$placeholders[] = '{' . $key . '}';
$replacements[] = $value['name'] ?? '';
}
$notification->setParsedSubject(
str_replace($placeholders, $replacements, $notification->getParsedSubject())
);
}

public function prepare(INotification $notification, string $languageCode): INotification {
if ($notification->getApp() !== Application::APP_NAME) {
throw new UnknownNotificationException();
}

$l = $this->factory->get(Application::APP_NAME, $languageCode);
$params = $notification->getSubjectParameters();

$actingUser = $params['actingUser'];
$actingDisplayName = $this->userManager->getDisplayName($actingUser) ?? $actingUser;

$collectiveRichObject = [
'type' => 'highlight',
'id' => $params['collectiveId'],
'name' => $params['collectiveName'],
'link' => $params['collectiveLink'],
];

$pageRichObject = [
'type' => 'highlight',
'id' => $params['pageId'],
'name' => $params['pageTitle'],
];
if (!empty($params['pageLink'])) {
$pageRichObject['link'] = $params['pageLink'];
}

$userRichObject = [
'type' => 'user',
'id' => $actingUser,
'name' => $actingDisplayName,
];

$richParams = [
'user' => $userRichObject,
'collective' => $collectiveRichObject,
'page' => $pageRichObject,
];

$notification->setIcon(
$this->urlGenerator->getAbsoluteURL(
$this->urlGenerator->imagePath('collectives', 'collectives-dark.svg')
)
);

if (!empty($params['pageLink'])) {
$notification->setLink($params['pageLink']);
} else {
$notification->setLink($params['collectiveLink']);
}

switch ($notification->getSubject()) {
case self::SUBJECT_PAGE_UPDATED:
$notification->setRichSubject(
$l->t('{user} updated {page} in {collective}'),
$richParams,
);
break;
case self::SUBJECT_PAGE_DELETED:
$notification->setRichSubject(
$l->t('{user} deleted {page} from {collective}'),
$richParams,
);
break;
default:
throw new UnknownNotificationException();
}

$this->setParsedSubjectFromRichSubject($notification);
return $notification;
}
}
2 changes: 2 additions & 0 deletions lib/Service/CollectiveHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

use OCA\Collectives\Db\Collective;
use OCA\Collectives\Db\CollectiveMapper;
use OCA\Collectives\Db\CollectiveUserSettings;
use OCA\Collectives\Db\CollectiveUserSettingsMapper;

class CollectiveHelper {
Expand Down Expand Up @@ -49,6 +50,7 @@ public function getCollectivesForUser(string $userId, bool $getLevel = true, boo
$c->setUserShowMembers(($settings ? $settings->getSetting('show_members') : null) ?? Collective::defaultShowMembers);
$c->setUserShowRecentPages(($settings ? $settings->getSetting('show_recent_pages') : null) ?? Collective::defaultShowRecentPages);
$c->setUserFavoritePages(($settings ? $settings->getSetting('favorite_pages') : null) ?? []);
$c->setUserNotify(($settings ? $settings->getSetting('notify') : null) ?? CollectiveUserSettings::NOTIFY_MENTION);
}
}
return $collectives;
Expand Down
15 changes: 15 additions & 0 deletions lib/Service/CollectiveUserSettingsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,19 @@ public function setFavoritePages(int $collectiveId, string $userId, string $favo
throw new NotPermittedException($e->getMessage(), 0, $e);
}
}

/**
* @throws NotFoundException
* @throws NotPermittedException
*/
public function setNotify(int $collectiveId, string $userId, int $notify): void {
$settings = $this->initSettings($collectiveId, $userId);
$settings->setNotify($notify);

try {
$this->collectiveUserSettingsMapper->insertOrUpdate($settings);
} catch (Exception $e) {
throw new NotPermittedException($e->getMessage(), 0, $e);
}
}
}
Loading
Loading