+
+
+ {{ t('spreed', 'Allow participants to mention @all') }}
+
+
+
+
+
+ {{ t('spreed', 'Mention permissions') }}
+
+
{{ summaryLabel }}
+
+
+
+
+
+
diff --git a/src/constants.js b/src/constants.js
index 6d617d5736b..992c77bc3bb 100644
--- a/src/constants.js
+++ b/src/constants.js
@@ -56,6 +56,11 @@ export const CONVERSATION = {
ALL: 2,
},
+ MENTION_PERMISSIONS: {
+ EVERYONE: 0,
+ MODERATORS: 1,
+ },
+
TYPE: {
ONE_TO_ONE: 1,
GROUP: 2,
diff --git a/src/services/conversationsService.js b/src/services/conversationsService.js
index c2fc37cf0a8..b565f942566 100644
--- a/src/services/conversationsService.js
+++ b/src/services/conversationsService.js
@@ -306,6 +306,12 @@ const changeListable = async function(token, listable) {
})
}
+const setMentionPermissions = async function(token, mentionPermissions) {
+ return axios.put(generateOcsUrl('apps/spreed/api/v4/room/{token}/mention-permissions', { token }), {
+ mentionPermissions,
+ })
+}
+
/**
* Set the default permissions for participants in a conversation.
*
@@ -375,4 +381,5 @@ export {
setConversationPermissions,
setCallPermissions,
setMessageExpiration,
+ setMentionPermissions,
}
diff --git a/src/store/conversationsStore.js b/src/store/conversationsStore.js
index febdd3db5c9..295ec7d94cf 100644
--- a/src/store/conversationsStore.js
+++ b/src/store/conversationsStore.js
@@ -46,6 +46,7 @@ import {
setConversationPassword,
createPublicConversation,
createPrivateConversation,
+ setMentionPermissions,
} from '../services/conversationsService.js'
import {
clearConversationHistory,
@@ -75,6 +76,7 @@ const DUMMY_CONVERSATION = {
participantType: PARTICIPANT.TYPE.USER,
readOnly: CONVERSATION.STATE.READ_ONLY,
listable: CONVERSATION.LISTABLE.NONE,
+ mentions: CONVERSATION.MENTION_PERMISSIONS.EVERYONE,
hasCall: false,
canStartCall: false,
lobbyState: WEBINAR.LOBBY.NONE,
@@ -228,6 +230,10 @@ const mutations = {
Vue.set(state.conversations[token], 'callPermissions', permissions)
},
+ setMentionPermissions(state, { token, mentionPermissions }) {
+ Vue.set(state.conversations[token], 'mentionPermissions', mentionPermissions)
+ },
+
setCallRecording(state, { token, callRecording }) {
Vue.set(state.conversations[token], 'callRecording', callRecording)
},
@@ -975,6 +981,15 @@ const actions = {
}
},
+ async setMentionPermissions(context, { token, mentionPermissions }) {
+ try {
+ await setMentionPermissions(token, mentionPermissions)
+ context.commit('setMentionPermissions', { token, mentionPermissions })
+ } catch (error) {
+ console.error('Error while updating mention permissions: ', error)
+ }
+ },
+
async startCallRecording(context, { token, callRecording }) {
try {
await startCallRecording(token, callRecording)
diff --git a/src/types/openapi/openapi-backend-sipbridge.ts b/src/types/openapi/openapi-backend-sipbridge.ts
index 37500bdd847..60fea67f2de 100644
--- a/src/types/openapi/openapi-backend-sipbridge.ts
+++ b/src/types/openapi/openapi-backend-sipbridge.ts
@@ -294,6 +294,8 @@ export type components = {
/** Format: int64 */
lobbyTimer: number;
/** Format: int64 */
+ mentionPermissions: number;
+ /** Format: int64 */
messageExpiration: number;
name: string;
/** Format: int64 */
diff --git a/src/types/openapi/openapi-federation.ts b/src/types/openapi/openapi-federation.ts
index 10f6282a268..b558303de44 100644
--- a/src/types/openapi/openapi-federation.ts
+++ b/src/types/openapi/openapi-federation.ts
@@ -337,6 +337,8 @@ export type components = {
/** Format: int64 */
lobbyTimer: number;
/** Format: int64 */
+ mentionPermissions: number;
+ /** Format: int64 */
messageExpiration: number;
name: string;
/** Format: int64 */
diff --git a/src/types/openapi/openapi-full.ts b/src/types/openapi/openapi-full.ts
index 63fc2217721..b1412694208 100644
--- a/src/types/openapi/openapi-full.ts
+++ b/src/types/openapi/openapi-full.ts
@@ -1183,6 +1183,23 @@ export type paths = {
patch?: never;
trace?: never;
};
+ "/ocs/v2.php/apps/spreed/api/{apiVersion}/room/{token}/mention-permissions": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ /** Update the mention permissions for a room */
+ put: operations["room-set-mention-permissions"];
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
"/ocs/v2.php/apps/spreed/api/{apiVersion}/settings/user": {
parameters: {
query?: never;
@@ -2087,6 +2104,8 @@ export type components = {
/** Format: int64 */
lobbyTimer: number;
/** Format: int64 */
+ mentionPermissions: number;
+ /** Format: int64 */
messageExpiration: number;
name: string;
/** Format: int64 */
@@ -7328,6 +7347,54 @@ export interface operations {
};
};
};
+ "room-set-mention-permissions": {
+ parameters: {
+ query: {
+ /** @description New mention permissions */
+ mentionPermissions: 0 | 1;
+ };
+ 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 Permissions updated successfully */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: components["schemas"]["Room"];
+ };
+ };
+ };
+ };
+ /** @description Updating permissions is not possible */
+ 400: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: unknown;
+ };
+ };
+ };
+ };
+ };
+ };
"settings-set-user-setting": {
parameters: {
query: {
diff --git a/src/types/openapi/openapi.ts b/src/types/openapi/openapi.ts
index 0f7fdedff7c..4f36d9b9ef2 100644
--- a/src/types/openapi/openapi.ts
+++ b/src/types/openapi/openapi.ts
@@ -1185,6 +1185,23 @@ export type paths = {
patch?: never;
trace?: never;
};
+ "/ocs/v2.php/apps/spreed/api/{apiVersion}/room/{token}/mention-permissions": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ get?: never;
+ /** Update the mention permissions for a room */
+ put: operations["room-set-mention-permissions"];
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
"/ocs/v2.php/apps/spreed/api/{apiVersion}/settings/user": {
parameters: {
query?: never;
@@ -1572,6 +1589,8 @@ export type components = {
/** Format: int64 */
lobbyTimer: number;
/** Format: int64 */
+ mentionPermissions: number;
+ /** Format: int64 */
messageExpiration: number;
name: string;
/** Format: int64 */
@@ -6906,6 +6925,54 @@ export interface operations {
};
};
};
+ "room-set-mention-permissions": {
+ parameters: {
+ query: {
+ /** @description New mention permissions */
+ mentionPermissions: 0 | 1;
+ };
+ 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 Permissions updated successfully */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: components["schemas"]["Room"];
+ };
+ };
+ };
+ };
+ /** @description Updating permissions is not possible */
+ 400: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": {
+ ocs: {
+ meta: components["schemas"]["OCSMeta"];
+ data: unknown;
+ };
+ };
+ };
+ };
+ };
+ };
"settings-set-user-setting": {
parameters: {
query: {
diff --git a/tests/integration/features/bootstrap/FeatureContext.php b/tests/integration/features/bootstrap/FeatureContext.php
index ea13b2a903a..9cd7513e2fa 100644
--- a/tests/integration/features/bootstrap/FeatureContext.php
+++ b/tests/integration/features/bootstrap/FeatureContext.php
@@ -4627,6 +4627,31 @@ public function sendXMLRequest($verb, $url, $body = null, array $headers = [], a
$this->sendRequestFullUrl($verb, $fullUrl, $body, $headers, $options);
}
+ /**
+ * @When /^user "([^"]*)" sets mention permissions for room "([^"]*)" to (all|moderators) with (\d+) \((v4)\)$/
+ *
+ * @param string $user
+ * @param string $identifier
+ * @param string $mentionPermissions
+ * @param int $statusCode
+ * @param string $apiVersion
+ */
+ public function userSetsMentionPermissionsOfTheRoom(string $user, string $identifier, string $mentionPermissions, int $statusCode, string $apiVersion): void {
+ $intMentionPermissions = 0; // all - default
+ if($mentionPermissions === 'moderators') {
+ $intMentionPermissions = 1;
+ }
+
+ $this->setCurrentUser($user);
+ $this->sendRequest(
+ 'PUT', '/apps/spreed/api/' . $apiVersion . '/room/' . self::$identifierToToken[$identifier] . '/mention-permissions',
+ new TableNode([
+ ['mentionPermissions', $intMentionPermissions],
+ ])
+ );
+ $this->assertStatusCode($this->response, $statusCode);
+ }
+
/**
* @param string $verb
* @param string $url
diff --git a/tests/integration/features/chat-1/mentions.feature b/tests/integration/features/chat-1/mentions.feature
index efb32e32efd..c3140dddf35 100644
--- a/tests/integration/features/chat-1/mentions.feature
+++ b/tests/integration/features/chat-1/mentions.feature
@@ -633,3 +633,25 @@ Feature: chat/mentions
Then user "participant1" sees the following messages in room "participant1-note-to-self" with 200
| room | actorType | actorId | actorDisplayName | message | messageParameters |
| participant1-note-to-self | users | participant1 | participant1-displayname | Test {mention-call1} | "IGNORE" |
+
+ Scenario: get mentions with different mention permissions available
+ Given user "participant1" creates room "group room" (v4)
+ | roomType | 2 |
+ | roomName | room |
+ And user "participant1" adds user "participant2" to room "group room" with 200 (v4)
+ Then user "participant1" gets the following candidate mentions in room "group room" for "" with 200
+ | id | label | source | mentionId |
+ | all | room | calls | all |
+ | participant2 | participant2-displayname | users | participant2 |
+ And user "participant2" gets the following candidate mentions in room "group room" for "" with 200
+ | id | label | source | mentionId |
+ | all | room | calls | all |
+ | participant1 | participant1-displayname | users | participant1 |
+ When user "participant1" sets mention permissions for room "group room" to moderators with 200 (v4)
+ Then user "participant1" gets the following candidate mentions in room "group room" for "" with 200
+ | id | label | source | mentionId |
+ | all | room | calls | all |
+ | participant2 | participant2-displayname | users | participant2 |
+ And user "participant2" gets the following candidate mentions in room "group room" for "" with 200
+ | id | label | source | mentionId |
+ | participant1 | participant1-displayname | users | participant1 |
diff --git a/tests/integration/features/chat-1/notifications.feature b/tests/integration/features/chat-1/notifications.feature
index e74482e1d8c..e29accc663d 100644
--- a/tests/integration/features/chat-1/notifications.feature
+++ b/tests/integration/features/chat-1/notifications.feature
@@ -544,3 +544,18 @@ Feature: chat/notifications
When user "participant1" sets lobby state for room "room" to "non moderators" with 200 (v4)
Then user "participant2" has the following notifications
| app | object_type | object_id | subject |
+
+ Scenario: At-all with different mention permissions
+ Given user "participant1" creates room "room" (v4)
+ | roomType | 2 |
+ | roomName | room |
+ And user "participant1" adds user "participant2" to room "room" with 200 (v4)
+ When user "participant2" sends message "Hi @all" to room "room" with 201
+ Then user "participant1" has the following notifications
+ | app | object_type | object_id | subject |
+ | spreed | chat | room/Hi @all | participant2-displayname mentioned everyone in conversation room |
+ When user "participant1" reads message "Hi @all" in room "room" with 200
+ And user "participant1" sets mention permissions for room "room" to moderators with 200 (v4)
+ And user "participant2" sends message "Hi @all" to room "room" with 201
+ Then user "participant1" has the following notifications
+ | app | object_type | object_id | subject |
diff --git a/tests/php/Chat/ChatManagerTest.php b/tests/php/Chat/ChatManagerTest.php
index 05444b0a43b..6160e01b8c9 100644
--- a/tests/php/Chat/ChatManagerTest.php
+++ b/tests/php/Chat/ChatManagerTest.php
@@ -694,38 +694,56 @@ public static function dataAddConversationNotify(): array {
],
[
'',
- ['getDisplayName' => 'test'],
+ ['getDisplayName' => 'test', 'getMentionPermissions' => 0],
['getAttendee' => Attendee::fromRow([
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'user',
])],
[['id' => 'all', 'label' => 'test', 'source' => 'calls', 'mentionId' => 'all']]
],
+ [
+ '',
+ ['getMentionPermissions' => 1],
+ ['hasModeratorPermissions' => false],
+ []
+ ],
[
'all',
- ['getDisplayName' => 'test'],
+ ['getDisplayName' => 'test', 'getMentionPermissions' => 0],
['getAttendee' => Attendee::fromRow([
'actor_type' => Attendee::ACTOR_USERS,
'actor_id' => 'user',
])],
[['id' => 'all', 'label' => 'test', 'source' => 'calls', 'mentionId' => 'all']]
],
+ [
+ 'all',
+ ['getDisplayName' => 'test', 'getMentionPermissions' => 1],
+ [
+ 'getAttendee' => Attendee::fromRow([
+ 'actor_type' => Attendee::ACTOR_USERS,
+ 'actor_id' => 'user',
+ ]),
+ 'hasModeratorPermissions' => true,
+ ],
+ [['id' => 'all', 'label' => 'test', 'source' => 'calls', 'mentionId' => 'all']]
+ ],
[
'here',
- ['getDisplayName' => 'test'],
+ ['getDisplayName' => 'test', 'getMentionPermissions' => 0],
['getAttendee' => Attendee::fromRow([
'actor_type' => Attendee::ACTOR_GUESTS,
'actor_id' => 'guest',
])],
[['id' => 'all', 'label' => 'test', 'source' => 'calls', 'mentionId' => 'all']]
- ]
+ ],
];
}
/**
* @dataProvider dataAddConversationNotify
*/
- public function testAddConversationNotify(string $search, array$roomMocks, array $participantMocks, array $expected): void {
+ public function testAddConversationNotify(string $search, array $roomMocks, array $participantMocks, array $expected): void {
$room = $this->createMock(Room::class);
foreach ($roomMocks as $method => $return) {
$room->expects($this->once())
diff --git a/tests/php/Chat/NotifierTest.php b/tests/php/Chat/NotifierTest.php
index 72b84d75479..445b56b51c7 100644
--- a/tests/php/Chat/NotifierTest.php
+++ b/tests/php/Chat/NotifierTest.php
@@ -176,7 +176,8 @@ public function testNotifyMentionedUsers(string $message, array $alreadyNotified
$room = $this->getRoom();
$comment = $this->newComment('108', 'users', 'testUser', new \DateTime('@' . 1000000016), $message);
$notifier = $this->getNotifier([]);
- $actual = $notifier->notifyMentionedUsers($room, $comment, $alreadyNotifiedUsers, false);
+ $participant = $this->createMock(Participant::class);
+ $actual = $notifier->notifyMentionedUsers($room, $comment, $alreadyNotifiedUsers, false, $participant);
$this->assertEqualsCanonicalizing($expectedReturn, $actual);
}
@@ -286,6 +287,8 @@ public static function dataAddMentionAllToList(): array {
'not notify' => [
[],
[],
+ 0,
+ true,
[],
],
'preserve notify list and do not notify all' => [
@@ -293,6 +296,8 @@ public static function dataAddMentionAllToList(): array {
['id' => 'user1', 'type' => Attendee::ACTOR_USERS, 'reason' => 'direct'],
],
[],
+ 0,
+ true,
[
['id' => 'user1', 'type' => Attendee::ACTOR_USERS, 'reason' => 'direct'],
],
@@ -306,24 +311,48 @@ public static function dataAddMentionAllToList(): array {
Attendee::fromRow(['actor_id' => 'user1', 'actor_type' => Attendee::ACTOR_USERS]),
Attendee::fromRow(['actor_id' => 'user2', 'actor_type' => Attendee::ACTOR_USERS]),
],
+ 0,
+ false,
[
['id' => 'user1', 'type' => Attendee::ACTOR_USERS, 'reason' => 'direct'],
['id' => 'user2', 'type' => Attendee::ACTOR_USERS, 'reason' => 'all'],
],
],
+ 'prevent non-moderator to notify all' => [
+ [
+ ['id' => 'user1', 'type' => Attendee::ACTOR_USERS, 'reason' => 'direct'],
+ ['id' => 'all', 'type' => Attendee::ACTOR_USERS, 'reason' => 'direct'],
+ ],
+ [
+ Attendee::fromRow(['actor_id' => 'user1', 'actor_type' => Attendee::ACTOR_USERS]),
+ Attendee::fromRow(['actor_id' => 'user2', 'actor_type' => Attendee::ACTOR_USERS]),
+ ],
+ 1,
+ false,
+ [
+ ['id' => 'user1', 'type' => Attendee::ACTOR_USERS, 'reason' => 'direct'],
+ ],
+ ],
];
}
/**
* @dataProvider dataAddMentionAllToList
*/
- public function testAddMentionAllToList(array $usersToNotify, array $participants, array $return): void {
+ public function testAddMentionAllToList(array $usersToNotify, array $participants, int $mentionPermissions, bool $moderatorPermissions, array $return): void {
$room = $this->createMock(Room::class);
+ $room->method('getMentionPermissions')
+ ->willReturn($mentionPermissions);
+
$this->participantService
->method('getActorsByType')
->willReturn($participants);
- $actual = self::invokePrivate($this->getNotifier(), 'addMentionAllToList', [$room, $usersToNotify]);
+ $participant = $this->createMock(Participant::class);
+ $participant->method('hasModeratorPermissions')
+ ->willReturn($moderatorPermissions);
+
+ $actual = self::invokePrivate($this->getNotifier(), 'addMentionAllToList', [$room, $usersToNotify, $participant]);
$this->assertCount(count($return), $actual);
foreach ($actual as $key => $value) {
$this->assertIsArray($value);
diff --git a/tests/php/Chat/Parser/UserMentionTest.php b/tests/php/Chat/Parser/UserMentionTest.php
index 60e9aac9713..5f9386ce6e0 100644
--- a/tests/php/Chat/Parser/UserMentionTest.php
+++ b/tests/php/Chat/Parser/UserMentionTest.php
@@ -65,13 +65,18 @@ public function setUp(): void {
/**
* @param array $mentions
+ * @param array|null $metadata
* @return MockObject|IComment
*/
- private function newComment(array $mentions): IComment {
+ private function newComment(array $mentions, ?array $metadata = null): IComment {
$comment = $this->createMock(IComment::class);
$comment->method('getMentions')->willReturn($mentions);
+ if ($metadata !== null) {
+ $comment->method('getMetaData')->willReturn($metadata);
+ }
+
return $comment;
}
@@ -385,7 +390,10 @@ public function testGetRichMessageWithAtAll(): void {
$mentions = [
['type' => 'user', 'id' => 'all'],
];
- $comment = $this->newComment($mentions);
+ $metadata = [
+ Message::METADATA_CAN_MENTION_ALL => true,
+ ];
+ $comment = $this->newComment($mentions, $metadata);
/** @var Room&MockObject $room */
$room = $this->createMock(Room::class);
diff --git a/tests/php/Service/RoomServiceTest.php b/tests/php/Service/RoomServiceTest.php
index b43ecc21a19..7307630cef9 100644
--- a/tests/php/Service/RoomServiceTest.php
+++ b/tests/php/Service/RoomServiceTest.php
@@ -366,6 +366,7 @@ public function testVerifyPassword(): void {
Room::RECORDING_NONE,
RecordingService::CONSENT_REQUIRED_NO,
Room::HAS_FEDERATION_NONE,
+ Room::MENTION_PERMISSIONS_EVERYONE,
);
$verificationResult = $service->verifyPassword($room, '1234');