Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/spreed.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.drone.yml130
-rw-r--r--appinfo/routes.php1
-rw-r--r--appinfo/routes/routesReactionController.php41
-rw-r--r--docs/capabilities.md1
-rw-r--r--docs/chat.md2
-rw-r--r--docs/index.md1
-rw-r--r--docs/reaction.md66
-rw-r--r--lib/Capabilities.php9
-rw-r--r--lib/Chat/Notifier.php44
-rw-r--r--lib/Chat/Parser/Listener.php12
-rw-r--r--lib/Chat/Parser/ReactionParser.php52
-rw-r--r--lib/Chat/ReactionManager.php153
-rw-r--r--lib/Controller/ReactionController.php130
-rw-r--r--lib/Exceptions/ReactionAlreadyExistsException.php29
-rw-r--r--lib/Exceptions/ReactionNotSupportedException.php29
-rw-r--r--lib/Exceptions/ReactionOutOfContextException.php29
-rw-r--r--lib/Manager.php2
-rw-r--r--lib/Model/Message.php2
-rw-r--r--lib/Notification/Notifier.php23
-rw-r--r--mkdocs.yml1
-rw-r--r--tests/integration/features/bootstrap/FeatureContext.php63
-rw-r--r--tests/integration/features/reaction/react.feature68
-rw-r--r--tests/php/CapabilitiesTest.php11
-rw-r--r--tests/php/Chat/NotifierTest.php50
24 files changed, 933 insertions, 16 deletions
diff --git a/.drone.yml b/.drone.yml
index d25ce0823..78fc3fea9 100644
--- a/.drone.yml
+++ b/.drone.yml
@@ -178,6 +178,43 @@ trigger:
---
kind: pipeline
+name: int-sqlite-reaction
+
+steps:
+ - name: integration-reaction
+ image: ghcr.io/nextcloud/continuous-integration-php8.0:latest
+ environment:
+ APP_NAME: spreed
+ CORE_BRANCH: master
+ GUESTS_BRANCH: master
+ DATABASEHOST: sqlite
+ commands:
+ - bash tests/drone-run-integration-tests.sh || exit 0
+ - wget https://raw.githubusercontent.com/nextcloud/travis_ci/master/before_install.sh
+ - bash ./before_install.sh $APP_NAME $CORE_BRANCH $DATABASEHOST
+ - cd ../server
+ - git clone --depth 1 -b "$GUESTS_BRANCH" https://github.com/nextcloud/guests apps/guests
+ - ./occ app:enable $APP_NAME
+ - cd apps/$APP_NAME
+
+ # Run integration tests
+ - cd tests/integration/
+ - bash run.sh features/reaction
+
+services:
+ - name: cache
+ image: ghcr.io/nextcloud/continuous-integration-redis:latest
+
+trigger:
+ branch:
+ - master
+ - stable*
+ event:
+ - pull_request
+ - push
+
+---
+kind: pipeline
name: int-sqlite-sharing
steps:
@@ -477,6 +514,53 @@ trigger:
---
kind: pipeline
+name: int-mysql-reaction
+
+steps:
+ - name: integration-reaction
+ image: ghcr.io/nextcloud/continuous-integration-php8.0:latest
+ environment:
+ APP_NAME: spreed
+ CORE_BRANCH: master
+ GUESTS_BRANCH: master
+ DATABASEHOST: mysql
+ commands:
+ - bash tests/drone-run-integration-tests.sh || exit 0
+ - wget https://raw.githubusercontent.com/nextcloud/travis_ci/master/before_install.sh
+ - bash ./before_install.sh $APP_NAME $CORE_BRANCH $DATABASEHOST
+ - cd ../server
+ - git clone --depth 1 -b "$GUESTS_BRANCH" https://github.com/nextcloud/guests apps/guests
+ - ./occ app:enable $APP_NAME
+ - cd apps/$APP_NAME
+
+ # Run integration tests
+ - cd tests/integration/
+ - bash run.sh features/reaction
+
+services:
+ - name: cache
+ image: ghcr.io/nextcloud/continuous-integration-redis:latest
+ - name: mysql
+ image: ghcr.io/nextcloud/continuous-integration-mariadb-10.4:10.4
+ environment:
+ MYSQL_ROOT_PASSWORD: owncloud
+ MYSQL_USER: oc_autotest
+ MYSQL_PASSWORD: owncloud
+ MYSQL_DATABASE: oc_autotest
+ command: [ "--innodb_large_prefix=true", "--innodb_file_format=barracuda", "--innodb_file_per_table=true" ]
+ tmpfs:
+ - /var/lib/mysql
+
+trigger:
+ branch:
+ - master
+ - stable*
+ event:
+# - pull_request
+ - push
+
+---
+kind: pipeline
name: int-mysql-sharing
steps:
@@ -791,6 +875,52 @@ trigger:
---
kind: pipeline
+name: int-pgsql-reaction
+
+steps:
+ - name: integration-reaction
+ image: ghcr.io/nextcloud/continuous-integration-php8.0:latest
+ environment:
+ APP_NAME: spreed
+ CORE_BRANCH: master
+ GUESTS_BRANCH: master
+ DATABASEHOST: pgsql
+ commands:
+ - bash tests/drone-run-integration-tests.sh || exit 0
+ - wget https://raw.githubusercontent.com/nextcloud/travis_ci/master/before_install.sh
+ - bash ./before_install.sh $APP_NAME $CORE_BRANCH $DATABASEHOST
+ - cd ../server
+ - git clone --depth 1 -b "$GUESTS_BRANCH" https://github.com/nextcloud/guests apps/guests
+ - ./occ app:enable $APP_NAME
+ - cd apps/$APP_NAME
+
+ # Run integration tests
+ - cd tests/integration/
+ - bash run.sh features/reaction
+
+services:
+ - name: cache
+ image: ghcr.io/nextcloud/continuous-integration-redis:latest
+ - name: pgsql
+ image: ghcr.io/nextcloud/continuous-integration-postgres-13:postgres-13
+ environment:
+ POSTGRES_USER: oc_autotest
+ POSTGRES_DB: oc_autotest_dummy
+ POSTGRES_HOST_AUTH_METHOD: trust
+ POSTGRES_PASSWORD:
+ tmpfs:
+ - /var/lib/postgresql/data
+
+trigger:
+ branch:
+ - master
+ - stable*
+ event:
+# - pull_request
+ - push
+
+---
+kind: pipeline
name: int-pgsql-sharing
steps:
diff --git a/appinfo/routes.php b/appinfo/routes.php
index e888b7006..fde29617e 100644
--- a/appinfo/routes.php
+++ b/appinfo/routes.php
@@ -36,6 +36,7 @@ return array_merge_recursive(
include(__DIR__ . '/routes/routesMatterbridgeSettingsController.php'),
include(__DIR__ . '/routes/routesPageController.php'),
include(__DIR__ . '/routes/routesPublicShareAuthController.php'),
+ include(__DIR__ . '/routes/routesReactionController.php'),
include(__DIR__ . '/routes/routesRoomController.php'),
include(__DIR__ . '/routes/routesSettingsController.php'),
include(__DIR__ . '/routes/routesSignalingController.php'),
diff --git a/appinfo/routes/routesReactionController.php b/appinfo/routes/routesReactionController.php
new file mode 100644
index 000000000..375478e3f
--- /dev/null
+++ b/appinfo/routes/routesReactionController.php
@@ -0,0 +1,41 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
+ *
+ * @author Vitor Mattos <vitor@php.rio>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+return [
+ 'ocs' => [
+ ['name' => 'Reaction#react', 'url' => '/api/{apiVersion}/reaction/{token}/{messageId}', 'verb' => 'POST', 'requirements' => [
+ 'apiVersion' => 'v1',
+ 'token' => '^[a-z0-9]{4,30}$',
+ ]],
+ ['name' => 'Reaction#delete', 'url' => '/api/{apiVersion}/reaction/{token}/{messageId}', 'verb' => 'DELETE', 'requirements' => [
+ 'apiVersion' => 'v1',
+ 'token' => '^[a-z0-9]{4,30}$',
+ ]],
+ ['name' => 'Reaction#getReactions', 'url' => '/api/{apiVersion}/reaction/{token}/{messageId}', 'verb' => 'GET', 'requirements' => [
+ 'apiVersion' => 'v1',
+ 'token' => '^[a-z0-9]{4,30}$',
+ ]],
+ ],
+];
diff --git a/docs/capabilities.md b/docs/capabilities.md
index 489ec3416..9ef1de814 100644
--- a/docs/capabilities.md
+++ b/docs/capabilities.md
@@ -89,3 +89,4 @@ title: Capabilities
## 14
* `chat-unread` - Whether the API to mark a conversation as unread is available
+* `reactions` - Api reactions to chat message
diff --git a/docs/chat.md b/docs/chat.md
index ff03979ef..07c3660b1 100644
--- a/docs/chat.md
+++ b/docs/chat.md
@@ -50,6 +50,7 @@ Base endpoint is: `/ocs/v2.php/apps/spreed/api/v1`
`message` | string | Message string with placeholders (see [Rich Object String](https://github.com/nextcloud/server/issues/1706))
`messageParameters` | array | Message parameters for `message` (see [Rich Object String](https://github.com/nextcloud/server/issues/1706))
`parent` | array | **Optional:** See `Parent data` below
+ `reactions` | array | **Optional:** An array map with relation between reaction emoji and total count of reactions with this emoji
#### Parent data
@@ -324,3 +325,4 @@ See [OCP\RichObjectStrings\Definitions](https://github.com/nextcloud/server/blob
* `matterbridge_config_removed` - {actor} removed the Matterbridge configuration
* `matterbridge_config_enabled` - {actor} started Matterbridge
* `matterbridge_config_disabled` - {actor} stopped Matterbridge
+
diff --git a/docs/index.md b/docs/index.md
index 0e19b823f..3e1dec1dc 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -16,6 +16,7 @@
* [Participant API](participant.md)
* [Call API](call.md)
* [Chat API](chat.md)
+* [Reaction API](reaction.md)
* [Webinar API](webinar.md)
* [Internal Signaling API](internal-signaling.md)
* [Standalone Signaling API](https://nextcloud-spreed-signaling.readthedocs.io/en/latest/)
diff --git a/docs/reaction.md b/docs/reaction.md
new file mode 100644
index 000000000..5fc16e9f6
--- /dev/null
+++ b/docs/reaction.md
@@ -0,0 +1,66 @@
+# Reaction API
+
+Base endpoint is: `/ocs/v2.php/apps/spreed/api/v1`
+
+## React to a message
+
+* Required capability: `reactions`
+* Method: `POST`
+* Endpoint: `/reaction/{token}/{messageId}`
+* Data:
+
+ field | type | Description
+ ---|---|---
+ `reaction` | string | the reaction emoji
+
+* Response:
+ - Status code:
+ + `200 OK` Reaction already exists
+ + `201 Created`
+ + `400 Bad Request` In case of no reaction support, message out of reactions context or any other error
+ + `404 Not Found` When the conversation or message to react could not be found for the participant
+ + `409 Conflict` User already did this reaction to this message
+
+## Delete a reaction
+
+* Required capability: `reactions`
+* Method: `DELETE`
+* Endpoint: `/reaction/{token}/{messageId}`
+* Data:
+
+ field | type | Description
+ ---|---|---
+ `reaction` | string | the reaction emoji
+
+* Response:
+ - Status code:
+ + `201 Created`
+ + `400 Bad Request` In case of no reaction support, message out of reactions context or any other error
+ + `404 Not Found` When the conversation or message to react or reaction could not be found for the participant
+
+## Retrieve reactions of a message by type
+
+* Required capability: `reactions`
+* Method: `GET`
+* Endpoint: `/reaction/{token}/{messageId}`
+* Data:
+
+ field | type | Description
+ ---|---|---
+ `reaction` | string | **Optional:** the reaction emoji
+
+* Response:
+ - Status code:
+ + `200 OK`
+ + `400 Bad Request` In case of no reaction support, message out of reactions context or any other error
+ + `404 Not Found` When the conversation or message to react could not be found for the participant
+
+ - Data:
+ Array with data of reactions:
+
+ field | type | Description
+ ---|---|---
+ `actorType` | string | `guests` or `users`
+ `actorId` | string | Actor id of the reacting participant
+ `actorDisplayName` | string | Display name of the reaction author
+ `timestamp` | int | Timestamp in seconds and UTC time zone
diff --git a/lib/Capabilities.php b/lib/Capabilities.php
index c4742a38d..f8426d58f 100644
--- a/lib/Capabilities.php
+++ b/lib/Capabilities.php
@@ -27,6 +27,7 @@ namespace OCA\Talk;
use OCA\Talk\Chat\ChatManager;
use OCP\Capabilities\IPublicCapability;
+use OCP\Comments\ICommentsManager;
use OCP\IConfig;
use OCP\IUser;
use OCP\IUserSession;
@@ -37,14 +38,18 @@ class Capabilities implements IPublicCapability {
protected $serverConfig;
/** @var Config */
protected $talkConfig;
+ /** @var ICommentsManager */
+ protected $commentsManager;
/** @var IUserSession */
protected $userSession;
public function __construct(IConfig $serverConfig,
Config $talkConfig,
+ ICommentsManager $commentsManager,
IUserSession $userSession) {
$this->serverConfig = $serverConfig;
$this->talkConfig = $talkConfig;
+ $this->commentsManager = $commentsManager;
$this->userSession = $userSession;
}
@@ -115,6 +120,10 @@ class Capabilities implements IPublicCapability {
],
];
+ if ($this->commentsManager->supportReactions()) {
+ $capabilities['features'][] = 'reactions';
+ }
+
if ($user instanceof IUser) {
$capabilities['config']['attachments']['folder'] = $this->talkConfig->getAttachmentFolder($user->getUID());
$capabilities['config']['chat']['read-privacy'] = $this->talkConfig->getUserReadPrivacy($user->getUID());
diff --git a/lib/Chat/Notifier.php b/lib/Chat/Notifier.php
index 83b45ac4c..e8428ebba 100644
--- a/lib/Chat/Notifier.php
+++ b/lib/Chat/Notifier.php
@@ -200,10 +200,11 @@ class Notifier {
* @param Room $chat
* @param IComment $comment
* @param IComment $replyTo
+ * @param string $subject
* @return array[] Actor that was replied to
* @psalm-return array<int, array{id: string, type: string}>
*/
- public function notifyReplyToAuthor(Room $chat, IComment $comment, IComment $replyTo): array {
+ public function notifyReplyToAuthor(Room $chat, IComment $comment, IComment $replyTo, string $subject = 'reply'): array {
if ($replyTo->getActorType() !== Attendee::ACTOR_USERS) {
// No reply notification when the replyTo-author was not a user
return [];
@@ -213,7 +214,7 @@ class Notifier {
return [];
}
- $notification = $this->createNotification($chat, $comment, 'reply');
+ $notification = $this->createNotification($chat, $comment, $subject);
$notification->setUser($replyTo->getActorId());
$this->notificationManager->notify($notification);
@@ -266,6 +267,34 @@ class Notifier {
}
}
+ public function notifyReacted(Room $chat, IComment $comment, IComment $reaction): void {
+ if ($comment->getActorType() !== Attendee::ACTOR_USERS) {
+ return;
+ }
+
+ if ($comment->getActorType() === $reaction->getActorType() && $comment->getActorId() === $reaction->getActorId()) {
+ return;
+ }
+
+ $participant = $chat->getParticipant($comment->getActorId(), false);
+ $notificationLevel = $participant->getAttendee()->getNotificationLevel();
+ if ($notificationLevel === Participant::NOTIFY_DEFAULT) {
+ if ($chat->getType() === Room::TYPE_ONE_TO_ONE) {
+ $notificationLevel = Participant::NOTIFY_ALWAYS;
+ } else {
+ $notificationLevel = $this->getDefaultGroupNotification();
+ }
+ }
+
+ if ($notificationLevel === Participant::NOTIFY_ALWAYS) {
+ $notification = $this->createNotification($chat, $comment, 'reaction', [
+ 'reaction' => $reaction->getMessage(),
+ ]);
+ $notification->setUser($comment->getActorId());
+ $this->notificationManager->notify($notification);
+ }
+ }
+
/**
* Removes all the pending notifications for the room with the given ID.
*
@@ -363,17 +392,18 @@ class Notifier {
* @param Room $chat
* @param IComment $comment
* @param string $subject
+ * @param array $subjectData
* @return INotification
*/
- private function createNotification(Room $chat, IComment $comment, string $subject): INotification {
+ private function createNotification(Room $chat, IComment $comment, string $subject, array $subjectData = []): INotification {
+ $subjectData['userType'] = $comment->getActorType();
+ $subjectData['userId'] = $comment->getActorId();
+
$notification = $this->notificationManager->createNotification();
$notification
->setApp('spreed')
->setObject('chat', $chat->getToken())
- ->setSubject($subject, [
- 'userType' => $comment->getActorType(),
- 'userId' => $comment->getActorId(),
- ])
+ ->setSubject($subject, $subjectData)
->setMessage($comment->getVerb(), [
'commentId' => $comment->getId(),
])
diff --git a/lib/Chat/Parser/Listener.php b/lib/Chat/Parser/Listener.php
index 83e26f673..ebb63fe22 100644
--- a/lib/Chat/Parser/Listener.php
+++ b/lib/Chat/Parser/Listener.php
@@ -100,6 +100,18 @@ class Listener {
$dispatcher->addListener(MessageParser::EVENT_MESSAGE_PARSE, static function (ChatMessageEvent $event) {
$chatMessage = $event->getMessage();
+ if ($chatMessage->getMessageType() !== 'reaction' && $chatMessage->getMessageType() !== 'reaction_deleted') {
+ return;
+ }
+
+ /** @var ReactionParser $parser */
+ $parser = \OC::$server->get(ReactionParser::class);
+ $parser->parseMessage($chatMessage);
+ });
+
+ $dispatcher->addListener(MessageParser::EVENT_MESSAGE_PARSE, static function (ChatMessageEvent $event) {
+ $chatMessage = $event->getMessage();
+
if ($chatMessage->getMessageType() !== 'comment_deleted') {
return;
}
diff --git a/lib/Chat/Parser/ReactionParser.php b/lib/Chat/Parser/ReactionParser.php
new file mode 100644
index 000000000..7e38e0f78
--- /dev/null
+++ b/lib/Chat/Parser/ReactionParser.php
@@ -0,0 +1,52 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
+ *
+ * @author Vitor Mattos <vitor@php.rio>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Talk\Chat\Parser;
+
+use OCA\Talk\Model\Message;
+use OCP\IL10N;
+
+class ReactionParser {
+ /** @var IL10N|null */
+ private $l;
+ /**
+ * @param Message $message
+ * @throws \OutOfBoundsException
+ */
+ public function parseMessage(Message $message): void {
+ $comment = $message->getComment();
+ if (!in_array($comment->getVerb(), ['reaction', 'reaction_deleted'])) {
+ throw new \OutOfBoundsException('Not a reaction');
+ }
+ $this->l = $message->getL10n();
+ $message->setMessageType('system');
+ if ($comment->getVerb() === 'reaction_deleted') {
+ // This message is necessary to make compatible with old clients
+ $message->setMessage($this->l->t('Message deleted by author'), [], $comment->getVerb());
+ } else {
+ $message->setMessage($message->getMessage(), [], $comment->getVerb());
+ }
+ }
+}
diff --git a/lib/Chat/ReactionManager.php b/lib/Chat/ReactionManager.php
new file mode 100644
index 000000000..4b4576ea4
--- /dev/null
+++ b/lib/Chat/ReactionManager.php
@@ -0,0 +1,153 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
+ *
+ * @author Vitor Mattos <vitor@php.rio>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Talk\Chat;
+
+use OCA\Talk\Exceptions\ReactionAlreadyExistsException;
+use OCA\Talk\Exceptions\ReactionNotSupportedException;
+use OCA\Talk\Exceptions\ReactionOutOfContextException;
+use OCA\Talk\Participant;
+use OCA\Talk\Room;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\Comments\IComment;
+use OCP\Comments\ICommentsManager;
+use OCP\Comments\NotFoundException;
+use OCP\IL10N;
+
+class ReactionManager {
+ /** @var ICommentsManager|CommentsManager */
+ private $commentsManager;
+ /** @var IL10N */
+ private $l;
+ /** @var MessageParser */
+ private $messageParser;
+ /** @var Notifier */
+ private $notifier;
+ /** @var ITimeFactory */
+ protected $timeFactory;
+
+ public function __construct(CommentsManager $commentsManager,
+ IL10N $l,
+ MessageParser $messageParser,
+ Notifier $notifier,
+ ITimeFactory $timeFactory) {
+ $this->commentsManager = $commentsManager;
+ $this->l = $l;
+ $this->messageParser = $messageParser;
+ $this->notifier = $notifier;
+ $this->timeFactory = $timeFactory;
+ }
+
+ public function addReactionMessage(Room $chat, Participant $participant, IComment $parentMessage, string $reaction): IComment {
+ try {
+ // Check if the user already reacted with the same reaction
+ $comment = $this->commentsManager->getReactionComment(
+ (int) $parentMessage->getId(),
+ $participant->getAttendee()->getActorType(),
+ $participant->getAttendee()->getActorId(),
+ $reaction
+ );
+ throw new ReactionAlreadyExistsException();
+ } catch (NotFoundException $e) {
+ }
+
+ $comment = $this->commentsManager->create(
+ $participant->getAttendee()->getActorType(),
+ $participant->getAttendee()->getActorId(),
+ 'chat',
+ (string) $chat->getId()
+ );
+ $comment->setParentId((string) $parentMessage->getId());
+ $comment->setMessage($reaction);
+ $comment->setVerb('reaction');
+ $this->commentsManager->save($comment);
+
+ $this->notifier->notifyReacted($chat, $parentMessage, $comment);
+ return $comment;
+ }
+
+ public function deleteReactionMessage(Participant $participant, int $messageId, string $reaction): IComment {
+ $comment = $this->commentsManager->getReactionComment(
+ $messageId,
+ $participant->getAttendee()->getActorType(),
+ $participant->getAttendee()->getActorId(),
+ $reaction
+ );
+ $comment->setMessage(
+ json_encode([
+ 'deleted_by_type' => $participant->getAttendee()->getActorType(),
+ 'deleted_by_id' => $participant->getAttendee()->getActorId(),
+ 'deleted_on' => $this->timeFactory->getDateTime()->getTimestamp(),
+ ])
+ );
+ $comment->setVerb('reaction_deleted');
+ $this->commentsManager->save($comment);
+ return $comment;
+ }
+
+ public function retrieveReactionMessages(Room $chat, Participant $participant, int $messageId, ?string $reaction): array {
+ if ($reaction) {
+ $comments = $this->commentsManager->retrieveAllReactionsWithSpecificReaction($messageId, $reaction);
+ } else {
+ $comments = $this->commentsManager->retrieveAllReactions($messageId);
+ }
+
+ foreach ($comments as $comment) {
+ $message = $this->messageParser->createMessage($chat, $participant, $comment, $this->l);
+ $this->messageParser->parseMessage($message);
+
+ $reactions[$comment->getMessage()][] = [
+ 'actorType' => $comment->getActorType(),
+ 'actorId' => $comment->getActorId(),
+ 'actorDisplayName' => $message->getActorDisplayName(),
+ 'timestamp' => $comment->getCreationDateTime()->getTimestamp(),
+ ];
+ }
+ return $reactions;
+ }
+
+ /**
+ * @param Room $chat
+ * @param string $messageId
+ * @return IComment
+ * @throws NotFoundException
+ * @throws ReactionNotSupportedException
+ * @throws ReactionOutOfContextException
+ */
+ public function getCommentToReact(Room $chat, string $messageId): IComment {
+ if (!$this->commentsManager->supportReactions()) {
+ throw new ReactionNotSupportedException();
+ }
+ $comment = $this->commentsManager->get($messageId);
+
+ if ($comment->getObjectType() !== 'chat'
+ || $comment->getObjectId() !== (string) $chat->getId()
+ || $comment->getVerb() !== 'comment') {
+ throw new ReactionOutOfContextException();
+ }
+
+ return $comment;
+ }
+}
diff --git a/lib/Controller/ReactionController.php b/lib/Controller/ReactionController.php
new file mode 100644
index 000000000..a16820ade
--- /dev/null
+++ b/lib/Controller/ReactionController.php
@@ -0,0 +1,130 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
+ *
+ * @author Vitor Mattos <vitor@php.rio>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Talk\Controller;
+
+use OCA\Talk\Chat\ReactionManager;
+use OCA\Talk\Exceptions\ReactionAlreadyExistsException;
+use OCA\Talk\Exceptions\ReactionNotSupportedException;
+use OCA\Talk\Exceptions\ReactionOutOfContextException;
+use OCP\AppFramework\Http;
+use OCP\AppFramework\Http\DataResponse;
+use OCP\Comments\NotFoundException;
+use OCP\IRequest;
+
+class ReactionController extends AEnvironmentAwareController {
+ /** @var ReactionManager */
+ private $reactionManager;
+
+ public function __construct(string $appName,
+ IRequest $request,
+ ReactionManager $reactionManager) {
+ parent::__construct($appName, $request);
+ $this->reactionManager = $reactionManager;
+ }
+
+ /**
+ * @NoAdminRequired
+ * @RequireParticipant
+ * @RequireReadWriteConversation
+ * @RequireModeratorOrNoLobby
+ *
+ * @param int $messageId for reaction
+ * @param string $reaction the reaction emoji
+ * @return DataResponse
+ */
+ public function react(int $messageId, string $reaction): DataResponse {
+ try {
+ $chat = $this->getRoom();
+ $participant = $this->getParticipant();
+ $parentMessage = $this->reactionManager->getCommentToReact($chat, (string) $messageId);
+ $this->reactionManager->addReactionMessage($chat, $participant, $parentMessage, $reaction);
+ } catch (NotFoundException $e) {
+ return new DataResponse([], Http::STATUS_NOT_FOUND);
+ } catch (ReactionAlreadyExistsException $e) {
+ return new DataResponse([], Http::STATUS_OK);
+ } catch (ReactionNotSupportedException | ReactionOutOfContextException | \Exception $e) {
+ return new DataResponse([], Http::STATUS_BAD_REQUEST);
+ }
+ return new DataResponse([], Http::STATUS_CREATED);
+ }
+
+ /**
+ * @NoAdminRequired
+ * @RequireParticipant
+ * @RequireReadWriteConversation
+ * @RequireModeratorOrNoLobby
+ *
+ * @param int $messageId for reaction
+ * @param string $reaction the reaction emoji
+ * @return DataResponse
+ */
+ public function delete(int $messageId, string $reaction): DataResponse {
+ $participant = $this->getParticipant();
+ try {
+ // Verify that messageId is part of the room
+ $this->reactionManager->getCommentToReact($this->getRoom(), (string) $messageId);
+ } catch (ReactionNotSupportedException | ReactionOutOfContextException | NotFoundException $e) {
+ return new DataResponse([], Http::STATUS_NOT_FOUND);
+ }
+
+ try {
+ $this->reactionManager->deleteReactionMessage(
+ $participant,
+ $messageId,
+ $reaction
+ );
+ } catch (NotFoundException $e) {
+ return new DataResponse([], Http::STATUS_NOT_FOUND);
+ } catch (\Exception $e) {
+ return new DataResponse([], Http::STATUS_BAD_REQUEST);
+ }
+
+ return new DataResponse([], Http::STATUS_OK);
+ }
+
+ /**
+ * @NoAdminRequired
+ * @RequireParticipant
+ * @RequireReadWriteConversation
+ * @RequireModeratorOrNoLobby
+ *
+ * @param int $messageId for reaction
+ * @param string|null $reaction the reaction emoji
+ * @return DataResponse
+ */
+ public function getReactions(int $messageId, ?string $reaction): DataResponse {
+ try {
+ // Verify that messageId is part of the room
+ $this->reactionManager->getCommentToReact($this->getRoom(), (string) $messageId);
+ } catch (ReactionNotSupportedException | ReactionOutOfContextException | NotFoundException $e) {
+ return new DataResponse([], Http::STATUS_NOT_FOUND);
+ }
+
+ $reactions = $this->reactionManager->retrieveReactionMessages($this->getRoom(), $this->getParticipant(), $messageId, $reaction);
+
+ return new DataResponse($reactions, Http::STATUS_OK);
+ }
+}
diff --git a/lib/Exceptions/ReactionAlreadyExistsException.php b/lib/Exceptions/ReactionAlreadyExistsException.php
new file mode 100644
index 000000000..399856d9b
--- /dev/null
+++ b/lib/Exceptions/ReactionAlreadyExistsException.php
@@ -0,0 +1,29 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2021 Vitor Mattos <vitor@php.rio>
+ *
+ * @author Vitor Mattos <vitor@php.rio>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Talk\Exceptions;
+
+class ReactionAlreadyExistsException extends \OutOfBoundsException {
+}
diff --git a/lib/Exceptions/ReactionNotSupportedException.php b/lib/Exceptions/ReactionNotSupportedException.php
new file mode 100644
index 000000000..478f6a80a
--- /dev/null
+++ b/lib/Exceptions/ReactionNotSupportedException.php
@@ -0,0 +1,29 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2022, Vitor Mattos <vitor@php.rio>
+ *
+ * @author Vitor Mattos <vitor@php.rio>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Talk\Exceptions;
+
+class ReactionNotSupportedException extends \Exception {
+}
diff --git a/lib/Exceptions/ReactionOutOfContextException.php b/lib/Exceptions/ReactionOutOfContextException.php
new file mode 100644
index 000000000..499dee69b
--- /dev/null
+++ b/lib/Exceptions/ReactionOutOfContextException.php
@@ -0,0 +1,29 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2022, Vitor Mattos <vitor@php.rio>
+ *
+ * @author Vitor Mattos <vitor@php.rio>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCA\Talk\Exceptions;
+
+class ReactionOutOfContextException extends \Exception {
+}
diff --git a/lib/Manager.php b/lib/Manager.php
index c3bb42004..993ade134 100644
--- a/lib/Manager.php
+++ b/lib/Manager.php
@@ -233,6 +233,7 @@ class Manager {
'reference_id' => $row['comment_reference_id'] ?? null,
'creation_timestamp' => $row['comment_creation_timestamp'],
'latest_child_timestamp' => $row['comment_latest_child_timestamp'],
+ 'reactions' => $row['comment_reactions'],
]);
}
@@ -1185,5 +1186,6 @@ class Manager {
}
$query->selectAlias('c.creation_timestamp', 'comment_creation_timestamp');
$query->selectAlias('c.latest_child_timestamp', 'comment_latest_child_timestamp');
+ $query->selectAlias('c.reactions', 'comment_reactions');
}
}
diff --git a/lib/Model/Message.php b/lib/Model/Message.php
index 30f1fa6d9..118adad72 100644
--- a/lib/Model/Message.php
+++ b/lib/Model/Message.php
@@ -163,6 +163,7 @@ class Message {
return $this->getMessageType() !== 'system' &&
$this->getMessageType() !== 'command' &&
$this->getMessageType() !== 'comment_deleted' &&
+ $this->getMessageType() !== 'reaction' &&
\in_array($this->getActorType(), [Attendee::ACTOR_USERS, Attendee::ACTOR_GUESTS]);
}
@@ -180,6 +181,7 @@ class Message {
'messageType' => $this->getMessageType(),
'isReplyable' => $this->isReplyable(),
'referenceId' => (string) $this->getComment()->getReferenceId(),
+ 'reactions' => $this->getComment()->getReactions(),
];
if ($this->getMessageType() === 'comment_deleted') {
diff --git a/lib/Notification/Notifier.php b/lib/Notification/Notifier.php
index e1100880f..529df5198 100644
--- a/lib/Notification/Notifier.php
+++ b/lib/Notification/Notifier.php
@@ -260,7 +260,7 @@ class Notifier implements INotifier {
}
return $this->parseCall($notification, $room, $l);
}
- if ($subject === 'reply' || $subject === 'mention' || $subject === 'chat') {
+ if ($subject === 'reply' || $subject === 'mention' || $subject === 'chat' || $subject === 'reaction') {
return $this->parseChatMessage($notification, $room, $participant, $l);
}
@@ -456,6 +456,27 @@ class Notifier implements INotifier {
$subject = $l->t('A guest replied to your message in conversation {call}');
}
}
+ } elseif ($notification->getSubject() === 'reaction') {
+ $richSubjectParameters['reaction'] = [
+ 'type' => 'highlight',
+ 'id' => $subjectParameters['reaction'],
+ 'name' => $subjectParameters['reaction'],
+ ];
+
+ if ($room->getType() === Room::TYPE_ONE_TO_ONE) {
+ $subject = $l->t('{user} reacted with {reaction} to your private message');
+ } elseif ($richSubjectUser) {
+ $subject = $l->t('{user} reacted with {reaction} to your message in conversation {call}');
+ } elseif (!$isGuest) {
+ $subject = $l->t('A deleted user reacted with {reaction} to your message in conversation {call}');
+ } else {
+ try {
+ $richSubjectParameters['guest'] = $this->getGuestParameter($room, $comment->getActorId());
+ $subject = $l->t('{guest} (guest) reacted with {reaction} to your message in conversation {call}');
+ } catch (ParticipantNotFoundException $e) {
+ $subject = $l->t('A guest reacted with {reaction} to your message in conversation {call}');
+ }
+ }
} elseif ($room->getType() === Room::TYPE_ONE_TO_ONE) {
$subject = $l->t('{user} mentioned you in a private conversation');
} elseif ($richSubjectUser) {
diff --git a/mkdocs.yml b/mkdocs.yml
index 28860dae3..d83858d64 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -26,6 +26,7 @@ nav:
- 'Participants management': 'participant.md'
- 'Call management': 'call.md'
- 'Chat management': 'chat.md'
+ - 'Reaction management': 'reaction.md'
- 'Webinar management': 'webinar.md'
- 'Settings': 'settings.md'
- 'Integration by other apps': 'integration.md'
diff --git a/tests/integration/features/bootstrap/FeatureContext.php b/tests/integration/features/bootstrap/FeatureContext.php
index 58814dd15..3bdca6641 100644
--- a/tests/integration/features/bootstrap/FeatureContext.php
+++ b/tests/integration/features/bootstrap/FeatureContext.php
@@ -447,8 +447,8 @@ class FeatureContext implements Context, SnippetAcceptingContext {
return $attendee;
}, $result);
- usort($expected, [$this, 'sortAttendees']);
- usort($result, [$this, 'sortAttendees']);
+ usort($expected, [self::class, 'sortAttendees']);
+ usort($result, [self::class, 'sortAttendees']);
Assert::assertEquals($expected, $result);
} else {
@@ -477,7 +477,7 @@ class FeatureContext implements Context, SnippetAcceptingContext {
}
}
- protected function sortAttendees(array $a1, array $a2): int {
+ protected static function sortAttendees(array $a1, array $a2): int {
if (array_key_exists('participantType', $a1) && array_key_exists('participantType', $a2) && $a1['participantType'] !== $a2['participantType']) {
return $a1['participantType'] <=> $a2['participantType'];
}
@@ -1559,6 +1559,7 @@ class FeatureContext implements Context, SnippetAcceptingContext {
}
$includeParents = in_array('parentMessage', $formData->getRow(0), true);
$includeReferenceId = in_array('referenceId', $formData->getRow(0), true);
+ $includeReactions = in_array('reactions', $formData->getRow(0), true);
$count = count($formData->getHash());
Assert::assertCount($count, $messages, 'Message count does not match');
@@ -1567,7 +1568,7 @@ class FeatureContext implements Context, SnippetAcceptingContext {
$messages[$i]['messageParameters'] = 'IGNORE';
}
}
- Assert::assertEquals($formData->getHash(), array_map(function ($message) use ($includeParents, $includeReferenceId) {
+ Assert::assertEquals($formData->getHash(), array_map(function ($message) use ($includeParents, $includeReferenceId, $includeReactions) {
$data = [
'room' => self::$tokenToIdentifier[$message['token']],
'actorType' => $message['actorType'],
@@ -1584,6 +1585,9 @@ class FeatureContext implements Context, SnippetAcceptingContext {
if ($includeReferenceId) {
$data['referenceId'] = $message['referenceId'];
}
+ if ($includeReactions) {
+ $data['reactions'] = json_encode($message['reactions'], JSON_UNESCAPED_UNICODE);
+ }
return $data;
}, $messages));
}
@@ -2079,6 +2083,57 @@ class FeatureContext implements Context, SnippetAcceptingContext {
$this->setCurrentUser($currentUser);
}
+ /**
+ * @Given /^user "([^"]*)" (delete react|react) with "([^"]*)" on message "([^"]*)" to room "([^"]*)" with (\d+)(?: \((v1)\))?$/
+ */
+ public function userReactWithOnMessageToRoomWith(string $user, string $action, string $reaction, string $message, string $identifier, int $statusCode, string $apiVersion = 'v1'): void {
+ $token = self::$identifierToToken[$identifier];
+ $messageId = self::$messages[$message];
+ $this->setCurrentUser($user);
+ $verb = $action === 'react' ? 'POST' : 'DELETE';
+ $this->sendRequest($verb, '/apps/spreed/api/' . $apiVersion . '/reaction/' . $token . '/' . $messageId, [
+ 'reaction' => $reaction
+ ]);
+ $this->assertStatusCode($this->response, $statusCode);
+ }
+
+ /**
+ * @Given /^user "([^"]*)" retrieve reactions "([^"]*)" of message "([^"]*)" in room "([^"]*)" with (\d+)(?: \((v1)\))?$/
+ */
+ public function userRetrieveReactionsOfMessageInRoomWith(string $user, string $reaction, string $message, string $identifier, int $statusCode, string $apiVersion = 'v1', TableNode $formData): void {
+ $token = self::$identifierToToken[$identifier];
+ $messageId = self::$messages[$message];
+ $this->setCurrentUser($user);
+ $reaction = $reaction !== 'all' ? '?reaction=' . $reaction : '';
+ $this->sendRequest('GET', '/apps/spreed/api/' . $apiVersion . '/reaction/' . $token . '/' . $messageId . $reaction);
+ $this->assertStatusCode($this->response, $statusCode);
+ $this->assertReactionList($formData);
+ }
+
+ private function assertReactionList(TableNode $formData): void {
+ $expected = [];
+ foreach ($formData->getHash() as $row) {
+ $reaction = $row['reaction'];
+ unset($row['reaction']);
+ $expected[$reaction][] = $row;
+ }
+
+ $result = $this->getDataFromResponse($this->response);
+ $result = array_map(static function ($reaction, $list) use ($expected): array {
+ $list = array_map(function ($reaction) {
+ unset($reaction['timestamp']);
+ return $reaction;
+ }, $list);
+ Assert::assertCount(count($list), $expected[$reaction], 'Reaction count by type does not match');
+
+ usort($expected[$reaction], [self::class, 'sortAttendees']);
+ usort($list, [self::class, 'sortAttendees']);
+ Assert::assertEquals($expected[$reaction], $list, 'Reaction list by type does not match');
+ return $list;
+ }, array_keys($result), array_values($result));
+ Assert::assertCount(count($expected), $result, 'Reaction count does not match');
+ }
+
/*
* Requests
*/
diff --git a/tests/integration/features/reaction/react.feature b/tests/integration/features/reaction/react.feature
new file mode 100644
index 000000000..e95805816
--- /dev/null
+++ b/tests/integration/features/reaction/react.feature
@@ -0,0 +1,68 @@
+Feature: reaction/react
+ Background:
+ Given user "participant1" exists
+ Given user "participant2" exists
+
+ Scenario: React to message with success
+ Given user "participant1" creates room "room" (v4)
+ | roomType | 3 |
+ | roomName | room |
+ And user "participant1" adds user "participant2" to room "room" with 200 (v4)
+ And user "participant1" sends message "Message 1" to room "room" with 201
+ And user "participant2" react with "👍" on message "Message 1" to room "room" with 201
+ Then user "participant1" sees the following messages in room "room" with 200
+ | room | actorType | actorId | actorDisplayName | message | messageParameters | reactions |
+ | room | users | participant1 | participant1-displayname | Message 1 | [] | {"👍":1} |
+ And user "participant1" react with "👍" on message "Message 1" to room "room" with 201
+ Then user "participant1" sees the following messages in room "room" with 200
+ | room | actorType | actorId | actorDisplayName | message | messageParameters | reactions |
+ | room | users | participant1 | participant1-displayname | Message 1 | [] | {"👍":2} |
+
+ Scenario: React two times to same message with the same reaction
+ Given user "participant1" creates room "room" (v4)
+ | roomType | 3 |
+ | roomName | room |
+ And user "participant1" adds user "participant2" to room "room" with 200 (v4)
+ And user "participant1" sends message "Message 1" to room "room" with 201
+ And user "participant2" react with "👍" on message "Message 1" to room "room" with 201
+ And user "participant2" react with "👍" on message "Message 1" to room "room" with 200
+ Then user "participant1" sees the following messages in room "room" with 200
+ | room | actorType | actorId | actorDisplayName | message | messageParameters | reactions |
+ | room | users | participant1 | participant1-displayname | Message 1 | [] | {"👍":1} |
+
+ Scenario: Delete reaction to message with success
+ Given user "participant1" creates room "room" (v4)
+ | roomType | 3 |
+ | roomName | room |
+ And user "participant1" adds user "participant2" to room "room" with 200 (v4)
+ And user "participant1" sends message "Message 1" to room "room" with 201
+ And user "participant2" react with "👍" on message "Message 1" to room "room" with 201
+ Then user "participant1" sees the following messages in room "room" with 200
+ | room | actorType | actorId | actorDisplayName | message | messageParameters | reactions |
+ | room | users | participant1 | participant1-displayname | Message 1 | [] | {"👍":1} |
+ And user "participant2" delete react with "👍" on message "Message 1" to room "room" with 200
+ Then user "participant1" sees the following messages in room "room" with 200
+ | room | actorType | actorId | actorDisplayName | message | messageParameters | reactions |
+ | room | users | participant1 | participant1-displayname | Message 1 | [] | [] |
+
+ Scenario: Retrieve reactions of a message
+ Given user "participant1" creates room "room" (v4)
+ | roomType | 3 |
+ | roomName | room |
+ And user "participant1" adds user "participant2" to room "room" with 200 (v4)
+ And user "participant1" sends message "Message 1" to room "room" with 201
+ And user "participant1" react with "👍" on message "Message 1" to room "room" with 201
+ And user "participant2" react with "👍" on message "Message 1" to room "room" with 201
+ Then user "participant1" retrieve reactions "👍" of message "Message 1" in room "room" with 200
+ | actorType | actorId | actorDisplayName | reaction |
+ | users | participant1 | participant1-displayname | 👍 |
+ | users | participant2 | participant2-displayname | 👍 |
+ And user "participant2" react with "👎" on message "Message 1" to room "room" with 201
+ And user "participant1" retrieve reactions "👎" of message "Message 1" in room "room" with 200
+ | actorType | actorId | actorDisplayName | reaction |
+ | users | participant2 | participant2-displayname | 👎 |
+ And user "participant1" retrieve reactions "all" of message "Message 1" in room "room" with 200
+ | actorType | actorId | actorDisplayName | reaction |
+ | users | participant1 | participant1-displayname | 👍 |
+ | users | participant2 | participant2-displayname | 👎 |
+ | users | participant2 | participant2-displayname | 👍 |
diff --git a/tests/php/CapabilitiesTest.php b/tests/php/CapabilitiesTest.php
index 7edc4f080..a247a18bc 100644
--- a/tests/php/CapabilitiesTest.php
+++ b/tests/php/CapabilitiesTest.php
@@ -26,6 +26,7 @@ declare(strict_types=1);
namespace OCA\Talk\Tests\Unit;
use OCA\Talk\Capabilities;
+use OCA\Talk\Chat\CommentsManager;
use OCA\Talk\Config;
use OCA\Talk\Participant;
use OCP\Capabilities\IPublicCapability;
@@ -41,6 +42,8 @@ class CapabilitiesTest extends TestCase {
protected $serverConfig;
/** @var Config|MockObject */
protected $talkConfig;
+ /** @var CommentsManager|MockObject */
+ protected $commentsManager;
/** @var IUserSession|MockObject */
protected $userSession;
/** @var array */
@@ -50,7 +53,11 @@ class CapabilitiesTest extends TestCase {
parent::setUp();
$this->serverConfig = $this->createMock(IConfig::class);
$this->talkConfig = $this->createMock(Config::class);
+ $this->commentsManager = $this->createMock(CommentsManager::class);
$this->userSession = $this->createMock(IUserSession::class);
+ $this->commentsManager->expects($this->any())
+ ->method('supportReactions')
+ ->willReturn(true);
$this->baseFeatures = [
'audio',
@@ -96,6 +103,7 @@ class CapabilitiesTest extends TestCase {
'direct-mention-flag',
'notification-calls',
'conversation-permissions',
+ 'reactions',
];
}
@@ -103,6 +111,7 @@ class CapabilitiesTest extends TestCase {
$capabilities = new Capabilities(
$this->serverConfig,
$this->talkConfig,
+ $this->commentsManager,
$this->userSession
);
@@ -160,6 +169,7 @@ class CapabilitiesTest extends TestCase {
$capabilities = new Capabilities(
$this->serverConfig,
$this->talkConfig,
+ $this->commentsManager,
$this->userSession
);
@@ -246,6 +256,7 @@ class CapabilitiesTest extends TestCase {
$capabilities = new Capabilities(
$this->serverConfig,
$this->talkConfig,
+ $this->commentsManager,
$this->userSession
);
diff --git a/tests/php/Chat/NotifierTest.php b/tests/php/Chat/NotifierTest.php
index f3fc6df01..2d09b9cb9 100644
--- a/tests/php/Chat/NotifierTest.php
+++ b/tests/php/Chat/NotifierTest.php
@@ -129,20 +129,24 @@ class NotifierTest extends TestCase {
/**
* @return Room|MockObject
*/
- private function getRoom() {
+ private function getRoom($settings = []) {
/** @var Room|MockObject */
$room = $this->createMock(Room::class);
$room->expects($this->any())
->method('getParticipant')
- ->willReturnCallback(function (string $actorId) use ($room): Participant {
+ ->willReturnCallback(function (string $actorId) use ($room, $settings): Participant {
if ($actorId === 'userNotInOneToOneChat') {
throw new ParticipantNotFoundException();
}
- $attendee = Attendee::fromRow([
+ $attendeeRow = [
'actor_type' => 'user',
'actor_id' => $actorId,
- ]);
+ ];
+ if (isset($settings['attendee'][$actorId])) {
+ $attendeeRow = array_merge($attendeeRow, $settings['attendee'][$actorId]);
+ }
+ $attendee = Attendee::fromRow($attendeeRow);
return new Participant($room, $attendee, null);
});
@@ -374,6 +378,44 @@ class NotifierTest extends TestCase {
}
/**
+ * @dataProvider dataNotifyReacted
+ */
+ public function testNotifyReacted(int $notify, int $notifyType, int $roomType, string $authorId): void {
+ $this->notificationManager->expects($this->exactly($notify))
+ ->method('notify');
+
+ $room = $this->getRoom([
+ 'attendee' => [
+ 'testUser' => [
+ 'notificationLevel' => $notifyType,
+ ]
+ ]
+ ]);
+ $room->method('getType')
+ ->willReturn($roomType);
+ $comment = $this->newComment('108', 'users', 'testUser', new \DateTime('@' . 1000000016), 'message');
+ $reaction = $this->newComment('108', 'users', $authorId, new \DateTime('@' . 1000000016), 'message');
+
+ $notifier = $this->getNotifier([]);
+ $notifier->notifyReacted($room, $comment, $reaction);
+ }
+
+ public function dataNotifyReacted(): array {
+ return [
+ 'author react to own message' =>
+ [0, Participant::NOTIFY_MENTION, Room::TYPE_GROUP, 'testUser'],
+ 'notify never' =>
+ [0, Participant::NOTIFY_NEVER, Room::TYPE_GROUP, 'testUser2'],
+ 'notify default, not one to one' =>
+ [0, Participant::NOTIFY_DEFAULT, Room::TYPE_GROUP, 'testUser2'],
+ 'notify default, one to one' =>
+ [1, Participant::NOTIFY_DEFAULT, Room::TYPE_ONE_TO_ONE, 'testUser2'],
+ 'notify always' =>
+ [1, Participant::NOTIFY_ALWAYS, Room::TYPE_GROUP, 'testUser2'],
+ ];
+ }
+
+ /**
* @dataProvider dataGetMentionedUsers
*/
public function testGetMentionedUsers(string $message, array $expectedReturn): void {