parameters['user']); $this->readString('sessionID'); $this->parameters['sessionID'] = \pack( 'H*', \str_replace('-', '', $this->parameters['sessionID']) ); $this->readInteger('roomID'); $room = RoomCache::getInstance()->getRoom($this->parameters['roomID']); if ($room === null) { throw new UserInputException('roomID'); } if (!$room->canSee($user = null, $reason)) { throw $reason; } if (!$room->canJoin($user = null, $reason)) { throw $reason; } } /** * Makes the given user join the current chat room. */ public function join() { $objectTypeID = ObjectTypeCache::getInstance()->getObjectTypeIDByName( 'be.bastelstu.chat.messageType', 'be.bastelstu.chat.messageType.join' ); if (!$objectTypeID) { throw new \LogicException('Missing object type'); } // User cannot be set during an AJAX request, but may be set by Tim’s Chat itself. if (!isset($this->parameters['user'])) { $this->parameters['user'] = WCF::getUser(); } $user = new ChatUser($this->parameters['user']); // Check parameters $room = RoomCache::getInstance()->getRoom($this->parameters['roomID']); if ($room === null) { throw new UserInputException('roomID'); } $sessionID = $this->parameters['sessionID']; if (\strlen($sessionID) !== 16) { throw new UserInputException('sessionID'); } try { // Create room_to_user mapping. $sql = "INSERT INTO chat1_room_to_user (active, roomID, userID) VALUES (?, ?, ?)"; $statement = WCF::getDB()->prepare($sql); $statement->execute([ 0, $room->roomID, $user->userID ]); } catch (\wcf\system\database\exception\DatabaseException $e) { // Ignore if there already is a mapping. if ((string)$e->getCode() !== '23000') { throw $e; } } try { $sql = "INSERT INTO chat1_session (roomID, userID, sessionID, lastRequest) VALUES (?, ?, ?, ?)"; $statement = WCF::getDB()->prepare($sql); $statement->execute([ $room->roomID, $user->userID, $sessionID, TIME_NOW, ]); } catch (\wcf\system\database\exception\DatabaseException $e) { if ((string)$e->getCode() !== '23000') { throw $e; } throw new UserInputException('sessionID'); } $markAsBack = static function () use ($user, $room) { $userProfile = new UserProfile($user->getDecoratedObject()); $package = PackageCache::getInstance()->getPackageByIdentifier('be.bastelstu.chat'); $command = CommandCache::getInstance()->getCommandByPackageAndIdentifier($package, 'back'); $processor = $command->getProcessor(); $processor->execute([ ], $room, $userProfile); }; if ($user->chatAway !== null) { $markAsBack(); } // Attempt to mark the user as active in the room. $sql = "UPDATE chat1_room_to_user SET active = ? WHERE roomID = ? AND userID = ?"; $statement = WCF::getDB()->prepare($sql); $statement->execute([ 1, $room->roomID, $user->userID ]); if ($statement->getAffectedRows() === 0) { // The User already is inside the room: Nothing to do here. return; } // Update lastPull. This must not be merged into the above query, because of the 'getAffectedRows' check. $sql = "UPDATE chat1_room_to_user SET lastPull = ? WHERE roomID = ? AND userID = ?"; $statement = WCF::getDB()->prepare($sql); $statement->execute([ TIME_NOW, $room->roomID, $user->userID ]); (new MessageAction( [ ], 'create', [ 'data' => [ 'roomID' => $room->roomID, 'userID' => $user->userID, 'username' => $user->username, 'time' => TIME_NOW, 'objectTypeID' => $objectTypeID, 'payload' => \serialize([ ]), ], ] ))->executeAction(); UserActivityPointHandler::getInstance()->fireEvent( 'be.bastelstu.chat.activityPointEvent.join', 0, $user->userID ); $pushHandler = PushHandler::getInstance(); $pushHandler->sendMessage([ 'message' => 'be.bastelstu.chat.join', 'target' => 'registered', ]); } /** * Validates parameters and permissions. */ public function validateLeave() { unset($this->parameters['user']); $this->readString('sessionID'); $this->parameters['sessionID'] = \pack( 'H*', \str_replace('-', '', $this->parameters['sessionID']) ); $this->readInteger('roomID'); $room = RoomCache::getInstance()->getRoom($this->parameters['roomID']); if ($room === null) { throw new UserInputException('roomID'); } // Do not check permissions: If the user is not inside the room nothing happens, if he is it // may lead to a faster eviction of the user. } /** * Makes the given user leave the current chat room. */ public function leave() { $objectTypeID = ObjectTypeCache::getInstance()->getObjectTypeIDByName( 'be.bastelstu.chat.messageType', 'be.bastelstu.chat.messageType.leave' ); if ($objectTypeID) { // User cannot be set during an AJAX request, but may be set by Tim’s Chat itself. if (!isset($this->parameters['user'])) { $this->parameters['user'] = WCF::getUser(); } $user = new ChatUser($this->parameters['user']); $room = RoomCache::getInstance()->getRoom($this->parameters['roomID']); if ($room === null) { throw new UserInputException('roomID'); } $sessionID = null; if (isset($this->parameters['sessionID'])) { $sessionID = $this->parameters['sessionID']; if (\strlen($sessionID) !== 16) { throw new UserInputException('sessionID'); } } // Delete session. $condition = new PreparedStatementConditionBuilder(); $condition->add('roomID = ?', [ $room->roomID ]); $condition->add('userID = ?', [ $user->userID ]); if ($sessionID !== null) { $condition->add('sessionID = ?', [ $sessionID ]); } $sql = "DELETE FROM chat1_session {$condition}"; $statement = WCF::getDB()->prepare($sql); $statement->execute($condition->getParameters()); if ($statement->getAffectedRows() === 0) { throw new UserInputException('sessionID'); } try { $commited = false; WCF::getDB()->beginTransaction(); // Check whether we deleted the last session. $sql = "SELECT COUNT(*) FROM chat1_session WHERE roomID = ? AND userID = ?"; $statement = WCF::getDB()->prepare($sql); $statement->execute([ $room->roomID, $user->userID ]); // We did not: Nothing to do here. if ($statement->fetchColumn()) { return; } // Mark the user as inactive. $sql = "UPDATE chat1_room_to_user SET active = ? WHERE roomID = ? AND userID = ?"; $statement = WCF::getDB()->prepare($sql); $statement->execute([ 0, $room->roomID, $user->userID ]); if ($statement->getAffectedRows() === 0) { throw new \LogicException('Unreachable'); } WCF::getDB()->commitTransaction(); $commited = true; } finally { if (!$commited) { WCF::getDB()->rollBackTransaction(); } } (new MessageAction( [ ], 'create', [ 'data' => [ 'roomID' => $room->roomID, 'userID' => $user->userID, 'username' => $user->username, 'time' => TIME_NOW, 'objectTypeID' => $objectTypeID, 'payload' => \serialize([ ]), ], ] ))->executeAction(); $pushHandler = PushHandler::getInstance(); $pushHandler->sendMessage([ 'message' => 'be.bastelstu.chat.leave', 'target' => 'registered', ]); } else { throw new \LogicException('Missing object type'); } } /** * Validates parameters and permissions. */ public function validateGetUsers() { if (empty($this->getObjects())) { $this->readObjects(); } if (\count($this->getObjects()) !== 1) { throw new UserInputException('objectIDs'); } $room = $this->getObjects()[0]; $user = new ChatUser(WCF::getUser()); if (!$user->isInRoom($room->getDecoratedObject())) { throw new PermissionDeniedException(); } } /** * Returns the userIDs of the users in this room. */ public function getUsers() { if (empty($this->getObjects())) { $this->readObjects(); } if (\count($this->getObjects()) !== 1) { throw new UserInputException('objectIDs'); } $room = $this->getObjects()[0]; $users = (new ChatUserAction( [ ], 'getUsersByID', [ 'userIDs' => \array_keys($room->getUsers()), ] ))->executeAction()['returnValues']; $users = \array_map(static function (array $user) use ($room) { $userProfile = UserProfileRuntimeCache::getInstance()->getObject($user['userID']); if (!isset($user['permissions'])) { $user['permissions'] = []; } $user['permissions']['canWritePublicly'] = $room->canWritePublicly($userProfile); return $user; }, $users); EventHandler::getInstance()->fireAction($this, 'getUsers', $users); return $users; } /** * @inheritDoc */ public function validateUpdatePosition() { // validate permissions if (\is_array($this->permissionsUpdate) && !empty($this->permissionsUpdate)) { WCF::getSession()->checkPermissions($this->permissionsUpdate); } else { throw new PermissionDeniedException(); } $this->readIntegerArray('structure', false, 'data'); $roomList = new RoomList(); $roomList->readObjects(); foreach ($this->parameters['data']['structure'][0] as $roomID) { $room = $roomList->search($roomID); if ($room === null) { throw new UserInputException('structure'); } } } /** * @inheritDoc */ public function updatePosition() { $roomList = new RoomList(); $roomList->readObjects(); $i = 0; WCF::getDB()->beginTransaction(); foreach ($this->parameters['data']['structure'][0] as $roomID) { $room = $roomList->search($roomID); if ($room === null) { continue; } $editor = new RoomEditor($room); $editor->update([ 'position' => $i++, ]); } WCF::getDB()->commitTransaction(); } /** * Validates permissions. */ public function validateGetBoxRoomList() { if (!Room::canSeeAny()) { throw new PermissionDeniedException(); } $this->readBoolean('isSidebar', true); $this->readBoolean('skipEmptyRooms', true); $this->readInteger('activeRoomID', true); unset($this->parameters['boxController']); $this->readInteger('boxID', true); if ($this->parameters['boxID']) { $box = new Box($this->parameters['boxID']); if ($box->boxID) { $this->parameters['boxController'] = $box->getController(); if ($this->parameters['boxController'] instanceof RoomListBoxController) { // all checks passed, end validation; otherwise throw the exception below return; } } throw new UserInputException('boxID'); } } /** * Returns dashboard roomlist. */ public function getBoxRoomList() { if (isset($this->parameters['boxController'])) { $this->parameters['boxController']->setActiveRoomID($this->parameters['activeRoomID']); return [ 'template' => $this->parameters['boxController']->getContent(), ]; } // Fetch all rooms, the templates have filtering in place $rooms = RoomCache::getInstance()->getRooms(); $template = 'boxRoomList' . ($this->parameters['isSidebar'] ? 'Sidebar' : ''); WCF::getTPL()->assign([ 'boxRoomList' => $rooms, 'skipEmptyRooms' => $this->parameters['skipEmptyRooms'], 'activeRoomID' => $this->parameters['activeRoomID'], ]); return [ 'template' => WCF::getTPL()->fetch($template, 'chat'), ]; } }