* * @author Arthur Schiwon * * @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 . * */ namespace OCA\Talk\Flow; use OCA\Talk\Chat\ChatManager; use OCA\Talk\Exceptions\ParticipantNotFoundException; use OCA\Talk\Exceptions\RoomNotFoundException; use OCA\Talk\Manager as TalkManager; use OCA\Talk\Participant; use OCA\Talk\Room; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventDispatcher; use OCP\IL10N; use OCP\IURLGenerator; use OCP\IUser; use OCP\IUserSession; use OCP\Util; use OCP\WorkflowEngine\EntityContext\IDisplayText; use OCP\WorkflowEngine\EntityContext\IUrl; use OCP\WorkflowEngine\IEntity; use OCP\WorkflowEngine\IManager as FlowManager; use OCP\WorkflowEngine\IOperation; use OCP\WorkflowEngine\IRuleMatcher; use Symfony\Component\EventDispatcher\GenericEvent; use UnexpectedValueException; class Operation implements IOperation { /** @var int[] */ public const MESSAGE_MODES = [ 'NO_MENTION' => 1, 'SELF_MENTION' => 2, 'ROOM_MENTION' => 3, ]; /** @var IL10N */ private $l; /** @var IURLGenerator */ private $urlGenerator; /** @var TalkManager */ private $talkManager; /** @var IUserSession */ private $session; /** @var ChatManager */ private $chatManager; public function __construct( IL10N $l, IURLGenerator $urlGenerator, TalkManager $talkManager, IUserSession $session, ChatManager $chatManager ) { $this->l = $l; $this->urlGenerator = $urlGenerator; $this->talkManager = $talkManager; $this->session = $session; $this->chatManager = $chatManager; } public static function register(IEventDispatcher $dispatcher): void { $dispatcher->addListener(FlowManager::EVENT_NAME_REG_OPERATION, function (GenericEvent $event) { $operation = \OC::$server->query(Operation::class); $event->getSubject()->registerOperation($operation); Util::addScript('spreed', 'flow'); }); } public function getDisplayName(): string { return $this->l->t('Write to conversation'); } public function getDescription(): string { return $this->l->t('Writes event information into a conversation of your choice'); } public function getIcon(): string { return $this->urlGenerator->imagePath('spreed', 'app.svg'); } public function isAvailableForScope(int $scope): bool { return $scope === FlowManager::SCOPE_USER; } /** * Validates whether a configured workflow rule is valid. If it is not, * an `\UnexpectedValueException` is supposed to be thrown. * * @throws UnexpectedValueException * @since 9.1 */ public function validateOperation(string $name, array $checks, string $operation): void { list($mode, $token) = $this->parseOperationConfig($operation); $this->validateOperationConfig($mode, $token, $this->getUser()->getUID()); } public function onEvent(string $eventName, Event $event, IRuleMatcher $ruleMatcher): void { $flows = $ruleMatcher->getFlows(false); foreach ($flows as $flow) { try { list($mode, $token) = $this->parseOperationConfig($flow['operation']); $uid = $flow['scope_actor_id']; $this->validateOperationConfig($mode, $token, $uid); $entity = $ruleMatcher->getEntity(); $message = $this->prepareText($entity, $eventName); if ($message === '') { continue; } $room = $this->getRoom($token, $uid); $participant = $this->getParticipant($uid, $room); $this->chatManager->sendMessage( $room, $participant, 'bots', $participant->getUser(), $this->prepareMention($mode, $participant) . $message, new \DateTime(), null, '' ); } catch (UnexpectedValueException $e) { continue; } catch (ParticipantNotFoundException $e) { continue; } catch (RoomNotFoundException $e) { continue; } } } protected function prepareText(IEntity $entity, string $eventName) { $message = $eventName; if ($entity instanceof IDisplayText) { $message = trim($entity->getDisplayText(3)); } if ($entity instanceof IUrl && $message !== '') { $message .= ' ' . $entity->getUrl(); } return $message; } /** * returns a mention including a trailing whitespace, or an empty string */ protected function prepareMention(int $mode, Participant $participant): string { switch ($mode) { case self::MESSAGE_MODES['ROOM_MENTION']: return '@all '; case self::MESSAGE_MODES['SELF_MENTION']: $hasWhitespace = strpos($participant->getUser(), ' ') !== false; $enclosure = $hasWhitespace ? '"' : ''; return '@' . $enclosure . $participant->getUser() . $enclosure . ' '; case self::MESSAGE_MODES['NO_MENTION']: default: return ''; } } protected function parseOperationConfig(string $raw): array { /** * We expect $operation be a json string, containing * 't' => string, the room token * 'm' => int > 0, see self::MESSAGE_MODES * * setting up room mentions are only permitted to moderators */ $opConfig = \json_decode($raw, true); if (!is_array($opConfig) || empty($opConfig)) { throw new UnexpectedValueException('Cannot decode operation details'); } $mode = (int)($opConfig['m'] ?? 0); $token = trim((string)($opConfig['t'] ?? '')); return [$mode, $token]; } protected function validateOperationConfig(int $mode, string $token, string $uid): void { if (!in_array($mode, self::MESSAGE_MODES)) { throw new UnexpectedValueException('Invalid mode'); } if (empty($token)) { throw new UnexpectedValueException('Invalid token'); } try { $room = $this->getRoom($token, $uid); } catch (RoomNotFoundException $e) { throw new UnexpectedValueException('Room not found', $e->getCode(), $e); } if ($mode === self::MESSAGE_MODES['ROOM_MENTION']) { try { $participant = $this->getParticipant($uid, $room); if (!$participant->hasModeratorPermissions(false)) { throw new UnexpectedValueException('Not allowed to mention room'); } } catch (ParticipantNotFoundException $e) { throw new UnexpectedValueException('Participant not found', $e->getCode(), $e); } } } /** * @throws UnexpectedValueException */ protected function getUser(): IUser { $user = $this->session->getUser(); if ($user === null) { throw new UnexpectedValueException('User not logged in'); } return $user; } /** * @throws RoomNotFoundException */ protected function getRoom(string $token, string $uid): Room { return $this->talkManager->getRoomForParticipantByToken($token, $uid); } /** * @throws ParticipantNotFoundException */ protected function getParticipant(string $uid, Room $room): Participant { return $room->getParticipant($uid); } }