summaryrefslogtreecommitdiffstats
path: root/lib/Service/RoomFormatter.php
diff options
context:
space:
mode:
Diffstat (limited to 'lib/Service/RoomFormatter.php')
-rw-r--r--lib/Service/RoomFormatter.php375
1 files changed, 375 insertions, 0 deletions
diff --git a/lib/Service/RoomFormatter.php b/lib/Service/RoomFormatter.php
new file mode 100644
index 000000000..8e1f72c9a
--- /dev/null
+++ b/lib/Service/RoomFormatter.php
@@ -0,0 +1,375 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2023 Joas Schilling <coding@schilljs.com>
+ *
+ * @author Joas Schilling <coding@schilljs.com>
+ *
+ * @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\Service;
+
+use OCA\Talk\Chat\ChatManager;
+use OCA\Talk\Chat\MessageParser;
+use OCA\Talk\Config;
+use OCA\Talk\Model\Attendee;
+use OCA\Talk\Model\BreakoutRoom;
+use OCA\Talk\Model\Session;
+use OCA\Talk\Participant;
+use OCA\Talk\Room;
+use OCA\Talk\Webinary;
+use OCP\App\IAppManager;
+use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\Comments\IComment;
+use OCP\IConfig;
+use OCP\IL10N;
+use OCP\IUser;
+use OCP\IUserManager;
+use OCP\UserStatus\IManager;
+use OCP\UserStatus\IUserStatus;
+
+class RoomFormatter {
+ public function __construct(
+ protected Config $talkConfig,
+ protected AvatarService $avatarService,
+ protected ParticipantService $participantService,
+ protected ChatManager $chatManager,
+ protected MessageParser $messageParser,
+ protected IConfig $serverConfig,
+ protected ITimeFactory $timeFactory,
+ protected IAppManager $appManager,
+ protected IManager $userStatusManager,
+ protected IUserManager $userManager,
+ protected IL10N $l10n,
+ protected ?string $userId,
+ ) {
+ }
+
+ public function formatRoom(
+ string $responseFormat,
+ array $commonReadMessages,
+ Room $room,
+ ?Participant $currentParticipant,
+ ?array $statuses = null,
+ bool $isSIPBridgeRequest = false,
+ bool $isListingBreakoutRooms = false,
+ ): array {
+ return $this->formatRoomV4(
+ $responseFormat,
+ $commonReadMessages,
+ $room,
+ $currentParticipant,
+ $statuses,
+ $isSIPBridgeRequest,
+ $isListingBreakoutRooms,
+ );
+ }
+
+ public function formatRoomV4(
+ string $responseFormat,
+ array $commonReadMessages,
+ Room $room,
+ ?Participant $currentParticipant,
+ ?array $statuses,
+ bool $isSIPBridgeRequest,
+ bool $isListingBreakoutRooms,
+ ): array {
+ $roomData = [
+ 'id' => $room->getId(),
+ 'token' => $room->getToken(),
+ 'type' => $room->getType(),
+ 'name' => '',
+ 'displayName' => '',
+ 'objectType' => '',
+ 'objectId' => '',
+ 'participantType' => Participant::GUEST,
+ 'participantFlags' => Participant::FLAG_DISCONNECTED,
+ 'readOnly' => Room::READ_WRITE,
+ 'hasPassword' => $room->hasPassword(),
+ 'hasCall' => false,
+ 'callStartTime' => 0,
+ 'callRecording' => Room::RECORDING_NONE,
+ 'canStartCall' => false,
+ 'lastActivity' => 0,
+ 'lastReadMessage' => 0,
+ 'unreadMessages' => 0,
+ 'unreadMention' => false,
+ 'unreadMentionDirect' => false,
+ 'isFavorite' => false,
+ 'canLeaveConversation' => false,
+ 'canDeleteConversation' => false,
+ 'notificationLevel' => Participant::NOTIFY_NEVER,
+ 'notificationCalls' => Participant::NOTIFY_CALLS_OFF,
+ 'lobbyState' => Webinary::LOBBY_NONE,
+ 'lobbyTimer' => 0,
+ 'lastPing' => 0,
+ 'sessionId' => '0',
+ 'lastMessage' => [],
+ 'sipEnabled' => Webinary::SIP_DISABLED,
+ 'actorType' => '',
+ 'actorId' => '',
+ 'attendeeId' => 0,
+ 'permissions' => Attendee::PERMISSIONS_CUSTOM,
+ 'attendeePermissions' => Attendee::PERMISSIONS_CUSTOM,
+ 'callPermissions' => Attendee::PERMISSIONS_CUSTOM,
+ 'defaultPermissions' => Attendee::PERMISSIONS_CUSTOM,
+ 'canEnableSIP' => false,
+ 'attendeePin' => '',
+ 'description' => '',
+ 'lastCommonReadMessage' => 0,
+ 'listable' => Room::LISTABLE_NONE,
+ 'callFlag' => Participant::FLAG_DISCONNECTED,
+ 'messageExpiration' => 0,
+ 'avatarVersion' => $this->avatarService->getAvatarVersion($room),
+ 'breakoutRoomMode' => BreakoutRoom::MODE_NOT_CONFIGURED,
+ 'breakoutRoomStatus' => BreakoutRoom::STATUS_STOPPED,
+ ];
+
+ $lastActivity = $room->getLastActivity();
+ if ($lastActivity instanceof \DateTimeInterface) {
+ $lastActivity = $lastActivity->getTimestamp();
+ } else {
+ $lastActivity = 0;
+ }
+
+ $lobbyTimer = $room->getLobbyTimer();
+ if ($lobbyTimer instanceof \DateTimeInterface) {
+ $lobbyTimer = $lobbyTimer->getTimestamp();
+ } else {
+ $lobbyTimer = 0;
+ }
+
+ if ($isSIPBridgeRequest
+ || ($isListingBreakoutRooms && !$currentParticipant instanceof Participant)
+ || ($room->getListable() !== Room::LISTABLE_NONE && !$currentParticipant instanceof Participant)
+ ) {
+ return array_merge($roomData, [
+ 'name' => $room->getName(),
+ 'displayName' => $room->getDisplayName($isSIPBridgeRequest || $this->userId === null ? '' : $this->userId),
+ 'objectType' => $room->getObjectType(),
+ 'objectId' => $room->getObjectId(),
+ 'readOnly' => $room->getReadOnly(),
+ 'hasCall' => $room->getActiveSince() instanceof \DateTimeInterface,
+ 'lastActivity' => $lastActivity,
+ 'callFlag' => $room->getCallFlag(),
+ 'lobbyState' => $room->getLobbyState(),
+ 'lobbyTimer' => $lobbyTimer,
+ 'sipEnabled' => $room->getSIPEnabled(),
+ 'listable' => $room->getListable(),
+ 'breakoutRoomMode' => $room->getBreakoutRoomMode(),
+ 'breakoutRoomStatus' => $room->getBreakoutRoomStatus(),
+ 'callStartTime' => $room->getActiveSince() instanceof \DateTimeInterface ? $room->getActiveSince()->getTimestamp() : 0,
+ 'callRecording' => $room->getCallRecording(),
+ ]);
+ }
+
+ if (!$currentParticipant instanceof Participant) {
+ return $roomData;
+ }
+
+ $attendee = $currentParticipant->getAttendee();
+ $userId = $attendee->getActorType() === Attendee::ACTOR_USERS ? $attendee->getActorId() : '';
+
+ $roomData = array_merge($roomData, [
+ 'name' => $room->getName(),
+ 'displayName' => $room->getDisplayName($userId),
+ 'objectType' => $room->getObjectType(),
+ 'objectId' => $room->getObjectId(),
+ 'participantType' => $attendee->getParticipantType(),
+ 'readOnly' => $room->getReadOnly(),
+ 'hasCall' => $room->getActiveSince() instanceof \DateTimeInterface,
+ 'callStartTime' => $room->getActiveSince() instanceof \DateTimeInterface ? $room->getActiveSince()->getTimestamp() : 0,
+ 'callRecording' => $room->getCallRecording(),
+ 'lastActivity' => $lastActivity,
+ 'callFlag' => $room->getCallFlag(),
+ 'isFavorite' => $attendee->isFavorite(),
+ 'notificationLevel' => $attendee->getNotificationLevel(),
+ 'notificationCalls' => $attendee->getNotificationCalls(),
+ 'lobbyState' => $room->getLobbyState(),
+ 'lobbyTimer' => $lobbyTimer,
+ 'actorType' => $attendee->getActorType(),
+ 'actorId' => $attendee->getActorId(),
+ 'attendeeId' => $attendee->getId(),
+ 'permissions' => $currentParticipant->getPermissions(),
+ 'attendeePermissions' => $attendee->getPermissions(),
+ 'callPermissions' => $room->getCallPermissions(),
+ 'defaultPermissions' => $room->getDefaultPermissions(),
+ 'description' => $room->getDescription(),
+ 'listable' => $room->getListable(),
+ 'messageExpiration' => $room->getMessageExpiration(),
+ 'breakoutRoomMode' => $room->getBreakoutRoomMode(),
+ 'breakoutRoomStatus' => $room->getBreakoutRoomStatus(),
+ ]);
+
+ if ($currentParticipant->getAttendee()->getReadPrivacy() === Participant::PRIVACY_PUBLIC) {
+ if (isset($commonReadMessages[$room->getId()])) {
+ $roomData['lastCommonReadMessage'] = $commonReadMessages[$room->getId()];
+ } else {
+ $roomData['lastCommonReadMessage'] = $this->chatManager->getLastCommonReadMessage($room);
+ }
+ }
+
+ if ($this->talkConfig->isSIPConfigured()) {
+ $roomData['sipEnabled'] = $room->getSIPEnabled();
+ if ($room->getSIPEnabled() !== Webinary::SIP_DISABLED) {
+ // Generate a PIN if the attendee is a user and doesn't have one.
+ $this->participantService->generatePinForParticipant($room, $currentParticipant);
+
+ $roomData['attendeePin'] = $attendee->getPin();
+ }
+ }
+
+ $session = $currentParticipant->getSession();
+ if ($session instanceof Session) {
+ $roomData = array_merge($roomData, [
+ 'participantFlags' => $session->getInCall(),
+ 'lastPing' => $session->getLastPing(),
+ 'sessionId' => $session->getSessionId(),
+ ]);
+ }
+
+ if ($roomData['notificationLevel'] === Participant::NOTIFY_DEFAULT) {
+ if ($currentParticipant->isGuest()) {
+ $roomData['notificationLevel'] = Participant::NOTIFY_NEVER;
+ } elseif ($room->getType() === Room::TYPE_ONE_TO_ONE || $room->getType() === Room::TYPE_ONE_TO_ONE_FORMER) {
+ $roomData['notificationLevel'] = Participant::NOTIFY_ALWAYS;
+ } else {
+ $adminSetting = (int)$this->serverConfig->getAppValue('spreed', 'default_group_notification', (string)Participant::NOTIFY_DEFAULT);
+ if ($adminSetting === Participant::NOTIFY_DEFAULT) {
+ $roomData['notificationLevel'] = Participant::NOTIFY_MENTION;
+ } else {
+ $roomData['notificationLevel'] = $adminSetting;
+ }
+ }
+ }
+
+ if ($room->getLobbyState() === Webinary::LOBBY_NON_MODERATORS &&
+ !$currentParticipant->hasModeratorPermissions() &&
+ !($currentParticipant->getPermissions() & Attendee::PERMISSIONS_LOBBY_IGNORE)) {
+ // No participants and chat messages for users in the lobby.
+ $roomData['hasCall'] = false;
+ return $roomData;
+ }
+
+ $roomData['canStartCall'] = $currentParticipant->canStartCall($this->serverConfig);
+
+ if ($attendee->getActorType() === Attendee::ACTOR_USERS) {
+ $currentUser = $this->userManager->get($attendee->getActorId());
+ if ($currentUser instanceof IUser) {
+ $lastReadMessage = $attendee->getLastReadMessage();
+ if ($lastReadMessage === -1) {
+ /*
+ * Because the migration from the old comment_read_markers was
+ * not possible in a programmatic way with a reasonable O(1) or O(n)
+ * but only with O(user×chat), we do the conversion here.
+ */
+ $lastReadMessage = $this->chatManager->getLastReadMessageFromLegacy($room, $currentUser);
+ $this->participantService->updateLastReadMessage($currentParticipant, $lastReadMessage);
+ }
+ if ($room->getLastMessage() && $lastReadMessage === (int)$room->getLastMessage()->getId()) {
+ // When the last message is the last read message, there are no unread messages,
+ // so we can save the query.
+ $roomData['unreadMessages'] = 0;
+ } else {
+ $roomData['unreadMessages'] = $this->chatManager->getUnreadCount($room, $lastReadMessage);
+ }
+
+ $lastMention = $attendee->getLastMentionMessage();
+ $lastMentionDirect = $attendee->getLastMentionDirect();
+ $roomData['unreadMention'] = $lastMention !== 0 && $lastReadMessage < $lastMention;
+ $roomData['unreadMentionDirect'] = $lastMentionDirect !== 0 && $lastReadMessage < $lastMentionDirect;
+ $roomData['lastReadMessage'] = $lastReadMessage;
+
+ $roomData['canDeleteConversation'] = $room->getType() !== Room::TYPE_ONE_TO_ONE
+ && $room->getType() !== Room::TYPE_ONE_TO_ONE_FORMER
+ && $currentParticipant->hasModeratorPermissions(false);
+ $roomData['canLeaveConversation'] = true;
+ $roomData['canEnableSIP'] =
+ $this->talkConfig->isSIPConfigured()
+ && !preg_match(Room::SIP_INCOMPATIBLE_REGEX, $room->getToken())
+ && ($room->getType() === Room::TYPE_GROUP || $room->getType() === Room::TYPE_PUBLIC)
+ && $currentParticipant->hasModeratorPermissions(false)
+ && $this->talkConfig->canUserEnableSIP($currentUser);
+ }
+ }
+
+ // FIXME This should not be done, but currently all the clients use it to get the avatar of the user …
+ if ($room->getType() === Room::TYPE_ONE_TO_ONE) {
+ $participants = json_decode($room->getName(), true);
+ foreach ($participants as $participant) {
+ if ($participant !== $attendee->getActorId()) {
+ $roomData['name'] = $participant;
+
+ if ($statuses === null
+ && $this->userId !== null
+ && $this->appManager->isEnabledForUser('user_status')) {
+ $statuses = $this->userStatusManager->getUserStatuses([$participant]);
+ }
+
+ if (isset($statuses[$participant])) {
+ $roomData['status'] = $statuses[$participant]->getStatus();
+ $roomData['statusIcon'] = $statuses[$participant]->getIcon();
+ $roomData['statusMessage'] = $statuses[$participant]->getMessage();
+ $roomData['statusClearAt'] = $statuses[$participant]->getClearAt();
+ } elseif (!empty($statuses)) {
+ $roomData['status'] = IUserStatus::OFFLINE;
+ $roomData['statusIcon'] = null;
+ $roomData['statusMessage'] = null;
+ $roomData['statusClearAt'] = null;
+ }
+ }
+ }
+ }
+
+ $lastMessage = $room->getLastMessage();
+ if ($lastMessage instanceof IComment) {
+ $roomData['lastMessage'] = $this->formatLastMessage(
+ $responseFormat,
+ $room,
+ $currentParticipant,
+ $lastMessage,
+ );
+ } else {
+ $roomData['lastMessage'] = [];
+ }
+
+ return $roomData;
+ }
+
+ public function formatLastMessage(
+ string $responseFormat,
+ Room $room,
+ Participant $participant,
+ IComment $lastMessage,
+ ): array {
+ $message = $this->messageParser->createMessage($room, $participant, $lastMessage, $this->l10n);
+ $this->messageParser->parseMessage($message);
+
+ if (!$message->getVisibility()) {
+ return [];
+ }
+
+ $now = $this->timeFactory->getDateTime();
+ $expireDate = $message->getComment()->getExpireDate();
+ if ($expireDate instanceof \DateTime && $expireDate < $now) {
+ return [];
+ }
+
+ return $message->toArray($responseFormat);
+ }
+}