1<?php
2
3namespace MatrixPhp;
4
5use MatrixPhp\Exceptions\MatrixRequestException;
6use function GuzzleHttp\default_ca_bundle;
7use http\Exception;
8use phpDocumentor\Reflection\DocBlock\Tags\Param;
9
10/**
11 * Call room-specific functions after joining a room from the client.
12 *
13 *  NOTE: This should ideally be called from within the Client.
14 *  NOTE: This does not verify the room with the Home Server.
15 *
16 * @package MatrixPhp
17 */
18class Room {
19
20    /** @var MatrixClient */
21    protected $client;
22    protected $roomId;
23    protected $listeners = [];
24    protected $stateListeners = [];
25    protected $ephemeralListeners = [];
26    protected $events = [];
27    protected $eventHistoryLimit = 20;
28    protected $name;
29    protected $canonicalAlias;
30    protected $aliases = [];
31    protected $topic;
32    protected $inviteOnly = false;
33    protected $guestAccess;
34    public $prevBatch;
35    protected $_members = [];
36    protected $membersDisplaynames = [
37        // $userId: $displayname,
38    ];
39    protected $encrypted = false;
40
41    public function __construct(MatrixClient $client, string $roomId) {
42        Util::checkRoomId($roomId);
43        $this->roomId = $roomId;
44        $this->client = $client;
45    }
46
47    /**
48     * Set user profile within a room.
49     *
50     * This sets displayname and avatar_url for the logged in user only in a
51     * specific room. It does not change the user's global user profile.
52     *
53     * @param string|null $displayname
54     * @param string|null $avatarUrl
55     * @param string $reason
56     * @throws Exceptions\MatrixException
57     * @throws Exceptions\MatrixHttpLibException
58     * @throws Exceptions\MatrixRequestException
59     */
60    public function setUserProfile(?string $displayname = null, ?string $avatarUrl = null,
61                                   string $reason = "Changing room profile information") {
62        $member = $this->api()->getMembership($this->roomId, $this->client->userId());
63        if ($member['membership'] != 'join') {
64            throw new \Exception("Can't set profile if you have not joined the room.");
65        }
66        if (!$displayname) {
67            $displayname = $member["displayname"];
68        }
69        if (!$avatarUrl) {
70            $avatarUrl = $member["avatar_url"];
71        }
72        $this->api()->setMembership(
73            $this->roomId,
74            $this->client->userId(),
75            'join',
76            $reason,
77            [
78                "displayname" => $displayname,
79                "avatar_url" => $avatarUrl
80            ]
81        );
82    }
83
84    /**
85     * Calculates the display name for a room.
86     *
87     * @return string
88     */
89    public function displayName() {
90        if ($this->name) {
91            return $this->name;
92        } elseif ($this->canonicalAlias) {
93            return $this->canonicalAlias;
94        }
95
96        // Member display names without me
97        $members = array_reduce($this->getJoinedMembers(), function (array $all, User $u) {
98            if ($this->client->userId() != $u->userId()) {
99                $all[] = $u->getDisplayName($this);
100            }
101            return $all;
102        }, []);
103        sort($members);
104
105        switch (count($members)) {
106            case 0:
107                return 'Empty room';
108            case 1:
109                return $members[0];
110            case 2:
111                return sprintf("%s and %s", $members[0], $members[1]);
112            default:
113                return sprintf("%s and %d others.", $members[0], count($members));
114        }
115    }
116
117    /**
118     * Send a plain text message to the room.
119     *
120     * @param string $text
121     * @return array|string
122     * @throws Exceptions\MatrixException
123     * @throws Exceptions\MatrixHttpLibException
124     * @throws Exceptions\MatrixRequestException
125     */
126    public function sendText(string $text) {
127        return $this->api()->sendMessage($this->roomId, $text);
128    }
129
130    public function getHtmlContent(string $html, ?string $body = null, string $msgType = 'm.text') {
131        return [
132            'body' => $body ?: strip_tags($html),
133            'msgtype' => $msgType,
134            'format' => "org.matrix.custom.html",
135            'formatted_body' => $html,
136        ];
137    }
138
139    /**
140     * Send an html formatted message.
141     *
142     * @param string $html The html formatted message to be sent.
143     * @param string|null $body The unformatted body of the message to be sent.
144     * @param string $msgType
145     * @return array|string
146     * @throws Exceptions\MatrixException
147     * @throws Exceptions\MatrixHttpLibException
148     * @throws Exceptions\MatrixRequestException
149     */
150    public function sendHtml(string $html, ?string $body = null, string $msgType = 'm.text') {
151        $content = $this->getHtmlContent($html, $body, $msgType);
152
153        return $this->api()->sendMessageEvent($this->roomId, 'm.room.message', $content);
154    }
155
156    /**
157     * @param string $type
158     * @param array $data
159     * @return array|string
160     * @throws Exceptions\MatrixException
161     * @throws Exceptions\MatrixHttpLibException
162     * @throws Exceptions\MatrixRequestException
163     */
164    public function setAccountData(string $type, array $data) {
165        return $this->api()->setRoomAccountData($this->client->userId(), $this->roomId, $type, $data);
166    }
167
168    /**
169     * @return array|string
170     * @throws Exceptions\MatrixException
171     * @throws Exceptions\MatrixHttpLibException
172     * @throws Exceptions\MatrixRequestException
173     */
174    public function getTags() {
175        return $this->api()->getUserTags($this->client->userId(), $this->roomId);
176    }
177
178    /**
179     * @param string $tag
180     * @return array|string
181     * @throws Exceptions\MatrixException
182     * @throws Exceptions\MatrixHttpLibException
183     * @throws Exceptions\MatrixRequestException
184     */
185    public function removeTag(string $tag) {
186        return $this->api()->removeUserTag($this->client->userId(), $this->roomId, $tag);
187    }
188
189    /**
190     * @param string $tag
191     * @param float|null $order
192     * @param array $content
193     * @return array|string
194     * @throws Exceptions\MatrixException
195     * @throws Exceptions\MatrixHttpLibException
196     * @throws Exceptions\MatrixRequestException
197     */
198    public function addTag(string $tag, ?float $order = null, array $content = []) {
199        return $this->api()->addUserTag($this->client->userId(), $this->roomId, $tag, $order, $content);
200    }
201
202    /**
203     * Send an emote (/me style) message to the room.
204     *
205     * @param string $text
206     * @return array|string
207     * @throws Exceptions\MatrixException
208     * @throws Exceptions\MatrixHttpLibException
209     * @throws Exceptions\MatrixRequestException
210     */
211    public function sendEmote(string $text) {
212        return $this->api()->sendEmote($this->roomId, $text);
213    }
214
215    /**
216     * Send a pre-uploaded file to the room.
217     *
218     * See http://matrix.org/docs/spec/r0.4.0/client_server.html#m-file for fileinfo.
219     *
220     * @param string $url The mxc url of the file.
221     * @param string $name The filename of the image.
222     * @param array $fileinfo Extra information about the file
223     * @return array|string
224     * @throws Exceptions\MatrixException
225     * @throws Exceptions\MatrixHttpLibException
226     * @throws Exceptions\MatrixRequestException
227     */
228    public function sendFile(string $url, string $name, array $fileinfo) {
229        return $this->api()->sendContent($this->roomId, $url, $name, 'm.file', $fileinfo);
230    }
231
232    /**
233     * Send a notice (from bot) message to the room.
234     *
235     * @param string $text
236     * @return array|string
237     * @throws Exceptions\MatrixException
238     * @throws Exceptions\MatrixHttpLibException
239     * @throws Exceptions\MatrixRequestException
240     */
241    public function sendNotice(string $text) {
242        return $this->api()->sendNotice($this->roomId, $text);
243    }
244
245    /**
246     * Send a pre-uploaded image to the room.
247     *
248     * See http://matrix.org/docs/spec/r0.0.1/client_server.html#m-image for imageinfo
249     *
250     * @param string $url The mxc url of the image.
251     * @param string $name The filename of the image.
252     * @param array $fileinfo Extra information about the image.
253     * @return array|string
254     * @throws Exceptions\MatrixException
255     * @throws Exceptions\MatrixHttpLibException
256     * @throws Exceptions\MatrixRequestException
257     */
258    public function sendImage(string $url, string $name, ?array $fileinfo) {
259        return $this->api()->sendContent($this->roomId, $url, $name, 'm.image', $fileinfo);
260    }
261
262    /**
263     * Send a location to the room.
264     * See http://matrix.org/docs/spec/client_server/r0.2.0.html#m-location for thumb_info
265     *
266     * @param string $geoUri The geo uri representing the location.
267     * @param string $name Description for the location.
268     * @param array $thumbInfo Metadata about the thumbnail, type ImageInfo.
269     * @param string|null $thumbUrl URL to the thumbnail of the location.
270     * @return array|string
271     * @throws Exceptions\MatrixException
272     * @throws Exceptions\MatrixHttpLibException
273     * @throws Exceptions\MatrixRequestException
274     */
275    public function sendLocation(string $geoUri, string $name, ?array $thumbInfo, ?string $thumbUrl = null) {
276        return $this->api()->sendLocation($this->roomId, $geoUri, $name, $thumbUrl, $thumbInfo);
277    }
278
279    /**
280     * Send a pre-uploaded video to the room.
281     * See http://matrix.org/docs/spec/client_server/r0.2.0.html#m-video for videoinfo
282     *
283     * @param string $url The mxc url of the video.
284     * @param string $name The filename of the video.
285     * @param array $videoinfo Extra information about the video.
286     * @return array|string
287     * @throws Exceptions\MatrixException
288     * @throws Exceptions\MatrixHttpLibException
289     * @throws Exceptions\MatrixRequestException
290     */
291    public function sendVideo(string $url, string $name, ?array $videoinfo) {
292        return $this->api()->sendContent($this->roomId, $url, $name, 'm.video', $videoinfo);
293    }
294
295    /**
296     * Send a pre-uploaded audio to the room.
297     * See http://matrix.org/docs/spec/client_server/r0.2.0.html#m-audio for audioinfo
298     *
299     * @param string $url The mxc url of the video.
300     * @param string $name The filename of the video.
301     * @param array $audioinfo Extra information about the video.
302     * @return array|string
303     * @throws Exceptions\MatrixException
304     * @throws Exceptions\MatrixHttpLibException
305     * @throws Exceptions\MatrixRequestException
306     */
307    public function sendAudio(string $url, string $name, ?array $audioinfo) {
308        return $this->api()->sendContent($this->roomId, $url, $name, 'm.audio', $audioinfo);
309    }
310
311    /**
312     * Redacts the message with specified event_id for the given reason.
313     *
314     * See https://matrix.org/docs/spec/r0.0.1/client_server.html#id112
315     *
316     * @param string $eventId
317     * @param string|null $reason
318     * @return array|string
319     * @throws Exceptions\MatrixException
320     * @throws Exceptions\MatrixHttpLibException
321     * @throws Exceptions\MatrixRequestException
322     */
323    public function redactMessage(string $eventId, ?string $reason = null) {
324        return $this->api()->redactEvent($this->roomId, $eventId, $reason);
325    }
326
327    /**
328     * Add a callback handler for events going to this room.
329     *
330     * @param callable $cb (func(room, event)): Callback called when an event arrives.
331     * @param string|null $eventType The event_type to filter for.
332     * @return string Unique id of the listener, can be used to identify the listener.
333     */
334    public function addListener(callable $cb, ?string $eventType = null) {
335        $listenerId = uniqid();
336        $this->listeners[] = [
337            'uid' => $listenerId,
338            'callback' => $cb,
339            'event_type' => $eventType,
340        ];
341
342        return $listenerId;
343    }
344
345    /**
346     * Remove listener with given uid.
347     *
348     * @param string $uid
349     */
350    public function removeListener(string $uid) {
351        $this->listeners = array_filter($this->listeners, function ($l) use ($uid) {
352            return $l['uid'] != $uid;
353        });
354    }
355
356    /**
357     * Add a callback handler for ephemeral events going to this room.
358     *
359     * @param callable $cb (func(room, event)): Callback called when an ephemeral event arrives.
360     * @param string|null $eventType The event_type to filter for.
361     * @return string Unique id of the listener, can be used to identify the listener.
362     */
363    public function addEphemeralListener(callable $cb, ?string $eventType = null) {
364        $listenerId = uniqid();
365        $this->ephemeralListeners[] = [
366            'uid' => $listenerId,
367            'callback' => $cb,
368            'event_type' => $eventType,
369        ];
370
371        return $listenerId;
372    }
373
374    /**
375     * Remove ephemeral listener with given uid.
376     *
377     * @param string $uid
378     */
379    public function removeEphemeralListener(string $uid) {
380        $this->ephemeralListeners = array_filter($this->ephemeralListeners, function ($l) use ($uid) {
381            return $l['uid'] != $uid;
382        });
383    }
384
385    /**
386     * Add a callback handler for state events going to this room.
387     *
388     * @param callable $cb Callback called when an event arrives.
389     * @param string|null $eventType The event_type to filter for.
390     */
391    public function addStateListener(callable $cb, ?string $eventType = null) {
392        $this->stateListeners[] = [
393            'callback' => $cb,
394            'event_type' => $eventType,
395        ];
396    }
397
398    public function putEvent(array $event) {
399        $this->events[] = $event;
400        if (count($this->events) > $this->eventHistoryLimit) {
401            array_pop($this->events);
402        }
403        if (array_key_exists('state_event', $event)) {
404            $this->processStateEvent($event);
405        }
406        // Dispatch for room-specific listeners
407        foreach ($this->listeners as $l) {
408            if (!$l['event_type'] || $l['event_type'] == $event['event_type']) {
409                $l['cb']($this, $event);
410            }
411        }
412    }
413
414    public function putEphemeralEvent(array $event) {
415        // Dispatch for room-specific listeners
416        foreach ($this->ephemeralListeners as $l) {
417            if (!$l['event_type'] || $l['event_type'] == $event['event_type']) {
418                $l['cb']($this, $event);
419            }
420        }
421    }
422
423    /**
424     * Get the most recent events for this room.
425     *
426     * @return array
427     */
428    public function getEvents(): array {
429        return $this->events;
430    }
431
432    /**
433     * Invite a user to this room.
434     *
435     * @param string $userId
436     * @return bool Whether invitation was sent.
437     * @throws Exceptions\MatrixException
438     * @throws Exceptions\MatrixHttpLibException
439     */
440    public function inviteUser(string $userId): bool {
441        try {
442            $this->api()->inviteUser($this->roomId, $userId);
443        } catch (MatrixRequestException $e) {
444            return false;
445        }
446
447        return true;
448    }
449
450    /**
451     * Kick a user from this room.
452     *
453     * @param string $userId The matrix user id of a user.
454     * @param string $reason A reason for kicking the user.
455     * @return bool Whether user was kicked.
456     * @throws Exceptions\MatrixException
457     */
458    public function kickUser(string $userId, string $reason = ''): bool {
459        try {
460            $this->api()->kickUser($this->roomId, $userId, $reason);
461        } catch (MatrixRequestException $e) {
462            return false;
463        }
464
465        return true;
466    }
467
468    /**
469     * Ban a user from this room.
470     *
471     * @param string $userId The matrix user id of a user.
472     * @param string $reason A reason for banning the user.
473     * @return bool Whether user was banned.
474     * @throws Exceptions\MatrixException
475     */
476    public function banUser(string $userId, string $reason = ''): bool {
477        try {
478            $this->api()->banUser($this->roomId, $userId, $reason);
479        } catch (MatrixRequestException $e) {
480            return false;
481        }
482
483        return true;
484    }
485
486    /**
487     * Leave the room.
488     *
489     * @return bool Leaving the room was successful.
490     * @throws Exceptions\MatrixException
491     * @throws Exceptions\MatrixHttpLibException
492     */
493    public function leave() {
494        try {
495            $this->api()->leaveRoom($this->roomId);
496            $this->client->forgetRoom($this->roomId);
497        } catch (MatrixRequestException $e) {
498            return false;
499        }
500
501        return true;
502    }
503
504    /**
505     * Updates $this->name and returns true if room name has changed.
506     * @return bool
507     * @throws Exceptions\MatrixException
508     */
509    public function updateRoomName() {
510        try {
511            $response = $this->api()->getRoomName($this->roomId);
512            $newName = array_get($response, 'name', $this->name);
513            $this->name = $newName;
514            if ($this->name != $newName) {
515                $this->name = $newName;
516                return true;
517            }
518        } catch (MatrixRequestException $e) {
519        }
520
521        return false;
522    }
523
524    /**
525     * Return True if room name successfully changed.
526     *
527     * @param string $name
528     * @return bool
529     * @throws Exceptions\MatrixException
530     */
531    public function setRoomName(string $name) {
532        try {
533            $this->api()->setRoomName($this->roomId, $name);
534            $this->name = $name;
535        } catch (MatrixRequestException $e) {
536            return false;
537        }
538
539        return true;
540    }
541
542    /**
543     * Send a state event to the room.
544     *
545     * @param string $eventType The type of event that you are sending.
546     * @param array $content An object with the content of the message.
547     * @param string $stateKey Optional. A unique key to identify the state.
548     * @throws Exceptions\MatrixException
549     */
550    public function sendStateEvent(string $eventType, array $content, string $stateKey = '') {
551        $this->api()->sendStateEvent($this->roomId, $eventType, $content, $stateKey);
552    }
553
554    /**
555     * Updates $this->topic and returns true if room topic has changed.
556     *
557     * @return bool
558     * @throws Exceptions\MatrixException
559     */
560    public function updateRoomTopic() {
561        try {
562            $response = $this->api()->getRoomTopic($this->roomId);
563            $oldTopic = $this->topic;
564            $this->topic = array_get($response, 'topic', $this->topic);
565        } catch (MatrixRequestException $e) {
566            return false;
567        }
568
569        return $oldTopic == $this->topic;
570    }
571
572    /**
573     * Return True if room topic successfully changed.
574     *
575     * @param string $topic
576     * @return bool
577     * @throws Exceptions\MatrixException
578     */
579    public function setRoomTopic(string $topic) {
580        try {
581            $this->api()->setRoomTopic($this->roomId, $topic);
582            $this->topic = $topic;
583        } catch (MatrixRequestException $e) {
584            return false;
585        }
586
587        return true;
588    }
589
590    /**
591     * Get aliases information from room state.
592     *
593     * @return bool True if the aliases changed, False if not
594     * @throws Exceptions\MatrixException
595     * @throws Exceptions\MatrixHttpLibException
596     */
597    public function updateAliases() {
598        try {
599            $response = $this->api()->getRoomState($this->roomId);
600            $oldAliases = $this->aliases;
601            foreach ($response as $chunk) {
602                if ($aliases = array_get($chunk, 'content.aliases')) {
603                    $this->aliases = $aliases;
604                    return $this->aliases == $oldAliases;
605                }
606            }
607        } catch (MatrixRequestException $e) {
608            return false;
609        }
610    }
611
612    /**
613     * Add an alias to the room and return True if successful.
614     *
615     * @param string $alias
616     * @return bool
617     * @throws Exceptions\MatrixException
618     * @throws Exceptions\MatrixHttpLibException
619     */
620    public function addRoomAlias(string $alias) {
621        try {
622            $this->api()->setRoomAlias($this->roomId, $alias);
623        } catch (MatrixRequestException $e) {
624            return false;
625        }
626
627        return true;
628    }
629
630    public function getJoinedMembers() {
631        if ($this->_members) {
632            return array_values($this->_members);
633        }
634        $response = $this->api()->getRoomMembers($this->roomId);
635        foreach ($response['chunk'] as $event) {
636            if (array_get($event, 'event.membership') == 'join') {
637                $userId = $event['state_key'];
638                $this->addMember($userId, array_get($event, 'content.displayname'));
639            }
640        }
641
642        return array_values($this->_members);
643    }
644
645    protected function addMember(string $userId, ?string $displayname) {
646        if ($displayname) {
647            $this->membersDisplaynames[$userId] = $displayname;
648        }
649        if (array_key_exists($userId, $this->_members)) {
650            return;
651        }
652        if (array_key_exists($userId, $this->client->users)) {
653            $this->_members[$userId] = $this->client->users[$userId];
654            return;
655        }
656        $this->_members[$userId] = new User($this->api(), $userId, $displayname);
657        $this->client->users[$userId] = $this->_members[$userId];
658    }
659
660    /**
661     * Backfill handling of previous messages.
662     *
663     * @param bool $reverse When false messages will be backfilled in their original
664     *          order (old to new), otherwise the order will be reversed (new to old).
665     * @param int $limit Number of messages to go back.
666     * @throws Exceptions\MatrixException
667     * @throws Exceptions\MatrixHttpLibException
668     * @throws MatrixRequestException
669     */
670    public function backfillPreviousMessages(bool $reverse = false, int $limit = 10) {
671        $res = $this->api()->getRoomMessages($this->roomId, $this->prevBatch, 'b', $limit);
672        $events = $res['chunk'];
673        if (!$reverse) {
674            $events = array_reverse($events);
675        }
676        foreach ($events as $event) {
677            $this->putEvent($event);
678        }
679    }
680
681    /**
682     * Modify the power level for a subset of users
683     *
684     * @param array $users Power levels to assign to specific users, in the form
685     *          {"@name0:host0": 10, "@name1:host1": 100, "@name3:host3", None}
686     *          A level of None causes the user to revert to the default level
687     *          as specified by users_default.
688     * @param int $userDefault Default power level for users in the room
689     * @return bool
690     * @throws Exceptions\MatrixException
691     */
692    public function modifyUserPowerLevels(array $users = null, int $userDefault = null) {
693        try {
694            $content = $this->api()->getPowerLevels($this->roomId);
695            if ($userDefault) {
696                $content['user_default'] = $userDefault;
697            }
698
699            if ($users) {
700                if (array_key_exists('users', $content)) {
701                    $content['users'] = array_merge($content['users'], $content);
702                } else {
703                    $content['users'] = $users;
704                }
705
706                // Remove any keys with value null
707                foreach ($content['users'] as $user => $pl) {
708                    if (!$pl) {
709                        unset($content['users'][$user]);
710                    }
711                }
712            }
713
714            $this->api()->setPowerLevels($this->roomId, $content);
715        } catch (MatrixRequestException $e) {
716            return false;
717        }
718
719        return true;
720    }
721
722    /**
723     * Modifies room power level requirements.
724     *
725     * @param array $events Power levels required for sending specific event types,
726     *          in the form {"m.room.whatever0": 60, "m.room.whatever2": None}.
727     *          Overrides events_default and state_default for the specified
728     *          events. A level of None causes the target event to revert to the
729     *          default level as specified by events_default or state_default.
730     * @param array $extra Key/value pairs specifying the power levels required for
731     *          various actions:
732     *
733     *          - events_default(int): Default level for sending message events
734     *          - state_default(int): Default level for sending state events
735     *          - invite(int): Inviting a user
736     *          - redact(int): Redacting an event
737     *          - ban(int): Banning a user
738     *          - kick(int): Kicking a user
739     * @return bool
740     * @throws Exceptions\MatrixException
741     */
742    public function modifyRequiredPowerLevels(array $events = [], array $extra = []) {
743        try {
744            $content = $this->api()->getPowerLevels($this->roomId);
745            $content = array_merge($content, $extra);
746            foreach ($content as $k => $v) {
747                if (!$v) {
748                    unset($content[$k]);
749                }
750            }
751
752            if ($events) {
753                if (array_key_exists('events', $content)) {
754                    $content["events"] = array_merge($content["events"], $events);
755                } else {
756                    $content["events"] = $events;
757                }
758
759                // Remove any keys with value null
760                foreach ($content['event'] as $event => $pl) {
761                    if (!$pl) {
762                        unset($content['event'][$event]);
763                    }
764                }
765            }
766
767            $this->api()->setPowerLevels($this->roomId, $content);
768        } catch (MatrixRequestException $e) {
769            return false;
770        }
771
772        return true;
773    }
774
775    /**
776     * Set how the room can be joined.
777     *
778     * @param bool $inviteOnly If True, users will have to be invited to join
779     *          the room. If False, anyone who knows the room link can join.
780     * @return bool True if successful, False if not
781     * @throws Exceptions\MatrixException
782     */
783    public function setInviteOnly(bool $inviteOnly) {
784        $joinRule = $inviteOnly ? 'invite' : 'public';
785        try {
786            $this->api()->setJoinRule($this->roomId, $joinRule);
787            $this->inviteOnly = $inviteOnly;
788        } catch (MatrixRequestException $e) {
789            return false;
790        }
791
792        return true;
793    }
794
795    /**
796     * Set whether guests can join the room and return True if successful.
797     *
798     * @param bool $allowGuest
799     * @return bool
800     * @throws Exceptions\MatrixException
801     */
802    public function setGuestAccess(bool $allowGuest) {
803        $guestAccess = $allowGuest ? 'can_join' : 'forbidden';
804        try {
805            $this->api()->setGuestAccess($this->roomId, $guestAccess);
806            $this->guestAccess = $allowGuest;
807        } catch (MatrixRequestException $e) {
808            return false;
809        }
810
811        return true;
812    }
813
814    /**
815     * Enables encryption in the room.
816     *
817     * NOTE: Once enabled, encryption cannot be disabled.
818     *
819     * @return bool True if successful, False if not
820     * @throws Exceptions\MatrixException
821     */
822    public function enableEncryption() {
823        try {
824            $this->sendStateEvent('m.room.encryption', ['algorithm' => 'm.megolm.v1.aes-sha2']);
825            $this->encrypted = true;
826        } catch (MatrixRequestException $e) {
827            return false;
828        }
829
830        return true;
831    }
832
833    public function processStateEvent(array $stateEvent) {
834        if (!array_key_exists('type', $stateEvent)) {
835            return;
836        }
837        $etype = $stateEvent['type'];
838        $econtent = $stateEvent['content'];
839        $clevel = $this->client->cacheLevel();
840
841        // Don't keep track of room state if caching turned off
842        if ($clevel >= Cache::SOME) {
843            switch ($etype) {
844                case 'm.room.name':
845                    $this->name = array_get($econtent, 'name');
846                    break;
847                case 'm.room.canonical_alias':
848                    $this->canonicalAlias = array_get($econtent, 'alias');
849                    break;
850                case 'm.room.topic':
851                    $this->topic = array_get($econtent, 'topic');
852                    break;
853                case 'm.room.aliases':
854                    $this->aliases = array_get($econtent, 'aliases');
855                    break;
856                case 'm.room.join_rules':
857                    $this->inviteOnly = $econtent["join_rule"] == "invite";
858                    break;
859                case 'm.room.guest_access':
860                    $this->guestAccess = $econtent["guest_access"] == "can_join";
861                    break;
862                case 'm.room.encryption':
863                    $this->encrypted = array_get($econtent, 'algorithm') ? true : $this->encrypted;
864                    break;
865                case 'm.room.member':
866                    // tracking room members can be large e.g. #matrix:matrix.org
867                    if ($clevel == Cache::ALL) {
868                        if ($econtent['membership'] == 'join') {
869                            $userId = $stateEvent['state_key'];
870                            $this->addMember($userId, array_get($econtent, 'displayname'));
871                        } elseif (in_array($econtent["membership"], ["leave", "kick", "invite"])) {
872                            unset($this->_members[array_get($stateEvent, 'state_key')]);
873                        }
874                    }
875                    break;
876            }
877        }
878
879        foreach ($this->stateListeners as $listener) {
880            if (!$listener['event_type'] || $listener['event_type'] == $stateEvent['type']) {
881                $listener['cb']($stateEvent);
882            }
883        }
884    }
885
886    public function getMembersDisplayNames(): array {
887        return $this->membersDisplaynames;
888    }
889
890    protected function api(): MatrixHttpApi {
891        return $this->client->api();
892    }
893
894
895}
896