summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/Chat/ChatManager.php43
-rw-r--r--lib/Chat/MessageParser.php2
-rw-r--r--lib/Chat/Parser/SystemMessage.php30
-rw-r--r--lib/Chat/SystemMessage/Listener.php40
-rw-r--r--lib/Command/User/TransferOwnership.php175
-rw-r--r--lib/Config.php25
-rw-r--r--lib/Controller/ChatController.php48
-rw-r--r--lib/Controller/RecordingController.php113
-rw-r--r--lib/Controller/RoomController.php44
-rw-r--r--lib/Dashboard/TalkWidget.php12
-rw-r--r--lib/Manager.php3
-rw-r--r--lib/Migration/Version16000Date20221208013745.php55
-rw-r--r--lib/Model/SelectHelper.php1
-rw-r--r--lib/Notification/Notifier.php10
-rw-r--r--lib/Room.php19
-rw-r--r--lib/Search/MessageSearch.php11
-rw-r--r--lib/Service/AvatarService.php11
-rw-r--r--lib/Service/ParticipantService.php3
-rw-r--r--lib/Service/RecordingService.php131
-rw-r--r--lib/Service/RoomService.php54
-rw-r--r--lib/Service/SIPBridgeService.php68
-rw-r--r--lib/Settings/Admin/AdminSettings.php6
-rw-r--r--lib/Signaling/Listener.php25
-rw-r--r--lib/TInitialState.php2
24 files changed, 871 insertions, 60 deletions
diff --git a/lib/Chat/ChatManager.php b/lib/Chat/ChatManager.php
index a55dd9848..6257b73f4 100644
--- a/lib/Chat/ChatManager.php
+++ b/lib/Chat/ChatManager.php
@@ -764,7 +764,7 @@ class ChatManager {
}
private function searchIsPartOfConversationNameOrAtAll(string $search, string $roomDisplayName): bool {
- if (stripos($roomDisplayName, $search) === 0) {
+ if (stripos($roomDisplayName, $search) !== false) {
return true;
}
if (strpos('all', $search) === 0) {
@@ -779,4 +779,45 @@ class ChatManager {
public function deleteExpiredMessages(): void {
$this->commentsManager->deleteCommentsExpiredAtObject('chat', '');
}
+
+ public function fileOfMessageExists(string $message): bool {
+ $parameters = $this->getParametersFromMessage($message);
+ try {
+ $this->shareProvider->getShareById($parameters['share']);
+ } catch (ShareNotFound $e) {
+ return false;
+ }
+ return true;
+ }
+
+ public function isSharedFile(string $message): bool {
+ $parameters = $this->getParametersFromMessage($message);
+ return !empty($parameters['share']);
+ }
+
+ protected function getParametersFromMessage(string $message): array {
+ $data = json_decode($message, true);
+ if (!\is_array($data) || !array_key_exists('parameters', $data) || !is_array($data['parameters'])) {
+ return [];
+ }
+ return $data['parameters'];
+ }
+
+ /**
+ * When receive a list of comments, filter the comments,
+ * removing all that have shares of file that no more exists
+ *
+ * @param IComment[] $comments
+ * @return IComment[]
+ */
+ public function filterCommentsWithNonExistingFiles(array $comments): array {
+ return array_filter($comments, function (IComment $comment) {
+ if ($this->isSharedFile($comment->getMessage())) {
+ if (!$this->fileOfMessageExists($comment->getMessage())) {
+ return false;
+ }
+ }
+ return true;
+ });
+ }
}
diff --git a/lib/Chat/MessageParser.php b/lib/Chat/MessageParser.php
index 766463c00..376af71f4 100644
--- a/lib/Chat/MessageParser.php
+++ b/lib/Chat/MessageParser.php
@@ -53,8 +53,8 @@ class MessageParser {
IUserManager $userManager,
ParticipantService $participantService) {
$this->dispatcher = $dispatcher;
- $this->userManager = $userManager;
$this->participantService = $participantService;
+ $this->userManager = $userManager;
}
public function createMessage(Room $room, Participant $participant, IComment $comment, IL10N $l): Message {
diff --git a/lib/Chat/Parser/SystemMessage.php b/lib/Chat/Parser/SystemMessage.php
index cefb79ed0..673d7121f 100644
--- a/lib/Chat/Parser/SystemMessage.php
+++ b/lib/Chat/Parser/SystemMessage.php
@@ -507,6 +507,16 @@ class SystemMessage {
if ($currentUserIsActor) {
$parsedMessage = $this->l->t('You cleared the history of the conversation');
}
+ } elseif ($message === 'avatar_set') {
+ $parsedMessage = $this->l->t('{actor} set the conversation avatar');
+ if ($currentUserIsActor) {
+ $parsedMessage = $this->l->t('You set the conversation avatar');
+ }
+ } elseif ($message === 'avatar_removed') {
+ $parsedMessage = $this->l->t('{actor} removed the conversation avatar');
+ if ($currentUserIsActor) {
+ $parsedMessage = $this->l->t('You removed the conversation avatar');
+ }
} elseif ($message === 'poll_closed') {
$parsedParameters['poll'] = $parameters['poll'];
$parsedParameters['poll']['id'] = (string) $parsedParameters['poll']['id'];
@@ -514,6 +524,26 @@ class SystemMessage {
if ($currentUserIsActor) {
$parsedMessage = $this->l->t('You ended the poll {poll}');
}
+ } elseif ($message === 'recording_started') {
+ $parsedMessage = $this->l->t('{actor} started the video recording');
+ if ($currentUserIsActor) {
+ $parsedMessage = $this->l->t('You started the video recording');
+ }
+ } elseif ($message === 'recording_stopped') {
+ $parsedMessage = $this->l->t('{actor} stopped the video recording');
+ if ($currentUserIsActor) {
+ $parsedMessage = $this->l->t('You stopped the video recording');
+ }
+ } elseif ($message === 'audio_recording_started') {
+ $parsedMessage = $this->l->t('{actor} started the audio recording');
+ if ($currentUserIsActor) {
+ $parsedMessage = $this->l->t('You started the audio recording');
+ }
+ } elseif ($message === 'audio_recording_stopped') {
+ $parsedMessage = $this->l->t('{actor} stopped the audio recording');
+ if ($currentUserIsActor) {
+ $parsedMessage = $this->l->t('You stopped the audio recording');
+ }
} elseif ($message === 'poll_voted') {
$parsedParameters['poll'] = $parameters['poll'];
$parsedParameters['poll']['id'] = (string) $parsedParameters['poll']['id'];
diff --git a/lib/Chat/SystemMessage/Listener.php b/lib/Chat/SystemMessage/Listener.php
index 2cc7f15ed..fecc384eb 100644
--- a/lib/Chat/SystemMessage/Listener.php
+++ b/lib/Chat/SystemMessage/Listener.php
@@ -100,6 +100,8 @@ class Listener implements IEventListener {
$dispatcher->addListener('OCP\Share::postShare', self::class . '::fixMimeTypeOfVoiceMessage');
$dispatcher->addListener(RoomShareProvider::EVENT_SHARE_FILE_AGAIN, self::class . '::fixMimeTypeOfVoiceMessage');
$dispatcher->addListener(Room::EVENT_AFTER_SET_MESSAGE_EXPIRATION, self::class . '::afterSetMessageExpiration');
+ $dispatcher->addListener(Room::EVENT_AFTER_SET_CALL_RECORDING, self::class . '::setCallRecording');
+ $dispatcher->addListener(Room::EVENT_AFTER_AVATAR_SET, self::class . '::avatarChanged');
}
public static function sendSystemMessageAboutBeginOfCall(ModifyParticipantEvent $event): void {
@@ -493,4 +495,42 @@ class Listener implements IEventListener {
]
);
}
+
+ public static function setCallRecording(ModifyRoomEvent $event): void {
+ $prefix = self::getCallRecordingPrefix($event);
+ $suffix = self::getCallRecordingSuffix($event);
+ $systemMessage = $prefix . 'recording_' . $suffix;
+
+ $listener = Server::get(self::class);
+ $listener->sendSystemMessage($event->getRoom(), $systemMessage);
+ }
+
+ private static function getCallRecordingSuffix(ModifyRoomEvent $event): string {
+ $newStatus = $event->getNewValue();
+ $startStatus = [
+ Room::RECORDING_VIDEO,
+ Room::RECORDING_AUDIO,
+ ];
+ $suffix = in_array($newStatus, $startStatus) ? 'started' : 'stopped';
+ return $suffix;
+ }
+
+ private static function getCallRecordingPrefix(ModifyRoomEvent $event): string {
+ $newValue = $event->getNewValue();
+ $oldValue = $event->getOldValue();
+ $isAudioStatus = $newValue === Room::RECORDING_AUDIO
+ || $oldValue === Room::RECORDING_AUDIO;
+ return $isAudioStatus ? 'audio_' : '';
+ }
+
+ public static function avatarChanged(ModifyRoomEvent $event): void {
+ if ($event->getNewValue()) {
+ $message = 'avatar_set';
+ } else {
+ $message = 'avatar_removed';
+ }
+
+ $listener = Server::get(self::class);
+ $listener->sendSystemMessage($event->getRoom(), $message);
+ }
}
diff --git a/lib/Command/User/TransferOwnership.php b/lib/Command/User/TransferOwnership.php
new file mode 100644
index 000000000..b26f1f852
--- /dev/null
+++ b/lib/Command/User/TransferOwnership.php
@@ -0,0 +1,175 @@
+<?php
+
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2022, 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\Command\User;
+
+use OC\Core\Command\Base;
+use OCA\Talk\Exceptions\ParticipantNotFoundException;
+use OCA\Talk\Manager;
+use OCA\Talk\Model\Attendee;
+use OCA\Talk\Participant;
+use OCA\Talk\Room;
+use OCA\Talk\Service\ParticipantService;
+use OCA\Talk\Service\RoomService;
+use OCP\IUserManager;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+class TransferOwnership extends Base {
+ private RoomService $roomService;
+ private ParticipantService $participantService;
+ private Manager $manager;
+ private IUserManager $userManager;
+
+ public function __construct(ParticipantService $participantService,
+ Manager $manager,
+ IUserManager $userManager) {
+ parent::__construct();
+ $this->participantService = $participantService;
+ $this->manager = $manager;
+ $this->userManager = $userManager;
+ }
+
+ protected function configure(): void {
+ $this
+ ->setName('talk:user:transfer-ownership')
+ ->setDescription('Adds the destination-user with the same participant type to all (not one-to-one) conversations of source-user')
+ ->addArgument(
+ 'source-user',
+ InputArgument::REQUIRED,
+ 'Owner of conversations which shall be moved'
+ )
+ ->addArgument(
+ 'destination-user',
+ InputArgument::REQUIRED,
+ 'User who will be the new owner of the conversations'
+ )
+ ->addOption(
+ 'include-non-moderator',
+ null,
+ InputOption::VALUE_NONE,
+ 'Also include conversations where the source-user is a normal user'
+ )
+ ->addOption(
+ 'remove-source-user',
+ null,
+ InputOption::VALUE_NONE,
+ 'Remove the source-user from the conversations'
+ )
+ ;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int {
+ $sourceUID = $input->getArgument('source-user');
+ $destinationUID = $input->getArgument('destination-user');
+
+ $destinationUser = $this->userManager->get($destinationUID);
+ if ($destinationUser === null) {
+ $output->writeln('<error>Destination user could not be found.</error>');
+ return 1;
+ }
+
+ $includeNonModeratorRooms = $input->getOption('include-non-moderator');
+ $removeSourceUser = $input->getOption('remove-source-user');
+
+ $modified = 0;
+ $rooms = $this->manager->getRoomsForActor(Attendee::ACTOR_USERS, $sourceUID);
+ foreach ($rooms as $room) {
+ if ($room->getType() !== Room::TYPE_GROUP && $room->getType() !== Room::TYPE_PUBLIC) {
+ // Skip one-to-one, changelog and any other room types
+ continue;
+ }
+
+ $sourceParticipant = $this->participantService->getParticipantByActor($room, Attendee::ACTOR_USERS, $sourceUID);
+
+ if ($sourceParticipant->getAttendee()->getParticipantType() === Participant::USER_SELF_JOINED) {
+ continue;
+ }
+
+ if (!$includeNonModeratorRooms && !$sourceParticipant->hasModeratorPermissions()) {
+ continue;
+ }
+
+ try {
+ $destinationParticipant = $this->participantService->getParticipantByActor($room, Attendee::ACTOR_USERS, $destinationUser->getUID());
+
+ $targetType = $this->shouldUpdateParticipantType($sourceParticipant->getAttendee()->getParticipantType(), $destinationParticipant->getAttendee()->getParticipantType());
+
+ if ($targetType !== null) {
+ $this->participantService->updateParticipantType(
+ $room,
+ $destinationParticipant,
+ $sourceParticipant->getAttendee()->getParticipantType()
+ );
+ $modified++;
+ }
+ } catch (ParticipantNotFoundException $e) {
+ $this->participantService->addUsers($room, [
+ [
+ 'actorType' => Attendee::ACTOR_USERS,
+ 'actorId' => $destinationUser->getUID(),
+ 'displayName' => $destinationUser->getDisplayName(),
+ 'participantType' => $sourceParticipant->getAttendee()->getParticipantType(),
+ ]
+ ]);
+ $modified++;
+ }
+
+ if ($removeSourceUser) {
+ $this->participantService->removeAttendee($room, $sourceParticipant, Room::PARTICIPANT_REMOVED);
+ }
+ }
+
+ $output->writeln('<info>Added or promoted user ' . $destinationUser->getUID() . ' in ' . $modified . ' rooms.</info>');
+ return 0;
+ }
+
+ protected function shouldUpdateParticipantType(int $sourceParticipantType, int $destinationParticipantType): ?int {
+ if ($sourceParticipantType === Participant::OWNER) {
+ if ($destinationParticipantType === Participant::OWNER) {
+ return null;
+ }
+ return $sourceParticipantType;
+ }
+
+ if ($sourceParticipantType === Participant::MODERATOR) {
+ if ($destinationParticipantType === Participant::OWNER || $destinationParticipantType === Participant::MODERATOR) {
+ return null;
+ }
+ return $sourceParticipantType;
+ }
+
+ if ($sourceParticipantType === Participant::USER) {
+ if ($destinationParticipantType !== Participant::USER_SELF_JOINED) {
+ return null;
+ }
+ return $sourceParticipantType;
+ }
+
+ return null;
+ }
+}
diff --git a/lib/Config.php b/lib/Config.php
index 9f63cec65..14882d0ae 100644
--- a/lib/Config.php
+++ b/lib/Config.php
@@ -34,7 +34,6 @@ use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserManager;
use OCP\Security\ISecureRandom;
-use OCP\Server;
class Config {
public const SIGNALING_INTERNAL = 'internal';
@@ -138,11 +137,28 @@ class Config {
return $this->canEnableSIP[$user->getUID()];
}
+ public function isSignalingDev(): bool {
+ return $this->config->getAppValue('spreed', 'signaling_dev', 'no') === 'yes';
+ }
+
public function isRecordingEnabled(): bool {
$isSignalingInternal = $this->getSignalingMode() === self::SIGNALING_INTERNAL;
- $recordingAllowed = $this->config->getAppValue('spreed', 'call_recording', 'yes') === 'yes';
+ $isSignalingDev = $this->isSignalingDev();
+ $isSignalingOk = $isSignalingDev || !$isSignalingInternal;
- return !$isSignalingInternal && $recordingAllowed;
+ $callRecordingConfig = $this->config->getAppValue('spreed', 'call_recording', 'yes');
+ $recordingEnabled = $callRecordingConfig === 'yes';
+
+ return $isSignalingOk && $recordingEnabled;
+ }
+
+ public function getRecordingFolder(string $userId): string {
+ return $this->config->getUserValue(
+ $userId,
+ 'spreed',
+ 'recording_folder',
+ $this->getAttachmentFolder($userId) . '/Recording'
+ );
}
public function isDisabledForUser(IUser $user): bool {
@@ -335,7 +351,7 @@ class Config {
}
if ($numSignalingServers === 1
&& $cleanExternalSignaling
- && $this->config->getAppValue('spreed', 'signaling_dev', 'no') === 'no') {
+ && !$this->isSignalingDev()) {
return self::SIGNALING_EXTERNAL;
}
@@ -431,6 +447,7 @@ class Config {
if (substr($alg, 0, 2) === 'ES') {
$privKey = openssl_pkey_new([
'curve_name' => 'prime256v1',
+ 'private_key_bits' => 2048,
'private_key_type' => OPENSSL_KEYTYPE_EC,
]);
$pubKey = openssl_pkey_get_details($privKey);
diff --git a/lib/Controller/ChatController.php b/lib/Controller/ChatController.php
index c3c80df14..99cd22b4a 100644
--- a/lib/Controller/ChatController.php
+++ b/lib/Controller/ChatController.php
@@ -466,12 +466,19 @@ class ChatController extends AEnvironmentAwareController {
$this->preloadShares($comments);