diff --git a/application/src/Controller/MatrixController.php b/application/src/Controller/MatrixController.php index 2bfb66d..92bf4f7 100644 --- a/application/src/Controller/MatrixController.php +++ b/application/src/Controller/MatrixController.php @@ -7,9 +7,9 @@ use App\Entity\RoomMember; use App\Entity\Token; use App\Entity\User; +use App\Service\Filter; use App\Traits\GeneralTrait; use App\Traits\MatrixSynapseTrait; -use stdClass; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\Routing\Annotation\Route; @@ -273,6 +273,51 @@ public function createRoom(string $serverID, Request $request):JsonResponse { return new JsonResponse((object) $response, 200); } + /** + * Sync Matrix room. + * Sync room according to the API: + * https://spec.matrix.org/v1.8/client-server-api/#get_matrixclientv3sync + * + * @Route("/v3/sync") + * @param string $serverID + * @param Request $request + * @return JsonResponse + */ + public function sync(string $serverID, Request $request):JsonResponse { + // 1. Check call auth. + // 2. Check HTTP method is accepted. + $accessCheck = $this->authHttpCheck(['GET'], $request); + if (!$accessCheck['status']) { + return $accessCheck['message']; + } + // Params are being queried via a filter in the request. + $filter = Filter::fromRequest($request); + + $roomSearch = [ + 'serverid' => $serverID, + ]; + + if ($roomIds = $filter->getRoomFilter()->getRoomIds()) { + $roomSearch['roomid'] = $roomIds; + } + + // Get a list of rooms. + $entityManager = $this->getDoctrine()->getManager(); + $roomList = $entityManager->getRepository(Room::class)->findBy($roomSearch); + + // Get room state data for each room. + $roomData = []; + foreach ($roomList as $room) { + $roomData[$room->getRoomId()] = $room->getRoomState($request); + } + + return new JsonResponse((object) [ + 'rooms' => [ + 'join' => $roomData, + ], + ]); + } + /** * Create Matrix room. * @@ -402,6 +447,25 @@ public function roomState(string $serverID, string $roomID, string $eventType, R if (!$check['status']) { return $check['message']; } + // Update power levels for each room member. + foreach ($payload->users as $userid => $level) { + if (!empty($level)) { + $user = $entityManager->getRepository(User::class)->findOneBy([ + 'serverid' => $serverID, + 'userid' => $userid, + ]); + if (!empty($user)) { + $membership = $entityManager->getRepository(RoomMember::class)->findOneBy([ + 'serverid' => $serverID, + 'room' => $room, + 'user' => $user, + ]); + if(!empty($membership)) { + $membership->setPowerLevel($level); + } + } + } + } } else { // Unknown state. return new JsonResponse((object) [ diff --git a/application/src/Entity/Room.php b/application/src/Entity/Room.php index f00d1f5..25b173b 100644 --- a/application/src/Entity/Room.php +++ b/application/src/Entity/Room.php @@ -200,4 +200,62 @@ public function getMembers(): Collection { return $this->members; } + + public function getRoomState(): array + { + // We will probably need to pass a filter in the future. + // At the moment we only know how to serve a set of power levels. + $roomData = [ + 'state' => [ + 'events' => [], + ], + ]; + + $roomData['state']['events'][] = $this->getPowerLevelState(); + + return $roomData; + } + + protected function getPowerLevelState(): array + { + // Power level values. + $powerLevelDefault = 0; + $powerLevelModerator = 50; + $powerLevelMaximum = 100; + + // Get the members of the room who have a powerlevel set. + $members = $this->getMembers()->filter(function (RoomMember $member) { + return $member->getPowerLevel() !== null; + }); + + // Build the power level state. + $memberInfo = $members->map(fn(RoomMember $member) => [ + $member->getUser()->getUserid() => $member->getPowerLevel(), + ])->toArray(); + + return [ + 'type' => 'm.room.power_levels', + 'content' => [ + 'users' => (object) $memberInfo, + 'users_default' => $powerLevelDefault, + 'events' => [ + 'm.room.name' => $powerLevelModerator, + 'm.room.power_levels' => $powerLevelMaximum, + 'm.room.history_visibility' => $powerLevelMaximum, + 'm.room.canonical_alias' => $powerLevelModerator, + 'm.room.avatar' => $powerLevelModerator, + 'm.room.tombstone' => $powerLevelMaximum, + 'm.room.server_acl' => $powerLevelMaximum, + 'm.room.encryption' => $powerLevelMaximum, + ], + 'events_default' => $powerLevelDefault, + 'state_default' => $powerLevelModerator, + 'ban' => $powerLevelModerator, + 'kick' => $powerLevelModerator, + 'redact' => $powerLevelModerator, + 'invite' => $powerLevelDefault, + 'historical' => $powerLevelMaximum, + ], + ]; + } } diff --git a/application/src/Entity/RoomMember.php b/application/src/Entity/RoomMember.php index 32e26fa..54f862c 100644 --- a/application/src/Entity/RoomMember.php +++ b/application/src/Entity/RoomMember.php @@ -56,6 +56,11 @@ class RoomMember */ private $state; + /** + * @ORM\Column(type="integer", nullable=true) + */ + private $powerlevel; + public function getId(): ?int { return $this->id; @@ -154,4 +159,16 @@ public function setState(string $state = null): self return $this; } + + public function getPowerLevel(): ?int + { + return $this->powerlevel; + } + + public function setPowerLevel(int $powerlevel): self + { + $this->powerlevel = $powerlevel; + + return $this; + } } diff --git a/application/src/Service/Filter.php b/application/src/Service/Filter.php new file mode 100644 index 0000000..f69cb96 --- /dev/null +++ b/application/src/Service/Filter.php @@ -0,0 +1,55 @@ +query->get('filter'); + if (!$filterData) { + return $filter; + } + + // Filter the event data according to the API: + // https://spec.matrix.org/v1.1/client-server-api/#filtering + // Filters are made up of different filter types. + // Detailed here: https://spec.matrix.org/v1.1/client-server-api/#post_matrixclientv3useruseridfilter + $filterData = json_decode($request->query->get('filter')); + + if (property_exists($filterData, 'room')) { + $filter->setRoomFilter(RoomFilter::fromObject($filterData->room)); + } + + return $filter; + } + + public function setRoomFilter(RoomFilter $roomFilter): static + { + $this->roomFilter = $roomFilter; + return $this; + } + + public function getRequestedRoomIds(): array + { + return $this->roomIds; + } + + public function getRoomFilter(): RoomFilter + { + if (empty($this->roomFilter)) { + $this->roomFilter = new RoomFilter(); + } + + return $this->roomFilter; + } +} diff --git a/application/src/Service/RoomFilter.php b/application/src/Service/RoomFilter.php new file mode 100644 index 0000000..6758fd9 --- /dev/null +++ b/application/src/Service/RoomFilter.php @@ -0,0 +1,49 @@ +rooms; + $filter->setRoomIds($rooms); + } + + // Check for a room state filter. + if (property_exists($filterData, 'state')) { + $filter->setRoomState(StateFilter::fromObject($filterData->state)); + } + + return $filter; + } + + public function setRoomIds(array $ids): static + { + $this->roomIds = $ids; + return $this; + } + + public function setRoomState(StateFilter $state): static + { + $this->roomStateFilter = $state; + return $this; + } + + public function getRoomIds(): array + { + return $this->roomIds; + } + + public function getRoomState(): StateFilter + { + return $this->roomStateFilter; + } +} diff --git a/application/src/Service/StateFilter.php b/application/src/Service/StateFilter.php new file mode 100644 index 0000000..d8b9220 --- /dev/null +++ b/application/src/Service/StateFilter.php @@ -0,0 +1,32 @@ +types; + $filter->setTypes($types); + } + + return $filter; + } + + public function setTypes(array $types): static + { + $this->types = $types; + return $this; + } + + public function getTypes(): array + { + return $this->types; + } +}