diff --git a/appinfo/info.xml b/appinfo/info.xml
index c5034914a5c..ce22dac5020 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -18,7 +18,7 @@
* 🌉 **Sync with other chat solutions** With [Matterbridge](https://github.com/42wim/matterbridge/) being integrated in Talk, you can easily sync a lot of other chat solutions to Nextcloud Talk and vice-versa.
]]>
- 24.0.0-dev.2
+ 24.0.0-dev.3
agpl
Anna Larch
diff --git a/docs/capabilities.md b/docs/capabilities.md
index 58f2ea9d908..6d1a1f0d3e8 100644
--- a/docs/capabilities.md
+++ b/docs/capabilities.md
@@ -226,3 +226,4 @@
* `config => conversations => group-mode` (local) - User selected grouping mode for conversations (`none`, `group-first` or `private-first`)
* `private-reply` - Whether clients can link the original message to a private reply in one-to-one conversations
* `config => attachments => conversation-subfolders` (local) - Whether per-conversation subfolders are used for Talk attachments; when `true` files must be uploaded to `Talk/-/-/` before calling the attachment endpoint
+* `mute-conversations` - Whether conversations can be muted for a given time
diff --git a/lib/Capabilities.php b/lib/Capabilities.php
index db47841106c..8856d7f2274 100644
--- a/lib/Capabilities.php
+++ b/lib/Capabilities.php
@@ -132,6 +132,7 @@ class Capabilities implements IPublicCapability {
'scheduled-messages',
'conversation-presets',
'private-reply',
+ 'mute-conversations',
];
public const CONDITIONAL_FEATURES = [
diff --git a/lib/Chat/Notifier.php b/lib/Chat/Notifier.php
index 30f35f3b93c..a1fb24952a8 100644
--- a/lib/Chat/Notifier.php
+++ b/lib/Chat/Notifier.php
@@ -692,6 +692,10 @@ protected function shouldMentionedUserBeNotified(string $userId, IComment $comme
return self::PRIORITY_NONE;
}
+ if ($attendee->getMuteUntil() >= $this->timeFactory->getTime()) {
+ return self::PRIORITY_NONE;
+ }
+
$notificationLevel = $attendee->getNotificationLevel();
$threadId = (int)$comment->getTopmostParentId();
if ($threadId !== 0) {
@@ -768,6 +772,10 @@ protected function shouldParticipantBeNotified(Participant $participant, ICommen
return self::PRIORITY_NONE;
}
+ if ($participant->getAttendee()->getMuteUntil() >= $this->timeFactory->getTime()) {
+ return self::PRIORITY_NONE;
+ }
+
if ($participant->getAttendee()->isImportant()) {
return self::PRIORITY_IMPORTANT;
}
diff --git a/lib/Controller/RoomController.php b/lib/Controller/RoomController.php
index 1e1e9ca838d..3c32552239f 100644
--- a/lib/Controller/RoomController.php
+++ b/lib/Controller/RoomController.php
@@ -3238,4 +3238,54 @@ public function scheduleMeeting(string $calendarUri, int $start, ?array $attende
return new DataResponse(null, Http::STATUS_OK);
}
+
+ /**
+ * Mute all notifications in a conversation until a specific time.
+ * Does not alter notification settings for the attendee.
+ *
+ * Required capability: `mute-conversations`
+ *
+ * @param int $muteUntil Unix timestamp until notifications are muted
+ * @return DataResponse|DataResponse
+ *
+ * 200: Conversation muted
+ * 400: Timestamp is in the past
+ */
+ #[NoAdminRequired]
+ #[FederationSupported]
+ #[RequireLoggedInParticipant]
+ #[ApiRoute(verb: 'POST', url: '/api/{apiVersion}/room/{token}/mute', requirements: [
+ 'apiVersion' => '(v4)',
+ 'token' => '[a-z0-9]{4,30}',
+ ])]
+ public function muteConversation(int $muteUntil): DataResponse {
+ if ($muteUntil <= $this->timeFactory->getTime()) {
+ return new DataResponse(['error' => 'mute-until'], Http::STATUS_BAD_REQUEST);
+ }
+
+ $this->participantService->setMuteUntil($this->participant, $muteUntil);
+ return new DataResponse($this->formatRoom($this->room, $this->participant));
+ }
+
+ /**
+ * Unmute all notifications in a conversation, when they were muted before.
+ * Does not alter notification settings for the attendee.
+ *
+ * Required capability: `mute-conversations`
+ *
+ * @return DataResponse
+ *
+ * 200: Conversation unmuted
+ */
+ #[NoAdminRequired]
+ #[FederationSupported]
+ #[RequireLoggedInParticipant]
+ #[ApiRoute(verb: 'DELETE', url: '/api/{apiVersion}/room/{token}/mute', requirements: [
+ 'apiVersion' => '(v4)',
+ 'token' => '[a-z0-9]{4,30}',
+ ])]
+ public function unmuteConversation(): DataResponse {
+ $this->participantService->setMuteUntil($this->participant, 0);
+ return new DataResponse($this->formatRoom($this->room, $this->participant));
+ }
}
diff --git a/lib/Migration/Version24000Date20260419190830.php b/lib/Migration/Version24000Date20260419190830.php
new file mode 100644
index 00000000000..2feae38211a
--- /dev/null
+++ b/lib/Migration/Version24000Date20260419190830.php
@@ -0,0 +1,42 @@
+getTable('talk_attendees');
+ if (!$table->hasColumn('mute_until')) {
+ $table->addColumn('mute_until', Types::BIGINT, [
+ 'notnull' => true,
+ 'default' => 0,
+ ]);
+ }
+
+ return $schema;
+ }
+}
diff --git a/lib/Model/Attendee.php b/lib/Model/Attendee.php
index 75cb0013f72..c56c8185efb 100644
--- a/lib/Model/Attendee.php
+++ b/lib/Model/Attendee.php
@@ -74,6 +74,8 @@
* @method int getHiddenPinnedId()
* @method void setHasScheduledMessages(int $scheduledMessages)
* @method int getHasScheduledMessages()
+ * @method void setMuteUntil(int $muteUntil)
+ * @method int getMuteUntil()
*/
class Attendee extends Entity {
public const ACTOR_USERS = 'users';
@@ -150,6 +152,7 @@ class Attendee extends Entity {
protected bool $hasUnreadThreadDirects = false;
protected int $hiddenPinnedId = 0;
protected int $hasScheduledMessages = 0;
+ protected int $muteUntil = 0;
public function __construct() {
$this->addType('roomId', Types::BIGINT);
@@ -183,7 +186,7 @@ public function __construct() {
$this->addType('hasUnreadThreadDirects', Types::BOOLEAN);
$this->addType('hiddenPinnedId', Types::BIGINT);
$this->addType('hasScheduledMessages', Types::INTEGER);
-
+ $this->addType('muteUntil', Types::BIGINT);
}
public function getDisplayName(): string {
diff --git a/lib/Model/AttendeeMapper.php b/lib/Model/AttendeeMapper.php
index f1715b06d1e..fd6aebaa1c0 100644
--- a/lib/Model/AttendeeMapper.php
+++ b/lib/Model/AttendeeMapper.php
@@ -316,6 +316,7 @@ public function createAttendeeFromRow(array $row): Attendee {
'has_unread_thread_directs' => (bool)$row['has_unread_thread_directs'],
'hidden_pinned_id' => (int)$row['hidden_pinned_id'],
'has_scheduled_messages' => (int)$row['has_scheduled_messages'],
+ 'mute_until' => (int)$row['mute_until'],
]);
}
}
diff --git a/lib/Model/SelectHelper.php b/lib/Model/SelectHelper.php
index 6ccc084f84f..9be81cc9a4e 100644
--- a/lib/Model/SelectHelper.php
+++ b/lib/Model/SelectHelper.php
@@ -114,6 +114,7 @@ public function selectAttendeesTable(IQueryBuilder $query, string $alias = 'a'):
$alias . 'has_unread_thread_directs',
$alias . 'hidden_pinned_id',
$alias . 'has_scheduled_messages',
+ $alias . 'mute_until',
])->selectAlias($alias . 'id', 'a_id');
}
diff --git a/lib/ResponseDefinitions.php b/lib/ResponseDefinitions.php
index 6f1a8e00aec..041f380071c 100644
--- a/lib/ResponseDefinitions.php
+++ b/lib/ResponseDefinitions.php
@@ -570,6 +570,8 @@
* hasScheduledMessages: int,
* // Bit-flag of enabled attributes of this conversation (only available with capability: `conversation-attributes`). See [attributes list](https://nextcloud-talk.readthedocs.io/en/latest/constants/#conversation-attributes) for details
* attributes: int,
+ * // Required capability: `mute-conversations`. Timestamp until the conversation is muted, i.e. not receiving notifications
+ * muteUntil: int,
* }
*
* @psalm-type TalkDashboardEventAttachment = array{
diff --git a/lib/Service/ParticipantService.php b/lib/Service/ParticipantService.php
index b0dc6c87f10..dfd03443b39 100644
--- a/lib/Service/ParticipantService.php
+++ b/lib/Service/ParticipantService.php
@@ -382,6 +382,13 @@ public function hidePinnedMessage(Participant $participant, int $messagesId): vo
$this->attendeeMapper->update($attendee);
}
+ public function setMuteUntil(Participant $participant, int $muteUntil): void {
+ $attendee = $participant->getAttendee();
+ $attendee->setMuteUntil($muteUntil);
+ $attendee->setLastAttendeeActivity($this->timeFactory->getTime());
+ $this->attendeeMapper->update($attendee);
+ }
+
/**
* @param RoomService $roomService
* @param Room $room
@@ -2058,6 +2065,7 @@ public function getParticipantUsersForCallNotifications(Room $room): array {
->where($query->expr()->eq('a.room_id', $query->createNamedParameter($room->getId(), IQueryBuilder::PARAM_INT)))
->andWhere($query->expr()->eq('a.actor_type', $query->createNamedParameter(Attendee::ACTOR_USERS)))
->andWhere($query->expr()->eq('a.notification_calls', $query->createNamedParameter(Participant::NOTIFY_CALLS_ON)))
+ ->andWhere($query->expr()->lte('a.mute_until', $query->createNamedParameter($this->timeFactory->getTime(), IQueryBuilder::PARAM_INT)))
->andWhere($query->expr()->isNull('s.in_call'));
if ($room->getLobbyState() !== Webinary::LOBBY_NONE) {
diff --git a/lib/Service/RoomFormatter.php b/lib/Service/RoomFormatter.php
index 2af9f20aa0e..92f875d7260 100644
--- a/lib/Service/RoomFormatter.php
+++ b/lib/Service/RoomFormatter.php
@@ -160,6 +160,7 @@ public function formatRoomV4(
'isSensitive' => false,
'hasScheduledMessages' => 0,
'attributes' => 0,
+ 'muteUntil' => 0,
];
if ($room->isFederatedConversation()) {
@@ -251,6 +252,7 @@ public function formatRoomV4(
'lastPinnedId' => $room->getLastPinnedId(),
'hiddenPinnedId' => $attendee->getHiddenPinnedId(),
'attributes' => $room->getAttributes(),
+ 'muteUntil' => $attendee->getMuteUntil(),
]);
if ($room->isFederatedConversation()) {
diff --git a/openapi-backend-sipbridge.json b/openapi-backend-sipbridge.json
index c23432e87eb..50628184c0c 100644
--- a/openapi-backend-sipbridge.json
+++ b/openapi-backend-sipbridge.json
@@ -941,7 +941,8 @@
"lastPinnedId",
"hiddenPinnedId",
"hasScheduledMessages",
- "attributes"
+ "attributes",
+ "muteUntil"
],
"properties": {
"actorId": {
@@ -1255,6 +1256,11 @@
"type": "integer",
"format": "int64",
"description": "Bit-flag of enabled attributes of this conversation (only available with capability: `conversation-attributes`). See [attributes list](https://nextcloud-talk.readthedocs.io/en/latest/constants/#conversation-attributes) for details"
+ },
+ "muteUntil": {
+ "type": "integer",
+ "format": "int64",
+ "description": "Required capability: `mute-conversations`. Timestamp until the conversation is muted, i.e. not receiving notifications"
}
}
},
diff --git a/openapi-federation.json b/openapi-federation.json
index 1f99fd6b5da..377569a4136 100644
--- a/openapi-federation.json
+++ b/openapi-federation.json
@@ -1006,7 +1006,8 @@
"lastPinnedId",
"hiddenPinnedId",
"hasScheduledMessages",
- "attributes"
+ "attributes",
+ "muteUntil"
],
"properties": {
"actorId": {
@@ -1320,6 +1321,11 @@
"type": "integer",
"format": "int64",
"description": "Bit-flag of enabled attributes of this conversation (only available with capability: `conversation-attributes`). See [attributes list](https://nextcloud-talk.readthedocs.io/en/latest/constants/#conversation-attributes) for details"
+ },
+ "muteUntil": {
+ "type": "integer",
+ "format": "int64",
+ "description": "Required capability: `mute-conversations`. Timestamp until the conversation is muted, i.e. not receiving notifications"
}
}
},
diff --git a/openapi-full.json b/openapi-full.json
index cd620ec092c..4f6e233b8f6 100644
--- a/openapi-full.json
+++ b/openapi-full.json
@@ -1990,7 +1990,8 @@
"lastPinnedId",
"hiddenPinnedId",
"hasScheduledMessages",
- "attributes"
+ "attributes",
+ "muteUntil"
],
"properties": {
"actorId": {
@@ -2304,6 +2305,11 @@
"type": "integer",
"format": "int64",
"description": "Bit-flag of enabled attributes of this conversation (only available with capability: `conversation-attributes`). See [attributes list](https://nextcloud-talk.readthedocs.io/en/latest/constants/#conversation-attributes) for details"
+ },
+ "muteUntil": {
+ "type": "integer",
+ "format": "int64",
+ "description": "Required capability: `mute-conversations`. Timestamp until the conversation is muted, i.e. not receiving notifications"
}
}
},
@@ -25587,6 +25593,287 @@
}
}
},
+ "/ocs/v2.php/apps/spreed/api/{apiVersion}/room/{token}/mute": {
+ "post": {
+ "operationId": "room-mute-conversation",
+ "summary": "Mute all notifications in a conversation until a specific time. Does not alter notification settings for the attendee.",
+ "description": "Required capability: `mute-conversations`",
+ "tags": [
+ "room"
+ ],
+ "security": [
+ {
+ "bearer_auth": []
+ },
+ {
+ "basic_auth": []
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "muteUntil"
+ ],
+ "properties": {
+ "muteUntil": {
+ "type": "integer",
+ "format": "int64",
+ "description": "Unix timestamp until notifications are muted"
+ }
+ }
+ }
+ }
+ }
+ },
+ "parameters": [
+ {
+ "name": "apiVersion",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "enum": [
+ "v4"
+ ],
+ "default": "v4"
+ }
+ },
+ {
+ "name": "token",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "pattern": "^[a-z0-9]{4,30}$"
+ }
+ },
+ {
+ "name": "OCS-APIRequest",
+ "in": "header",
+ "description": "Required to be true for the API request to pass",
+ "required": true,
+ "schema": {
+ "type": "boolean",
+ "default": true
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Conversation muted",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "$ref": "#/components/schemas/Room"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Timestamp is in the past",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "type": "object",
+ "required": [
+ "error"
+ ],
+ "properties": {
+ "error": {
+ "type": "string",
+ "enum": [
+ "mute-until"
+ ]
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Current user is not logged in",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {}
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "delete": {
+ "operationId": "room-unmute-conversation",
+ "summary": "Unmute all notifications in a conversation, when they were muted before. Does not alter notification settings for the attendee.",
+ "description": "Required capability: `mute-conversations`",
+ "tags": [
+ "room"
+ ],
+ "security": [
+ {
+ "bearer_auth": []
+ },
+ {
+ "basic_auth": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "apiVersion",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "enum": [
+ "v4"
+ ],
+ "default": "v4"
+ }
+ },
+ {
+ "name": "token",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "pattern": "^[a-z0-9]{4,30}$"
+ }
+ },
+ {
+ "name": "OCS-APIRequest",
+ "in": "header",
+ "description": "Required to be true for the API request to pass",
+ "required": true,
+ "schema": {
+ "type": "boolean",
+ "default": true
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Conversation unmuted",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "$ref": "#/components/schemas/Room"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Current user is not logged in",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {}
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"/ocs/v2.php/apps/spreed/api/{apiVersion}/settings/user": {
"post": {
"operationId": "settings-set-user-setting",
diff --git a/openapi.json b/openapi.json
index d80abd78355..e8898b71b0f 100644
--- a/openapi.json
+++ b/openapi.json
@@ -1878,7 +1878,8 @@
"lastPinnedId",
"hiddenPinnedId",
"hasScheduledMessages",
- "attributes"
+ "attributes",
+ "muteUntil"
],
"properties": {
"actorId": {
@@ -2192,6 +2193,11 @@
"type": "integer",
"format": "int64",
"description": "Bit-flag of enabled attributes of this conversation (only available with capability: `conversation-attributes`). See [attributes list](https://nextcloud-talk.readthedocs.io/en/latest/constants/#conversation-attributes) for details"
+ },
+ "muteUntil": {
+ "type": "integer",
+ "format": "int64",
+ "description": "Required capability: `mute-conversations`. Timestamp until the conversation is muted, i.e. not receiving notifications"
}
}
},
@@ -25475,6 +25481,287 @@
}
}
},
+ "/ocs/v2.php/apps/spreed/api/{apiVersion}/room/{token}/mute": {
+ "post": {
+ "operationId": "room-mute-conversation",
+ "summary": "Mute all notifications in a conversation until a specific time. Does not alter notification settings for the attendee.",
+ "description": "Required capability: `mute-conversations`",
+ "tags": [
+ "room"
+ ],
+ "security": [
+ {
+ "bearer_auth": []
+ },
+ {
+ "basic_auth": []
+ }
+ ],
+ "requestBody": {
+ "required": true,
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "muteUntil"
+ ],
+ "properties": {
+ "muteUntil": {
+ "type": "integer",
+ "format": "int64",
+ "description": "Unix timestamp until notifications are muted"
+ }
+ }
+ }
+ }
+ }
+ },
+ "parameters": [
+ {
+ "name": "apiVersion",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "enum": [
+ "v4"
+ ],
+ "default": "v4"
+ }
+ },
+ {
+ "name": "token",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "pattern": "^[a-z0-9]{4,30}$"
+ }
+ },
+ {
+ "name": "OCS-APIRequest",
+ "in": "header",
+ "description": "Required to be true for the API request to pass",
+ "required": true,
+ "schema": {
+ "type": "boolean",
+ "default": true
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Conversation muted",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "$ref": "#/components/schemas/Room"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "400": {
+ "description": "Timestamp is in the past",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "type": "object",
+ "required": [
+ "error"
+ ],
+ "properties": {
+ "error": {
+ "type": "string",
+ "enum": [
+ "mute-until"
+ ]
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Current user is not logged in",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {}
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "delete": {
+ "operationId": "room-unmute-conversation",
+ "summary": "Unmute all notifications in a conversation, when they were muted before. Does not alter notification settings for the attendee.",
+ "description": "Required capability: `mute-conversations`",
+ "tags": [
+ "room"
+ ],
+ "security": [
+ {
+ "bearer_auth": []
+ },
+ {
+ "basic_auth": []
+ }
+ ],
+ "parameters": [
+ {
+ "name": "apiVersion",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "enum": [
+ "v4"
+ ],
+ "default": "v4"
+ }
+ },
+ {
+ "name": "token",
+ "in": "path",
+ "required": true,
+ "schema": {
+ "type": "string",
+ "pattern": "^[a-z0-9]{4,30}$"
+ }
+ },
+ {
+ "name": "OCS-APIRequest",
+ "in": "header",
+ "description": "Required to be true for the API request to pass",
+ "required": true,
+ "schema": {
+ "type": "boolean",
+ "default": true
+ }
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Conversation unmuted",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {
+ "$ref": "#/components/schemas/Room"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "401": {
+ "description": "Current user is not logged in",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "ocs"
+ ],
+ "properties": {
+ "ocs": {
+ "type": "object",
+ "required": [
+ "meta",
+ "data"
+ ],
+ "properties": {
+ "meta": {
+ "$ref": "#/components/schemas/OCSMeta"
+ },
+ "data": {}
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"/ocs/v2.php/apps/spreed/api/{apiVersion}/settings/user": {
"post": {
"operationId": "settings-set-user-setting",
diff --git a/src/types/openapi/openapi-backend-sipbridge.ts b/src/types/openapi/openapi-backend-sipbridge.ts
index 4fa17d36ce1..6f203a17002 100644
--- a/src/types/openapi/openapi-backend-sipbridge.ts
+++ b/src/types/openapi/openapi-backend-sipbridge.ts
@@ -782,6 +782,11 @@ export type components = {
* @description Bit-flag of enabled attributes of this conversation (only available with capability: `conversation-attributes`). See [attributes list](https://nextcloud-talk.readthedocs.io/en/latest/constants/#conversation-attributes) for details
*/
attributes: number;
+ /**
+ * Format: int64
+ * @description Required capability: `mute-conversations`. Timestamp until the conversation is muted, i.e. not receiving notifications
+ */
+ muteUntil: number;
};
RoomLastMessage: components["schemas"]["ChatMessage"] | components["schemas"]["ChatProxyMessage"];
};
diff --git a/src/types/openapi/openapi-federation.ts b/src/types/openapi/openapi-federation.ts
index 7160990adda..ed6246bae83 100644
--- a/src/types/openapi/openapi-federation.ts
+++ b/src/types/openapi/openapi-federation.ts
@@ -826,6 +826,11 @@ export type components = {
* @description Bit-flag of enabled attributes of this conversation (only available with capability: `conversation-attributes`). See [attributes list](https://nextcloud-talk.readthedocs.io/en/latest/constants/#conversation-attributes) for details
*/
attributes: number;
+ /**
+ * Format: int64
+ * @description Required capability: `mute-conversations`. Timestamp until the conversation is muted, i.e. not receiving notifications
+ */
+ muteUntil: number;
};
RoomLastMessage: components["schemas"]["ChatMessage"] | components["schemas"]["ChatProxyMessage"];
};
diff --git a/src/types/openapi/openapi-full.ts b/src/types/openapi/openapi-full.ts
index d2ba011e53d..359f4fd8106 100644
--- a/src/types/openapi/openapi-full.ts
+++ b/src/types/openapi/openapi-full.ts
@@ -1782,6 +1782,30 @@ export type paths = {
patch?: never;
trace?: never;
};
+ "/ocs/v2.php/apps/spreed/api/{apiVersion}/room/{token}/mute": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ /**
+ * Mute all notifications in a conversation until a specific time. Does not alter notification settings for the attendee.
+ * @description Required capability: `mute-conversations`
+ */
+ post: operations["room-mute-conversation"];
+ /**
+ * Unmute all notifications in a conversation, when they were muted before. Does not alter notification settings for the attendee.
+ * @description Required capability: `mute-conversations`
+ */
+ delete: operations["room-unmute-conversation"];
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
"/ocs/v2.php/apps/spreed/api/{apiVersion}/settings/user": {
parameters: {
query?: never;
@@ -3581,6 +3605,11 @@ export type components = {
* @description Bit-flag of enabled attributes of this conversation (only available with capability: `conversation-attributes`). See [attributes list](https://nextcloud-talk.readthedocs.io/en/latest/constants/#conversation-attributes) for details
*/
attributes: number;
+ /**
+ * Format: int64
+ * @description Required capability: `mute-conversations`. Timestamp until the conversation is muted, i.e. not receiving notifications
+ */
+ muteUntil: number;
};
RoomLastMessage: components["schemas"]["ChatMessage"] | components["schemas"]["ChatProxyMessage"];
RoomWithInvalidInvitations: components["schemas"]["Room"] & {
@@ -12933,6 +12962,123 @@ export interface operations {
};
};
};
+ "room-mute-conversation": {
+ parameters: {
+ query?: never;
+ header: {
+ /** @description Required to be true for the API request to pass */
+ "OCS-APIRequest": boolean;
+ };
+ path: {
+ apiVersion: "v4";
+ token: string;
+ };
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": {
+ /**
+ * Format: int64
+ * @description Unix timestamp until notifications are muted
+ */
+ muteUntil: number;
+ };
+ };
+ };
+ responses: {
+ /** @description Conversation muted */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: components["schemas"]["Room"];
+ };
+ };
+ };
+ };
+ /** @description Timestamp is in the past */
+ 400: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: {
+ /** @enum {string} */
+ error: "mute-until";
+ };
+ };
+ };
+ };
+ };
+ /** @description Current user is not logged in */
+ 401: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: unknown;
+ };
+ };
+ };
+ };
+ };
+ };
+ "room-unmute-conversation": {
+ parameters: {
+ query?: never;
+ header: {
+ /** @description Required to be true for the API request to pass */
+ "OCS-APIRequest": boolean;
+ };
+ path: {
+ apiVersion: "v4";
+ token: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Conversation unmuted */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: components["schemas"]["Room"];
+ };
+ };
+ };
+ };
+ /** @description Current user is not logged in */
+ 401: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: unknown;
+ };
+ };
+ };
+ };
+ };
+ };
"settings-set-user-setting": {
parameters: {
query?: never;
diff --git a/src/types/openapi/openapi.ts b/src/types/openapi/openapi.ts
index 38444c0a210..16324f78962 100644
--- a/src/types/openapi/openapi.ts
+++ b/src/types/openapi/openapi.ts
@@ -1782,6 +1782,30 @@ export type paths = {
patch?: never;
trace?: never;
};
+ "/ocs/v2.php/apps/spreed/api/{apiVersion}/room/{token}/mute": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ put?: never;
+ /**
+ * Mute all notifications in a conversation until a specific time. Does not alter notification settings for the attendee.
+ * @description Required capability: `mute-conversations`
+ */
+ post: operations["room-mute-conversation"];
+ /**
+ * Unmute all notifications in a conversation, when they were muted before. Does not alter notification settings for the attendee.
+ * @description Required capability: `mute-conversations`
+ */
+ delete: operations["room-unmute-conversation"];
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
"/ocs/v2.php/apps/spreed/api/{apiVersion}/settings/user": {
parameters: {
query?: never;
@@ -3014,6 +3038,11 @@ export type components = {
* @description Bit-flag of enabled attributes of this conversation (only available with capability: `conversation-attributes`). See [attributes list](https://nextcloud-talk.readthedocs.io/en/latest/constants/#conversation-attributes) for details
*/
attributes: number;
+ /**
+ * Format: int64
+ * @description Required capability: `mute-conversations`. Timestamp until the conversation is muted, i.e. not receiving notifications
+ */
+ muteUntil: number;
};
RoomLastMessage: components["schemas"]["ChatMessage"] | components["schemas"]["ChatProxyMessage"];
RoomWithInvalidInvitations: components["schemas"]["Room"] & {
@@ -12366,6 +12395,123 @@ export interface operations {
};
};
};
+ "room-mute-conversation": {
+ parameters: {
+ query?: never;
+ header: {
+ /** @description Required to be true for the API request to pass */
+ "OCS-APIRequest": boolean;
+ };
+ path: {
+ apiVersion: "v4";
+ token: string;
+ };
+ cookie?: never;
+ };
+ requestBody: {
+ content: {
+ "application/json": {
+ /**
+ * Format: int64
+ * @description Unix timestamp until notifications are muted
+ */
+ muteUntil: number;
+ };
+ };
+ };
+ responses: {
+ /** @description Conversation muted */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: components["schemas"]["Room"];
+ };
+ };
+ };
+ };
+ /** @description Timestamp is in the past */
+ 400: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: {
+ /** @enum {string} */
+ error: "mute-until";
+ };
+ };
+ };
+ };
+ };
+ /** @description Current user is not logged in */
+ 401: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: unknown;
+ };
+ };
+ };
+ };
+ };
+ };
+ "room-unmute-conversation": {
+ parameters: {
+ query?: never;
+ header: {
+ /** @description Required to be true for the API request to pass */
+ "OCS-APIRequest": boolean;
+ };
+ path: {
+ apiVersion: "v4";
+ token: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Conversation unmuted */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: components["schemas"]["Room"];
+ };
+ };
+ };
+ };
+ /** @description Current user is not logged in */
+ 401: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: unknown;
+ };
+ };
+ };
+ };
+ };
+ };
"settings-set-user-setting": {
parameters: {
query?: never;
diff --git a/tests/integration/features/bootstrap/FeatureContext.php b/tests/integration/features/bootstrap/FeatureContext.php
index f8e1f212d97..4f2eb79c29d 100644
--- a/tests/integration/features/bootstrap/FeatureContext.php
+++ b/tests/integration/features/bootstrap/FeatureContext.php
@@ -548,6 +548,12 @@ private function assertRooms(array $rooms, TableNode $formData, bool $shouldOrde
$data['lobbyTimer'] = 'GREATER_THAN_ZERO';
}
}
+ if (isset($expectedRoom['muteUntil'])) {
+ $data['muteUntil'] = (int)$room['muteUntil'];
+ if ($expectedRoom['muteUntil'] === 'GREATER_THAN_ZERO' && $room['muteUntil'] > 0) {
+ $data['muteUntil'] = 'GREATER_THAN_ZERO';
+ }
+ }
if (isset($expectedRoom['breakoutRoomMode'])) {
$data['breakoutRoomMode'] = (int)$room['breakoutRoomMode'];
}
@@ -4975,6 +4981,26 @@ public function userMarksConversationSensitive(string $user, string $identifier,
$this->assertStatusCode($this->response, $statusCode);
}
+ #[When('/^user "([^"]*)" mutes room "([^"]*)" until OFFSET\((\d+)\) with (\d+) \((v4)\)$/')]
+ public function userMutesConversationUntil(string $user, string $identifier, int $muteUntil, int $statusCode, string $apiVersion): void {
+ $this->setCurrentUser($user);
+ $this->sendRequest(
+ 'POST', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/mute', [
+ 'muteUntil' => time() + $muteUntil,
+ ],
+ );
+ $this->assertStatusCode($this->response, $statusCode);
+ }
+
+ #[When('/^user "([^"]*)" unmutes room "([^"]*)" with (\d+) \((v4)\)$/')]
+ public function userUnmutesConversationUntil(string $user, string $identifier, int $statusCode, string $apiVersion): void {
+ $this->setCurrentUser($user);
+ $this->sendRequest(
+ 'DELETE', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/mute',
+ );
+ $this->assertStatusCode($this->response, $statusCode);
+ }
+
public function sendRequestFullUrl(string $verb, string $fullUrl, TableNode|array|string|null $body = null, array $headers = [], array $options = []): void {
$client = new Client();
$options = array_merge($options, ['cookies' => $this->getUserCookieJar($this->currentUser)]);
diff --git a/tests/integration/features/conversation-5/mute.feature b/tests/integration/features/conversation-5/mute.feature
new file mode 100644
index 00000000000..1bffbf0da2c
--- /dev/null
+++ b/tests/integration/features/conversation-5/mute.feature
@@ -0,0 +1,50 @@
+Feature: conversation-5/mute
+ Background:
+ Given user "participant1" exists
+ Given user "participant2" exists
+
+ Scenario: Mark as (un-)mute
+ Given user "participant1" creates room "group room" (v4)
+ | roomType | 3 |
+ | roomName | room |
+ And user "participant1" is participant of the following unordered rooms (v4)
+ | id | name | muteUntil |
+ | group room | room | 0 |
+ And user "participant1" mutes room "group room" until OFFSET(3600) with 200 (v4)
+ And user "participant1" is participant of the following unordered rooms (v4)
+ | id | name | muteUntil |
+ | group room | room | GREATER_THAN_ZERO |
+ And user "participant1" unmutes room "group room" with 200 (v4)
+ And user "participant1" is participant of the following unordered rooms (v4)
+ | id | name | muteUntil |
+ | group room | room | 0 |
+
+ Scenario: No notifications for muted rooms
+ When user "participant1" creates room "one-to-one room" (v4)
+ | roomType | 1 |
+ | invite | participant2 |
+ And user "participant2" creates room "one-to-one room" with 200 (v4)
+ | roomType | 1 |
+ | invite | participant1 |
+ And user "participant1" sends message "Message1" to room "one-to-one room" with 201
+ And user "participant2" mutes room "one-to-one room" until OFFSET(3600) with 200 (v4)
+ And user "participant1" sends message "Message with mention for @participant2" to room "one-to-one room" with 201
+ Then user "participant2" has the following notifications
+ | app | object_type | object_id | subject | message |
+ | spreed | chat | one-to-one room/Message1 | participant1-displayname sent you a private message | Message1 |
+ And user "participant2" unmutes room "one-to-one room" with 200 (v4)
+ Then user "participant2" has the following notifications
+ | app | object_type | object_id | subject | message |
+ | spreed | chat | one-to-one room/Message1 | participant1-displayname sent you a private message | Message1 |
+
+ Scenario: Participant1 calls while participant2 has muted the conversation
+ When user "participant1" creates room "room" (v4)
+ | roomType | 3 |
+ | roomName | room |
+ And user "participant1" adds user "participant2" to room "room" with 200 (v4)
+ Then user "participant1" is participant of room "room" (v4)
+ And user "participant2" is participant of room "room" (v4)
+ And user "participant2" mutes room "room" until OFFSET(3600) with 200 (v4)
+ Then user "participant1" joins room "room" with 200 (v4)
+ Then user "participant1" joins call "room" with 200 (v4)
+ Then user "participant2" has the following notifications
diff --git a/tests/php/Chat/ChatManagerTest.php b/tests/php/Chat/ChatManagerTest.php
index f931b8c27b2..2a9ef1680c3 100644
--- a/tests/php/Chat/ChatManagerTest.php
+++ b/tests/php/Chat/ChatManagerTest.php
@@ -444,6 +444,7 @@ public function testDeleteMessage(): void {
'has_unread_thread_directs' => false,
'hidden_pinned_id' => 0,
'has_scheduled_messages' => 0,
+ 'mute_until' => 0,
]);
$chat = $this->createMock(Room::class);
$chat->expects($this->any())
@@ -514,6 +515,7 @@ public function testDeleteMessageFileShare(): void {
'has_unread_thread_directs' => false,
'hidden_pinned_id' => 0,
'has_scheduled_messages' => 0,
+ 'mute_until' => 0,
]);
$chat = $this->createMock(Room::class);
$chat->expects($this->any())
@@ -606,6 +608,7 @@ public function testDeleteMessageFileShareNotFound(): void {
'has_unread_thread_directs' => false,
'hidden_pinned_id' => 0,
'has_scheduled_messages' => 0,
+ 'mute_until' => 0,
]);
$chat = $this->createMock(Room::class);
$chat->expects($this->any())
diff --git a/tests/php/Chat/NotifierTest.php b/tests/php/Chat/NotifierTest.php
index eb0987342e8..6d7e079ddaf 100644
--- a/tests/php/Chat/NotifierTest.php
+++ b/tests/php/Chat/NotifierTest.php
@@ -177,6 +177,10 @@ public function testNotifyMentionedUsers(string $message, array $alreadyNotified
->method('notify');
}
+ $current = 1234567;
+ $this->timeFactory->method('getTime')
+ ->willReturn($current);
+
$room = $this->getRoom();
$comment = $this->newComment('108', 'users', 'testUser', new \DateTime('@' . 1000000016), $message);
$notifier = $this->getNotifier([]);
@@ -188,24 +192,30 @@ public function testNotifyMentionedUsers(string $message, array $alreadyNotified
public static function dataShouldParticipantBeNotified(): array {
return [
- [Attendee::ACTOR_GROUPS, 'test1', null, Attendee::ACTOR_USERS, 'test1', [], false, Notifier::PRIORITY_NONE],
- [Attendee::ACTOR_USERS, 'test1', null, Attendee::ACTOR_USERS, 'test1', [], false, Notifier::PRIORITY_NONE],
- [Attendee::ACTOR_USERS, 'test1', null, Attendee::ACTOR_USERS, 'test2', [], false, Notifier::PRIORITY_NORMAL],
- [Attendee::ACTOR_USERS, 'test1', null, Attendee::ACTOR_USERS, 'test2', [['id' => 'test1', 'type' => Attendee::ACTOR_USERS]], false, Notifier::PRIORITY_NONE],
- [Attendee::ACTOR_USERS, 'test1', null, Attendee::ACTOR_USERS, 'test2', [['id' => 'test1', 'type' => Attendee::ACTOR_FEDERATED_USERS]], false, Notifier::PRIORITY_NORMAL],
- [Attendee::ACTOR_USERS, 'test1', Session::SESSION_TIMEOUT - 5, Attendee::ACTOR_USERS, 'test2', [], false, Notifier::PRIORITY_NONE],
- [Attendee::ACTOR_USERS, 'test1', Session::SESSION_TIMEOUT + 5, Attendee::ACTOR_USERS, 'test2', [], false, Notifier::PRIORITY_NORMAL],
+ [Attendee::ACTOR_GROUPS, 'test1', null, Attendee::ACTOR_USERS, 'test1', [], false, 0, Notifier::PRIORITY_NONE],
+ [Attendee::ACTOR_USERS, 'test1', null, Attendee::ACTOR_USERS, 'test1', [], false, 0, Notifier::PRIORITY_NONE],
+ [Attendee::ACTOR_USERS, 'test1', null, Attendee::ACTOR_USERS, 'test2', [], false, 0, Notifier::PRIORITY_NORMAL],
+ [Attendee::ACTOR_USERS, 'test1', null, Attendee::ACTOR_USERS, 'test2', [['id' => 'test1', 'type' => Attendee::ACTOR_USERS]], false, 0, Notifier::PRIORITY_NONE],
+ [Attendee::ACTOR_USERS, 'test1', null, Attendee::ACTOR_USERS, 'test2', [['id' => 'test1', 'type' => Attendee::ACTOR_FEDERATED_USERS]], false, 0, Notifier::PRIORITY_NORMAL],
+ [Attendee::ACTOR_USERS, 'test1', Session::SESSION_TIMEOUT - 5, Attendee::ACTOR_USERS, 'test2', [], false, 0, Notifier::PRIORITY_NONE],
+ [Attendee::ACTOR_USERS, 'test1', Session::SESSION_TIMEOUT + 5, Attendee::ACTOR_USERS, 'test2', [], false, 0, Notifier::PRIORITY_NORMAL],
// Marked as important, still blocked by session and being the author, but otherwise with PRIORITY_IMPORTANT
- [Attendee::ACTOR_USERS, 'test1', null, Attendee::ACTOR_USERS, 'test1', [], true, Notifier::PRIORITY_NONE],
- [Attendee::ACTOR_USERS, 'test1', null, Attendee::ACTOR_USERS, 'test2', [], true, Notifier::PRIORITY_IMPORTANT],
- [Attendee::ACTOR_USERS, 'test1', Session::SESSION_TIMEOUT - 5, Attendee::ACTOR_USERS, 'test2', [], true, Notifier::PRIORITY_NONE],
- [Attendee::ACTOR_USERS, 'test1', Session::SESSION_TIMEOUT + 5, Attendee::ACTOR_USERS, 'test2', [], true, Notifier::PRIORITY_IMPORTANT],
+ [Attendee::ACTOR_USERS, 'test1', null, Attendee::ACTOR_USERS, 'test1', [], true, 0, Notifier::PRIORITY_NONE],
+ [Attendee::ACTOR_USERS, 'test1', null, Attendee::ACTOR_USERS, 'test2', [], true, 0, Notifier::PRIORITY_IMPORTANT],
+ [Attendee::ACTOR_USERS, 'test1', Session::SESSION_TIMEOUT - 5, Attendee::ACTOR_USERS, 'test2', [], true, 0, Notifier::PRIORITY_NONE],
+ [Attendee::ACTOR_USERS, 'test1', Session::SESSION_TIMEOUT + 5, Attendee::ACTOR_USERS, 'test2', [], true, 0, Notifier::PRIORITY_IMPORTANT],
+
+ // Marked as muted, no notification even with PRIORITY_IMPORTANT
+ [Attendee::ACTOR_USERS, 'test1', null, Attendee::ACTOR_USERS, 'test1', [], true, 9999999, Notifier::PRIORITY_NONE],
+ [Attendee::ACTOR_USERS, 'test1', null, Attendee::ACTOR_USERS, 'test2', [], true, 9999999, Notifier::PRIORITY_NONE],
+ [Attendee::ACTOR_USERS, 'test1', Session::SESSION_TIMEOUT - 5, Attendee::ACTOR_USERS, 'test2', [], true, 9999999, Notifier::PRIORITY_NONE],
+ [Attendee::ACTOR_USERS, 'test1', Session::SESSION_TIMEOUT + 5, Attendee::ACTOR_USERS, 'test2', [], true, 9999999, Notifier::PRIORITY_NONE],
];
}
#[DataProvider('dataShouldParticipantBeNotified')]
- public function testShouldParticipantBeNotified(string $actorType, string $actorId, ?int $sessionAge, string $commentActorType, string $commentActorId, array $alreadyNotifiedUsers, bool $isImportant, int $expected): void {
+ public function testShouldParticipantBeNotified(string $actorType, string $actorId, ?int $sessionAge, string $commentActorType, string $commentActorId, array $alreadyNotifiedUsers, bool $isImportant, int $muteUntil, int $expected): void {
$comment = $this->createMock(IComment::class);
$comment->method('getActorType')
->willReturn($commentActorType);
@@ -217,13 +227,14 @@ public function testShouldParticipantBeNotified(string $actorType, string $actor
'actor_type' => $actorType,
'actor_id' => $actorId,
'important' => $isImportant,
+ 'mute_until' => $muteUntil,
]);
+ $current = 1234567;
+ $this->timeFactory->method('getTime')
+ ->willReturn($current);
+
$session = null;
if ($sessionAge !== null) {
- $current = 1234567;
- $this->timeFactory->method('getTime')
- ->willReturn($current);
-
$session = Session::fromRow([
'last_ping' => $current - $sessionAge,
]);