text:
import { MockedObject } from 'jest-mock';

import * as utils from "../test-utils/test-utils";
import { makeBeaconEvent, makeBeaconInfoEvent } from "../test-utils/beacon";
import { filterEmitCallsByEventType } from "../test-utils/emitter";
import { RoomState, RoomStateEvent } from "../../src/models/room-state";
import {
    Beacon,
    BeaconEvent,
    getBeaconInfoIdentifier,
} from "../../src/models/beacon";
import { EventType, RelationType, UNSTABLE_MSC2716_MARKER } from "../../src/@types/event";
import {
    MatrixEvent,
    MatrixEventEvent,
} from "../../src/models/event";
import { M_BEACON } from "../../src/@types/beacon";
import { MatrixClient } from "../../src/client";

describe("RoomState", function() {
    const roomId = "!foo:bar";
    const userA = "@alice:bar";
    const userB = "@bob:bar";
    const userC = "@cleo:bar";
    const userLazy = "@lazy:bar";

    let state = new RoomState(roomId);

    beforeEach(function() {
        state = new RoomState(roomId);
        state.setStateEvents([
            utils.mkMembership({  // userA joined
                event: true, mship: "join", user: userA, room: roomId,
            }),
            utils.mkMembership({  // userB joined
                event: true, mship: "join", user: userB, room: roomId,
            }),
            utils.mkEvent({  // Room name is "Room name goes here"
                type: "m.room.name", user: userA, room: roomId, event: true, content: {
                    name: "Room name goes here",
                },
            }),
            utils.mkEvent({  // Room creation
                type: "m.room.create", user: userA, room: roomId, event: true, content: {
                    creator: userA,
                },
            }),
        ]);
    });

    describe("getMembers", function() {
        it("should return an empty list if there are no members", function() {
            state = new RoomState(roomId);
            expect(state.getMembers().length).toEqual(0);
        });

        it("should return a member for each m.room.member event", function() {
            const members = state.getMembers();
            expect(members.length).toEqual(2);
            // ordering unimportant
            expect([userA, userB].indexOf(members[0].userId)).not.toEqual(-1);
            expect([userA, userB].indexOf(members[1].userId)).not.toEqual(-1);
        });
    });

    describe("getMember", function() {
        it("should return null if there is no member", function() {
            expect(state.getMember("@no-one:here")).toEqual(null);
        });

        it("should return a member if they exist", function() {
            expect(state.getMember(userB)).toBeTruthy();
        });

        it("should return a member which changes as state changes", function() {
            const member = state.getMember(userB);
            expect(member?.membership).toEqual("join");
            expect(member?.name).toEqual(userB);

            state.setStateEvents([
                utils.mkMembership({
                    room: roomId, user: userB, mship: "leave", event: true,
                    name: "BobGone",
                }),
            ]);

            expect(member?.membership).toEqual("leave");
            expect(member?.name).toEqual("BobGone");
        });
    });

    describe("getSentinelMember", function() {
        it("should return a member with the user id as name", function() {
            expect(state.getSentinelMember("@no-one:here")?.name).toEqual("@no-one:here");
        });

        it("should return a member which doesn't change when the state is updated",
            function() {
                const preLeaveUser = state.getSentinelMember(userA);
                state.setStateEvents([
                    utils.mkMembership({
                        room: roomId, user: userA, mship: "leave", event: true,
                        name: "AliceIsGone",
                    }),
                ]);
                const postLeaveUser = state.getSentinelMember(userA);

                expect(preLeaveUser?.membership).toEqual("join");
                expect(preLeaveUser?.name).toEqual(userA);

                expect(postLeaveUser?.membership).toEqual("leave");
                expect(postLeaveUser?.name).toEqual("AliceIsGone");
            });
    });

    describe("getStateEvents", function() {
        it("should return null if a state_key was specified and there was no match",
            function() {
                expect(state.getStateEvents("foo.bar.baz", "keyname")).toEqual(null);
            });

        it("should return an empty list if a state_key was not specified and there" +
            " was no match", function() {
            expect(state.getStateEvents("foo.bar.baz")).toEqual([]);
        });

        it("should return a list of matching events if no state_key was specified",
            function() {
                const events = state.getStateEvents("m.room.member");
                expect(events.length).toEqual(2);
                // ordering unimportant
                expect([userA, userB].indexOf(events[0].getStateKey() as string)).not.toEqual(-1);
                expect([userA, userB].indexOf(events[1].getStateKey() as string)).not.toEqual(-1);
            });

        it("should return a single MatrixEvent if a state_key was specified",
            function() {
                const event = state.getStateEvents("m.room.member", userA);
                expect(event?.getContent()).toMatchObject({
                    membership: "join",
                });
            });
    });

    describe("setStateEvents", function() {
        it("should emit 'RoomState.members' for each m.room.member event", function() {
            const memberEvents = [
                utils.mkMembership({
                    user: "@cleo:bar", mship: "invite", room: roomId, event: true,
                }),
                utils.mkMembership({
                    user: "@daisy:bar", mship: "join", room: roomId, event: true,
                }),
            ];
            let emitCount = 0;
            state.on(RoomStateEvent.Members, function(ev, st, mem) {
                expect(ev).toEqual(memberEvents[emitCount]);
                expect(st).toEqual(state);
                expect(mem).toEqual(state.getMember(ev.getSender()!));
                emitCount += 1;
            });
            state.setStateEvents(memberEvents);
            expect(emitCount).toEqual(2);
        });

        it("should emit 'RoomState.newMember' for each new member added", function() {
            const memberEvents = [
                utils.mkMembership({
                    user: "@cleo:bar", mship: "invite", room: roomId, event: true,
                }),
                utils.mkMembership({
                    user: "@daisy:bar", mship: "join", room: roomId, event: true,
                }),
            ];
            let emitCount = 0;
            state.on(RoomStateEvent.NewMember, function(ev, st, mem) {
                expect(state.getMember(mem.userId)).toEqual(mem);
                expect(mem.userId).toEqual(memberEvents[emitCount].getSender());
                expect(mem.membership).toBeFalsy();  // not defined yet
                emitCount += 1;
            });
            state.setStateEvents(memberEvents);
            expect(emitCount).toEqual(2);
        });

        it("should emit 'RoomState.events' for each state event", function() {
            const events = [
                utils.mkMembership({
                    user: "@cleo:bar", mship: "invite", room: roomId, event: true,
                }),
                utils.mkEvent({
                    user: userB, room: roomId, type: "m.room.topic", event: true,
                    content: {
                        topic: "boo!",
                    },
                }),
                utils.mkMessage({  // Not a state event
                    user: userA, room: roomId, event: true,
                }),
            ];
            let emitCount = 0;
            state.on(RoomStateEvent.Events, function(ev, st) {
                expect(ev).toEqual(events[emitCount]);
                expect(st).toEqual(state);
                emitCount += 1;
            });
            state.setStateEvents(events);
            expect(emitCount).toEqual(2);
        });

        it("should call setPowerLevelEvent on each RoomMember for m.room.power_levels", function() {
            const powerLevelEvent = utils.mkEvent({
                type: "m.room.power_levels", room: roomId, user: userA, event: true,
                content: {
                    users_default: 10,
                    state_default: 50,
                    events_default: 25,
                },
            });

            // spy on the room members
            jest.spyOn(state.members[userA], "setPowerLevelEvent");
            jest.spyOn(state.members[userB], "setPowerLevelEvent");
            state.setStateEvents([powerLevelEvent]);

            expect(state.members[userA].setPowerLevelEvent).toHaveBeenCalledWith(powerLevelEvent);
            expect(state.members[userB].setPowerLevelEvent).toHaveBeenCalledWith(powerLevelEvent);
        });

        it("should call setPowerLevelEvent on a new RoomMember if power levels exist", function() {
            const memberEvent = utils.mkMembership({
                mship: "join", user: userC, room: roomId, event: true,
            });
            const powerLevelEvent = utils.mkEvent({
                type: "m.room.power_levels", room: roomId, user: userA, event: true,
                content: {
                    users_default: 10,
                    state_default: 50,
                    events_default: 25,
                    users: {},
                },
            });

            state.setStateEvents([powerLevelEvent]);
            state.setStateEvents([memberEvent]);

            // TODO: We do this because we don't DI the RoomMember constructor
            // so we can't inject a mock :/ so we have to infer.
            expect(state.members[userC]).toBeTruthy();
            expect(state.members[userC].powerLevel).toEqual(10);
        });

        it("should call setMembershipEvent on the right RoomMember", function() {
            const memberEvent = utils.mkMembership({
                user: userB, mship: "leave", room: roomId, event: true,
            });
            // spy on the room members
            jest.spyOn(state.members[userA], "setMembershipEvent");
            jest.spyOn(state.members[userB], "setMembershipEvent");
            state.setStateEvents([memberEvent]);

            expect(state.members[userA].setMembershipEvent).not.toHaveBeenCalled();
            expect(state.members[userB].setMembershipEvent).toHaveBeenCalledWith(
                memberEvent, state,
            );
        });

        it("should emit `RoomStateEvent.Marker` for each marker event", function() {
            const events = [
                utils.mkEvent({
                    event: true,
                    type: UNSTABLE_MSC2716_MARKER.name,
                    room: roomId,
                    user: userA,
                    skey: "",
                    content: {
                        "m.insertion_id": "$abc",
                    },
                }),
            ];
            let emitCount = 0;
            state.on(RoomStateEvent.Marker, function(markerEvent, markerFoundOptions) {
                expect(markerEvent).toEqual(events[emitCount]);
                expect(markerFoundOptions).toEqual({ timelineWasEmpty: true });
                emitCount += 1;
            });
            state.setStateEvents(events, { timelineWasEmpty: true });
            expect(emitCount).toEqual(1);
        });
    });

    describe('beacon events', () => {
        it('adds new beacon info events to state and emits', () => {
            const beaconEvent = makeBeaconInfoEvent(userA, roomId);
            const emitSpy = jest.spyOn(state, 'emit');

            state.setStateEvents([beaconEvent]);

            expect(state.beacons.size).toEqual(1);
            const beaconInstance = state.beacons.get(`${roomId}_${userA}`);
            expect(beaconInstance).toBeTruthy();
            expect(emitSpy).toHaveBeenCalledWith(BeaconEvent.New, beaconEvent, beaconInstance);
        });

        it('does not add redacted beacon info events to state', () => {
            const redactedBeaconEvent = makeBeaconInfoEvent(userA, roomId);
            const redactionEvent = new MatrixEvent({ type: 'm.room.redaction' });
            redactedBeaconEvent.makeRedacted(redactionEvent);
            const emitSpy = jest.spyOn(state, 'emit');

            state.setStateEvents([redactedBeaconEvent]);

            // no beacon added
            expect(state.beacons.size).toEqual(0);
            expect(state.beacons.get(getBeaconInfoIdentifier(redactedBeaconEvent))).toBeFalsy();
            // no new beacon emit
            expect(filterEmitCallsByEventType(BeaconEvent.New, emitSpy).length).toBeFalsy();
        });

        it('updates existing beacon info events in state', () => {
            const beaconId = '$beacon1';
            const beaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId);
            const updatedBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: false }, beaconId);

            state.setStateEvents([beaconEvent]);
            const beaconInstance = state.beacons.get(getBeaconInfoIdentifier(beaconEvent));
            expect(beaconInstance?.isLive).toEqual(true);

            state.setStateEvents([updatedBeaconEvent]);

            // same Beacon
            expect(state.beacons.get(getBeaconInfoIdentifier(beaconEvent))).toBe(beaconInstance);
            // updated liveness
            expect(state.beacons.get(getBeaconInfoIdentifier(beaconEvent))?.isLive).toEqual(false);
        });

        it('destroys and removes redacted beacon events', () => {
            const beaconId = '$beacon1';
            const beaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId);
            const redactedBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, beaconId);
            const redactionEvent = new MatrixEvent({ type: 'm.room.redaction', redacts: beaconEvent.getId() });
            redactedBeaconEvent.makeRedacted(redactionEvent);

            state.setStateEvents([beaconEvent]);
            const beaconInstance = state.beacons.get(getBeaconInfoIdentifier(beaconEvent));
            const destroySpy = jest.spyOn(beaconInstance as Beacon, 'destroy');
            expect(beaconInstance?.isLive).toEqual(true);

            state.setStateEvents([redactedBeaconEvent]);

            expect(destroySpy).toHaveBeenCalled();
            expect(state.beacons.get(getBeaconInfoIdentifier(beaconEvent))).toBe(undefined);
        });

        it('updates live beacon ids once after setting state events', () => {
            const liveBeaconEvent = makeBeaconInfoEvent(userA, roomId, { isLive: true }, '$beacon1');
            const deadBeaconEvent = makeBeaconInfoEvent(userB, roomId, { isLive: false }, '$beacon2');

            const emitSpy = jest.spyOn(state, 'emit');

            state.setStateEvents([liveBeaconEvent, deadBeaconEvent]);

            // called once
            expect(filterEmitCallsByEventType(RoomStateEvent.BeaconLiveness, emitSpy).length).toBe(1);

            // live beacon is now not live
            const updatedLiveBeaconEvent = makeBeaconInfoEvent(
                userA, roomId, { isLive: false }, liveBeaconEvent.getId(),
            );

            state.setStateEvents([updatedLiveBeaconEvent]);

            expect(state.hasLiveBeacons).toBe(false);
            expect(filterEmitCallsByEventType(RoomStateEvent.BeaconLiveness, emitSpy).length).toBe(3);
            expect(emitSpy).toHaveBeenCalledWith(RoomStateEvent.BeaconLiveness, state, false);
        });
    });

    describe("setOutOfBandMembers", function() {
        it("should add a new member", function() {
            const oobMemberEvent = utils.mkMembership({
                user: userLazy, mship: "join", room: roomId, event: true,
            });
            state.markOutOfBandMembersStarted();
            state.setOutOfBandMembers([oobMemberEvent]);
            const member = state.getMember(userLazy);
            expect(member?.userId).toEqual(userLazy);
            expect(member?.isOutOfBand()).toEqual(true);
        });

        it("should have no effect when not in correct status", function() {
            state.setOutOfBandMembers([utils.mkMembership({
                user: userLazy, mship: "join", room: roomId, event: true,
            })]);
            expect(state.getMember(userLazy)).toBeFalsy();
        });

        it("should emit newMember when adding a member", function() {
            const userLazy = "@oob:hs";
            const oobMemberEvent = utils.mkMembership({
                user: userLazy, mship: "join", room: roomId, event: true,
            });
            let eventReceived = false;
            state.once(RoomStateEvent.NewMember, (_event, _state, member) => {
                expect(member.userId).toEqual(userLazy);
                eventReceived = true;
            });
            state.markOutOfBandMembersStarted();
            state.setOutOfBandMembers([oobMemberEvent]);
            expect(eventReceived).toEqual(true);
        });

        it("should never overwrite existing members", function() {
            const oobMemberEvent = utils.mkMembership({
                user: userA, mship: "join", room: roomId, event: true,
            });
            state.markOutOfBandMembersStarted();
            state.setOutOfBandMembers([oobMemberEvent]);
            const memberA = state.getMember(userA);
            expect(memberA?.events?.member?.getId()).not.toEqual(oobMemberEvent.getId());
            expect(memberA?.isOutOfBand()).toEqual(false);
        });

        it("should emit members when updating a member", function() {
            const doesntExistYetUserId = "@doesntexistyet:hs";
            const oobMemberEvent = utils.mkMembership({
                user: doesntExistYetUserId, mship: "join", room: roomId, event: true,
            });
            let eventReceived = false;
            state.once(RoomStateEvent.Members, (_event, _state, member) => {
                expect(member.userId).toEqual(doesntExistYetUserId);
                eventReceived = true;
            });

            state.markOutOfBandMembersStarted();
            state.setOutOfBandMembers([oobMemberEvent]);
            expect(eventReceived).toEqual(true);
        });
    });

    describe("clone", function() {
        it("should contain same information as original", function() {
            // include OOB members in copy
            state.markOutOfBandMembersStarted();
            state.setOutOfBandMembers([utils.mkMembership({
                user: userLazy, mship: "join", room: roomId, event: true,
            })]);
            const copy = state.clone();
            // check individual members
            [userA, userB, userLazy].forEach((userId) => {
                const member = state.getMember(userId);
                const memberCopy = copy.getMember(userId);
                expect(member?.name).toEqual(memberCopy?.name);
                expect(member?.isOutOfBand()).toEqual(memberCopy?.isOutOfBand());
            });
            // check member keys
            expect(Object.keys(state.members)).toEqual(Object.keys(copy.members));
            // check join count
            expect(state.getJoinedMemberCount()).toEqual(copy.getJoinedMemberCount());
        });

        it("should mark old copy as not waiting for out of band anymore", function() {
            state.markOutOfBandMembersStarted();
            const copy = state.clone();
            copy.setOutOfBandMembers([utils.mkMembership({
                user: userA, mship: "join", room: roomId, event: true,
            })]);
            // should have no effect as it should be marked in status finished just like copy
            state.setOutOfBandMembers([utils.mkMembership({
                user: userLazy, mship: "join", room: roomId, event: true,
            })]);
            expect(state.getMember(userLazy)).toBeFalsy();
        });

        it("should return copy independent of original", function() {
            const copy = state.clone();
            copy.setStateEvents([utils.mkMembership({
                user: userLazy, mship: "join", room: roomId, event: true,
            })]);

            expect(state.getMember(userLazy)).toBeFalsy();
            expect(state.getJoinedMemberCount()).toEqual(2);
            expect(copy.getJoinedMemberCount()).toEqual(3);
        });
    });

    describe("setTypingEvent", function() {
        it("should call setTypingEvent on each RoomMember", function() {
            const typingEvent = utils.mkEvent({
                type: "m.typing", room: roomId, event: true, content: {
                    user_ids: [userA],
                },
            });
            // spy on the room members
            jest.spyOn(state.members[userA], "setTypingEvent");
            jest.spyOn(state.members[userB], "setTypingEvent");
            state.setTypingEvent(typingEvent);

            expect(state.members[userA].setTypingEvent).toHaveBeenCalledWith(typingEvent);
            expect(state.members[userB].setTypingEvent).toHaveBeenCalledWith(typingEvent);
        });
    });

    describe("maySendStateEvent", function() {
        it("should say any member may send state with no power level event",
            function() {
                expect(state.maySendStateEvent('m.room.name', userA)).toEqual(true);
            });

        it("should say members with power >=50 may send state with power level event " +
        "but no state default",
        function() {
            const powerLevelEvent = new MatrixEvent({
                type: "m.room.power_levels", room_id: roomId, sender: userA,
                state_key: "",
                content: {
                    users_default: 10,
                    // state_default: 50, "intentionally left blank"
                    events_default: 25,
                    users: {
                        [userA]: 50,
                    },
                },
            });

            state.setStateEvents([powerLevelEvent]);

            expect(state.maySendStateEvent('m.room.name', userA)).toEqual(true);
            expect(state.maySendStateEvent('m.room.name', userB)).toEqual(false);
        });

        it("should obey state_default",
            function() {
                const powerLevelEvent = new MatrixEvent({
                    type: "m.room.power_levels", room_id: roomId, sender: userA,
                    state_key: "",
                    content: {
                        users_default: 10,
                        state_default: 30,
                        events_default: 25,
                        users: {
                            [userA]: 30,
                            [userB]: 29,
                        },
                    },
                });

                state.setStateEvents([powerLevelEvent]);

                expect(state.maySendStateEvent('m.room.name', userA)).toEqual(true);
                expect(state.maySendStateEvent('m.room.name', userB)).toEqual(false);
            });

        it("should honour explicit event power levels in the power_levels event",
            function() {
                const powerLevelEvent = new MatrixEvent({
                    type: "m.room.power_levels", room_id: roomId, sender: userA,
                    state_key: "", content: {
                        events: {
                            "m.room.other_thing": 76,
                        },
                        users_default: 10,
                        state_default: 50,
                        events_default: 25,
                        users: {
                            [userA]: 80,
                            [userB]: 50,
                        },
                    },
                });

                state.setStateEvents([powerLevelEvent]);

                expect(state.maySendStateEvent('m.room.name', userA)).toEqual(true);
                expect(state.maySendStateEvent('m.room.name', userB)).toEqual(true);

                expect(state.maySendStateEvent('m.room.other_thing', userA)).toEqual(true);
                expect(state.maySendStateEvent('m.room.other_thing', userB)).toEqual(false);
            });
    });

    describe("getJoinedMemberCount", function() {
        beforeEach(() => {
            state = new RoomState(roomId);
        });

        it("should update after adding joined member", function() {
            state.setStateEvents([
                utils.mkMembership({ event: true, mship: "join",
                    user: userA, room: roomId }),
            ]);
            expect(state.getJoinedMemberCount()).toEqual(1);
            state.setStateEvents([
                utils.mkMembership({ event: true, mship: "join",
                    user: userC, room: roomId }),
            ]);
            expect(state.getJoinedMemberCount()).toEqual(2);
        });
    });

    describe("getInvitedMemberCount", function() {
        beforeEach(() => {
            state = new RoomState(roomId);
        });

        it("should update after adding invited member", function() {
            state.setStateEvents([
                utils.mkMembership({ event: true, mship: "invite",
                    user: userA, room: roomId }),
            ]);
            expect(state.getInvitedMemberCount()).toEqual(1);
            state.setStateEvents([
                utils.mkMembership({ event: true, mship: "invite",
                    user: userC, room: roomId }),
            ]);
            expect(state.getInvitedMemberCount()).toEqual(2);
        });
    });

    describe("setJoinedMemberCount", function() {
        beforeEach(() => {
            state = new RoomState(roomId);
        });

        it("should, once used, override counting members from state", function() {
            state.setStateEvents([
                utils.mkMembership({ event: true, mship: "join",
                    user: userA, room: roomId }),
            ]);
            expect(state.getJoinedMemberCount()).toEqual(1);
            state.setJoinedMemberCount(100);
            expect(state.getJoinedMemberCount()).toEqual(100);
            state.setStateEvents([
                utils.mkMembership({ event: true, mship: "join",
                    user: userC, room: roomId }),
            ]);
            expect(state.getJoinedMemberCount()).toEqual(100);
        });

        it("should, once used, override counting members from state, " +
        "also after clone", function() {
            state.setStateEvents([
                utils.mkMembership({ event: true, mship: "join",
                    user: userA, room: roomId }),
            ]);
            state.setJoinedMemberCount(100);
            const copy = state.clone();
            copy.setStateEvents([
                utils.mkMembership({ event: true, mship: "join",
                    user: userC, room: roomId }),
            ]);
            expect(state.getJoinedMemberCount()).toEqual(100);
        });
    });

    describe("setInvitedMemberCount", function() {
        beforeEach(() => {
            state = new RoomState(roomId);
        });

        it("should, once used, override counting members from state", function() {
            state.setStateEvents([
                utils.mkMembership({ event: true, mship: "invite",
                    user: userB, room: roomId }),
            ]);
            expect(state.getInvitedMemberCount()).toEqual(1);
            state.setInvitedMemberCount(100);
            expect(state.getInvitedMemberCount()).toEqual(100);
            state.setStateEvents([
                utils.mkMembership({ event: true, mship: "invite",
                    user: userC, room: roomId }),
            ]);
            expect(state.getInvitedMemberCount()).toEqual(100);
        });

        it("should, once used, override counting members from state, " +
        "also after clone", function() {
            state.setStateEvents([
                utils.mkMembership({ event: true, mship: "invite",
                    user: userB, room: roomId }),
            ]);
            state.setInvitedMemberCount(100);
            const copy = state.clone();
            copy.setStateEvents([
                utils.mkMembership({ event: true, mship: "invite",
                    user: userC, room: roomId }),
            ]);
            expect(state.getInvitedMemberCount()).toEqual(100);
        });
    });

    describe("maySendEvent", function() {
        it("should say any member may send events with no power level event",
            function() {
                expect(state.maySendEvent('m.room.message', userA)).toEqual(true);
                expect(state.maySendMessage(userA)).toEqual(true);
            });

        it("should obey events_default",
            function() {
                const powerLevelEvent = new MatrixEvent({
                    type: "m.room.power_levels", room_id: roomId, sender: userA,
                    state_key: "",
                    content: {
                        users_default: 10,
                        state_default: 30,
                        events_default: 25,
                        users: {
                            [userA]: 26,
                            [userB]: 24,
                        },
                    },
                });

                state.setStateEvents([powerLevelEvent]);

                expect(state.maySendEvent('m.room.message', userA)).toEqual(true);
                expect(state.maySendEvent('m.room.message', userB)).toEqual(false);

                expect(state.maySendMessage(userA)).toEqual(true);
                expect(state.maySendMessage(userB)).toEqual(false);
            });

        it("should honour explicit event power levels in the power_levels event",
            function() {
                const powerLevelEvent = new MatrixEvent({
                    type: "m.room.power_levels", room_id: roomId, sender: userA,
                    state_key: "",
                    content: {
                        events: {
                            "m.room.other_thing": 33,
                        },
                        users_default: 10,
                        state_default: 50,
                        events_default: 25,
                        users: {
                            [userA]: 40,
                            [userB]: 30,
                        },
                    },
                });

                state.setStateEvents([powerLevelEvent]);

                expect(state.maySendEvent('m.room.message', userA)).toEqual(true);
                expect(state.maySendEvent('m.room.message', userB)).toEqual(true);

                expect(state.maySendMessage(userA)).toEqual(true);
                expect(state.maySendMessage(userB)).toEqual(true);

                expect(state.maySendEvent('m.room.other_thing', userA)).toEqual(true);
                expect(state.maySendEvent('m.room.other_thing', userB)).toEqual(false);
            });
    });

    describe('processBeaconEvents', () => {
        const beacon1 = makeBeaconInfoEvent(userA, roomId, {}, '$beacon1');
        const beacon2 = makeBeaconInfoEvent(userB, roomId, {}, '$beacon2');

        const mockClient = { decryptEventIfNeeded: jest.fn() } as unknown as MockedObject<MatrixClient>;

        beforeEach(() => {
            mockClient.decryptEventIfNeeded.mockClear();
        });

        it('does nothing when state has no beacons', () => {
            const emitSpy = jest.spyOn(state, 'emit');
            state.processBeaconEvents([makeBeaconEvent(userA, { beaconInfoId: '$beacon1' })], mockClient);
            expect(emitSpy).not.toHaveBeenCalled();
            expect(mockClient.decryptEventIfNeeded).not.toHaveBeenCalled();
        });

        it('does nothing when there are no events', () => {
            state.setStateEvents([beacon1, beacon2]);
            const emitSpy = jest.spyOn(state, 'emit').mockClear();
            state.processBeaconEvents([], mockClient);
            expect(emitSpy).not.toHaveBeenCalled();
            expect(mockClient.decryptEventIfNeeded).not.toHaveBeenCalled();
        });

        describe('without encryption', () => {
            it('discards events for beacons that are not in state', () => {
                const location = makeBeaconEvent(userA, {
                    beaconInfoId: 'some-other-beacon',
                });
                const otherRelatedEvent = new MatrixEvent({
                    sender: userA,
                    type: EventType.RoomMessage,
                    content: {
                        ['m.relates_to']: {
                            event_id: 'whatever',
                        },
                    },
                });
                state.setStateEvents([beacon1, beacon2]);
                const emitSpy = jest.spyOn(state, 'emit').mockClear();
                state.processBeaconEvents([location, otherRelatedEvent], mockClient);
                expect(emitSpy).not.toHaveBeenCalled();
            });

            it('discards events that are not beacon type', () => {
                // related to beacon1
                const otherRelatedEvent = new MatrixEvent({
                    sender: userA,
                    type: EventType.RoomMessage,
                    content: {
                        ['m.relates_to']: {
                            rel_type: RelationType.Reference,
                            event_id: beacon1.getId(),
                        },
                    },
                });
                state.setStateEvents([beacon1, beacon2]);
                const emitSpy = jest.spyOn(state, 'emit').mockClear();
                state.processBeaconEvents([otherRelatedEvent], mockClient);
                expect(emitSpy).not.toHaveBeenCalled();
            });

            it('adds locations to beacons', () => {
                const location1 = makeBeaconEvent(userA, {
                    beaconInfoId: '$beacon1', timestamp: Date.now() + 1,
                });
                const location2 = makeBeaconEvent(userA, {
                    beaconInfoId: '$beacon1', timestamp: Date.now() + 2,
                });
                const location3 = makeBeaconEvent(userB, {
                    beaconInfoId: 'some-other-beacon',
                });

                state.setStateEvents([beacon1, beacon2]);

                expect(state.beacons.size).toEqual(2);

                const beaconInstance = state.beacons.get(getBeaconInfoIdentifier(beacon1)) as Beacon;
                const addLocationsSpy = jest.spyOn(beaconInstance, 'addLocations');

                state.processBeaconEvents([location1, location2, location3], mockClient);

                expect(addLocationsSpy).toHaveBeenCalledTimes(2);
                // only called with locations for beacon1
                expect(addLocationsSpy).toHaveBeenCalledWith([location1]);
                expect(addLocationsSpy).toHaveBeenCalledWith([location2]);
            });
        });

        describe('with encryption', () => {
            const beacon1RelationContent = { ['m.relates_to']: {
                rel_type: RelationType.Reference,
                event_id: beacon1.getId(),
            } };
            const relatedEncryptedEvent = new MatrixEvent({
                sender: userA,
                type: EventType.RoomMessageEncrypted,
                content: beacon1RelationContent,
            });
            const decryptingRelatedEvent = new MatrixEvent({
                sender: userA,
                type: EventType.RoomMessageEncrypted,
                content: beacon1RelationContent,
            });
            jest.spyOn(decryptingRelatedEvent, 'isBeingDecrypted').mockReturnValue(true);

            const failedDecryptionRelatedEvent = new MatrixEvent({
                sender: userA,
                type: EventType.RoomMessageEncrypted,
                content: beacon1RelationContent,
            });
            jest.spyOn(failedDecryptionRelatedEvent, 'isDecryptionFailure').mockReturnValue(true);

            it('discards events without relations', () => {
                const unrelatedEvent = new MatrixEvent({
                    sender: userA,
                    type: EventType.RoomMessageEncrypted,
                });
                state.setStateEvents([beacon1, beacon2]);
                const emitSpy = jest.spyOn(state, 'emit').mockClear();
                state.processBeaconEvents([unrelatedEvent], mockClient);
                expect(emitSpy).not.toHaveBeenCalled();
                // discard unrelated events early
                expect(mockClient.decryptEventIfNeeded).not.toHaveBeenCalled();
            });

            it('discards events for beacons that are not in state', () => {
                const location = makeBeaconEvent(userA, {
                    beaconInfoId: 'some-other-beacon',
                });
                const otherRelatedEvent = new MatrixEvent({
                    sender: userA,
                    type: EventType.RoomMessageEncrypted,
                    content: {
                        ['m.relates_to']: {
                            rel_type: RelationType.Reference,
                            event_id: 'whatever',
                        },
                    },
                });
                state.setStateEvents([beacon1, beacon2]);

                const beacon = state.beacons.get(getBeaconInfoIdentifier(beacon1)) as Beacon;
                const addLocationsSpy = jest.spyOn(beacon, 'addLocations').mockClear();
                state.processBeaconEvents([location, otherRelatedEvent], mockClient);
                expect(addLocationsSpy).not.toHaveBeenCalled();
                // discard unrelated events early
                expect(mockClient.decryptEventIfNeeded).not.toHaveBeenCalled();
            });

            it('decrypts related events if needed', () => {
                const location = makeBeaconEvent(userA, {
                    beaconInfoId: beacon1.getId(),
                });
                state.setStateEvents([beacon1, beacon2]);
                state.processBeaconEvents([location, relatedEncryptedEvent], mockClient);
                // discard unrelated events early
                expect(mockClient.decryptEventIfNeeded).toHaveBeenCalledTimes(2);
            });

            it('listens for decryption on events that are being decrypted', () => {
                const decryptingRelatedEvent = new MatrixEvent({
                    sender: userA,
                    type: EventType.RoomMessageEncrypted,
                    content: beacon1RelationContent,
                });
                jest.spyOn(decryptingRelatedEvent, 'isBeingDecrypted').mockReturnValue(true);
                // spy on event.once
                const eventOnceSpy = jest.spyOn(decryptingRelatedEvent, 'once');

                state.setStateEvents([beacon1, beacon2]);
                state.processBeaconEvents([decryptingRelatedEvent], mockClient);

                // listener was added
                expect(eventOnceSpy).toHaveBeenCalled();
            });

            it('listens for decryption on events that have decryption failure', () => {
                const failedDecryptionRelatedEvent = new MatrixEvent({
                    sender: userA,
                    type: EventType.RoomMessageEncrypted,
                    content: beacon1RelationContent,
                });
                jest.spyOn(failedDecryptionRelatedEvent, 'isDecryptionFailure').mockReturnValue(true);
                // spy on event.once
                const eventOnceSpy = jest.spyOn(decryptingRelatedEvent, 'once');

                state.setStateEvents([beacon1, beacon2]);
                state.processBeaconEvents([decryptingRelatedEvent], mockClient);

                // listener was added
                expect(eventOnceSpy).toHaveBeenCalled();
            });

            it('discard events that are not m.beacon type after decryption', () => {
                const decryptingRelatedEvent = new MatrixEvent({
                    sender: userA,
                    type: EventType.RoomMessageEncrypted,
                    content: beacon1RelationContent,
                });
                jest.spyOn(decryptingRelatedEvent, 'isBeingDecrypted').mockReturnValue(true);
                state.setStateEvents([beacon1, beacon2]);
                const beacon = state.beacons.get(getBeaconInfoIdentifier(beacon1)) as Beacon;
                const addLocationsSpy = jest.spyOn(beacon, 'addLocations').mockClear();
                state.processBeaconEvents([decryptingRelatedEvent], mockClient);

                // this event is a message after decryption
                decryptingRelatedEvent.event.type = EventType.RoomMessage;
                decryptingRelatedEvent.emit(MatrixEventEvent.Decrypted, decryptingRelatedEvent);

                expect(addLocationsSpy).not.toHaveBeenCalled();
            });

            it('adds locations to beacons after decryption', () => {
                const decryptingRelatedEvent = new MatrixEvent({
                    sender: userA,
                    type: EventType.RoomMessageEncrypted,
                    content: beacon1RelationContent,
                });
                const locationEvent = makeBeaconEvent(userA, {
                    beaconInfoId: '$beacon1', timestamp: Date.now() + 1,
                });
                jest.spyOn(decryptingRelatedEvent, 'isBeingDecrypted').mockReturnValue(true);
                state.setStateEvents([beacon1, beacon2]);
                const beacon = state.beacons.get(getBeaconInfoIdentifier(beacon1)) as Beacon;
                const addLocationsSpy = jest.spyOn(beacon, 'addLocations').mockClear();
                state.processBeaconEvents([decryptingRelatedEvent], mockClient);

                // update type after '''decryption'''
                decryptingRelatedEvent.event.type = M_BEACON.name;
                decryptingRelatedEvent.event.content = locationEvent.event.content;
                decryptingRelatedEvent.emit(MatrixEventEvent.Decrypted, decryptingRelatedEvent);

                expect(addLocationsSpy).toHaveBeenCalledWith([decryptingRelatedEvent]);
            });
        });
    });

    describe("mayClientSendStateEvent", () => {
        it("should return false if the user isn't authenticated", () => {
            expect(state.mayClientSendStateEvent("m.room.message", {
                isGuest: jest.fn().mockReturnValue(false),
                credentials: {},
            } as unknown as MatrixClient)).toBeFalsy();
        });

        it("should return false if the user is a guest", () => {
            expect(state.mayClientSendStateEvent("m.room.message", {
                isGuest: jest.fn().mockReturnValue(true),
                credentials: { userId: userA },
            } as unknown as MatrixClient)).toBeFalsy();
        });
    });
});


text:
Love & Hip Hop: Miami is the fourth installment of the Love & Hip Hop reality television franchise. It premiered on January 1, 2018, on VH1 and chronicles the lives of several people in the Miami area involved with hip hop music. The show features appearances from notable figures associated with Southern hip hop, as well as Latin music and reggaeton.

On July 11, 2022, VH1 announced that additional episodes of season four would air from August 8, 2022.

Development
Producers first considered a Love & Hip Hop spin-off set in Miami in 2012, before settling on Atlanta instead, with producer Stefan Springman revealing in the behind-the-scenes special Love & Hip Hop: Dirty Little Secrets: "we found great characters there but it just didn't feel right". Several episodes in Love & Hip Hop: New York second season were filmed in Miami, originally intended to set up a spin-off starring Teairra Marí and Erica Mena. On July 4, 2014, Scott-Young discussed expanding the franchise to other cities: "we have looked at a bunch of other cities, Miami I've looked at as well, so we're always looking to expand." On November 20, 2014, rapper Trina revealed she had turned down an offer for Love & Hip Hop: Miami.

On February 28, 2016, it was reported that potential spin-offs set in Miami and Houston were in pre-production and the producers were auditioning potential cast members. In April 2016, Trick Daddy and Trina confirmed their involvement in Love & Hip Hop: Miami, despite the latter turning down the offer years earlier. Filming was set to begin in May 2016, with Trick's storyline centered on managing his artists Mike Smiff and Love & Hip Hop: Atlantas PreMadonna. Plies, Brianna Perry and PreMadonna's husband Buck Thomas were also reported to be cast members at this time, however all their scenes were left on the cutting room floor during the show's nearly two year development and filming process. On April 20, 2017, after over a year of development hell, it was reported that the show's producers had been granted permission to start filming in South Florida. At some point, the show's producers approached Gucci Mane and his fiancée Keyshia Ka'oir to star in the series, however they turned it down to film a wedding special for BET.

On May 8, 2017, Gunplay appeared on Love & Hip Hop: Atlanta, leading to speculation that his appearance was intended to set up Love & Hip Hop: Miami.

On August 25, 2017, VH1 announced Love & Hip Hop: Miami would make its series premiere on January 1, 2018.

Series synopsis

Overview and casting

Love & Hip Hop: Miami revolves around the personal and professional struggles of several rappers, singers and socialites in Miami's music scene. The series is notable for its diverse cast, which reflects Miami's racially and economically diverse community. It has a sprawling supporting cast, who (in most cases) share the same amount of screen time and storyline focus as the show's leads.

The original cast consisted of Trina, Trick Daddy, Gunplay, Afro-Latina singer Amara La Negra, Cuban-Venezuelan rapper Veronica Vega, openly gay rapper Bobby Lytes, socialite and party promoter Prince, and Flavor of Loves Shay Johnson. The first season's supporting cast would include Steph Lecor, Latin trap producer Young Hollywood, Keyara Stone, urban model and girlfriend of Gunplay, Grammy Award-nominated singer Pleasure P and his Pretty Ricky bandmate Baby Blue Whoaaaa, stripper-turned rapper Miami Tip, Joy Young, estranged wife of Trick Daddy, celebrity stylist Jojo Zarur, Jeffrey White, JT Money's openly gay son and boyfriend of Bobby Lytes, stylist Malik Williams, DJ Michelle Pooch, Prince's girlfriend Liz Cifuentes, model Gabby Davis, and mother-and-daughter brand ambassadors Chinese Kitty and Chinese Nicky. Although not mentioned in the initial cast announcement, Amara's mother Mami Ana and Jojo's mother Faride Nemer would appear in minor supporting roles. Love & Hip Hop: New Yorks Juju C. and Love & Hip Hop: Atlantas Lil Scrappy would make special crossover appearances.

On November 19, 2018, VH1 confirmed that nearly all of the show's cast would return for a second season, with Jojo promoted to the main cast. New supporting cast members would include Haitian-American comedienne Jessie Woo, rapper Khaotic and Spectacular of Pretty Ricky. Khaotic made headlines during filming, after he was allegedly involved in a hit-and-run incident.

On November 15, 2019, Jojo Zarur announced that the show would return on January 6, 2020. On December 9, 2019, VH1 confirmed the season's premiere date, along with a promo featuring new cast members Sukihana, Brisco, Hood Brat and waist trainer entrepreneur PreMadonna, who had previously been attached to the show when it first started filming in 2016. Jojo would return in a supporting role, while Veronica Vega left the series entirely. Prince, Gunplay and Baby Blue Whoaaaa, Pleasure P and Spectacular of Pretty Ricky filmed scenes for the season and were included as cast members in the season's press release, however were cut from the season when it eventually aired.  On December 11, 2019, VH1 announced the return of Love & Hip Hop: Atlantas Joseline Hernandez to the franchise, who would join the cast alongside Shay Johnson's brother EmJay Johnson and model Nikki Natural, while rapper KaMillion and social media personality Saucy Santana, best known for his friendship with Yung Miami of City Girls, would appear as recurring guest stars. Despite being credited as a main cast member for the entire season and heavily featured in its promotional material, Joseline disappeared from the show entirely after four episodes, after a series of interviews in which she criticised producer Mona Scott-Young.

After over a year long hiatus due to the COVID-19 pandemic, the show was retooled for season four, with only Trina, Amara, Trick and Sukihana returning to the main cast. New cast members include Ace Hood and his wife Shelah Marie, Noreaga and his wife Neri, and Florence El Luche. Bobby Lytes was demoted to supporting cast, along with Florence's sister Gaelle and husband Marlon, Suki's fiancé Kill Bill and cousin Isaiah, Trina's boyfriend Raymond Taylor and manager C.O. Love & Hip Hop: Hollywood Ray J and Princess Love make crossover appearances. The season continued a year later without Ace and Shelah, and with the return of Shay Johnson in a supporting role.

Cast timeline
  Main cast (appears in opening credits)
  Secondary cast (appears in green screen confessional segments and in end credits alongside the main cast)
  Guest cast (appears in a guest role or cameo)

Note:

Storylines
The premiere episode deals with colorism and eurocentrism within the Latin community, including a scene where light-skinned producer Young Hollywood criticises dark-skinned singer Amara La Negra's afro.

Reception

Critical response
Ebony praised the show and Afro-Latina star Amara La Negra for "inviting cultural conversations" about misogynoir and the underrepresentation of black Latinas in mainstream entertainment, hoping the franchise would continue to go in a more "conscious" direction. La Negra was dubbed by Billboard as the show's "breakout star", landing a multi-album record deal with BMG hours after the show's premiere. However, the show was not well-received by some Miami locals, including Khia and TS Madison who reviewed it negatively on their web show The Queen's Court, comparing it to a "telemundo" and criticising the casting and production values, with Khia stating "they had everything bad but a bad bitch on there".

Ratings
The series premiere garnered 1.8 million viewers, making it VH1's best launch since Hip Hop Squares the previous year. The show has struggled with ratings overall compared to its predecessors, which has been attributed to its confusing scheduling changes, having had several different time slots in season two, before settling on Monday nights after Love & Hip Hop: New York.

Episodes

Broadcast history
On November 19, 2018, VH1 announced that Love & Hip Hop: Miami would be returning for a second season on January 2, 2019.

On December 9, 2019, VH1 announced that Love & Hip Hop: Miami would be returning for a third season on January 6, 2020.

On August 9, 2021, VH1 announced that Love & Hip Hop: Miami would return for a fourth season on August 23, 2021.

Distribution
Love & Hip Hop: Miami episodes air regularly on VH1 in the United States. Episodes run from around 41–44 minutes and are broadcast in high definition. The series' episodes are also available on demand through the official VH1 website, as well as for digital download at the iTunes Store and Amazon.

The show has never been released on DVD and episodes are currently only available on digital platforms.

Internationally, the first season is available uncensored on Hayu.

Censored seasons 1 and 2 are available to stream on Paramount+.

References

External links

 Love & Hip Hop: Miami at VH1.com

Love & Hip Hop
2010s American reality television series
2010s LGBT-related reality television series
2020s American reality television series
2020s LGBT-related reality television series
2018 American television series debuts
African-American reality television series
American LGBT-related reality television series
Television shows set in Miami
VH1 original programming
Reality television spin-offs
Hip hop television
English-language television shows
Television productions postponed due to the COVID-19 pandemic
American television spin-offs

redpajama_set_name: RedPajamaWikipedia


text:
Badis kyanos — один з п'яти нових видів роду бадіс (Badis), виявлених у Західній Бенгалії (Індія) і описаних у 2015 році.

Вид був виявлений в річці Чел (), басейн річки Тіста (), лише в місці, розташованому за 7 км на захід від Дам-Дім (), округ Джалпайгурі (). Зразки для дослідження були надані Ендрю Рао (), власником місцевої фірми з експорту тропічних риб Malabar Tropicals.

Форма
Максимальна стандартна (без хвостового плавця) довжина риб серед досліджених зразків становила 35,2 мм (самець).

Тіло видовжене і стиснуте з боків, голова і морда округлені, якщо дивитися на риб збоку. Очі розташовані в передній верхній частині голови. Лінія спини трохи вигнута, контур черева у самців трохи увігнутий, а у самок, навпаки, випуклий, верхній і нижній краї хвостового стебла рівні.

Луска на боках ктеноїдна, на верхній частині голови і в грудній області циклоїдна. Уздовж бічної лінії розташовано 25-27 лусок. Спинний плавець має 15-17 твердих променів і 10 м'яких, анальний — 3 твердих і 7-10 м'яких, грудні плавці — 13-14 променів (усі м'які). Хребців: 26.

Забарвлення
Забарвлення самця залежить від настрою і стану риби. Основний колір м'який шиферно-сірий із зеленкуватим відтінком. Біля очей добре помітні три короткі чорні смужки. Боками проходить дванадцять вертикальних чорних смуг з нечіткими контурами. Вони можуть об'єднуватись між собою, а також фрагментуватись на спинну і черевну частини.

Усе тіло самця має слабкий синій лиск. Луски іноді виявляють оранжеві або коричневі цятки. Зяброві кришки темні, з оранжевими або коричневими плямами.

Хвостовий плавець прозорий, спинний має оранжевий відтінок. Передні частини анального і черевних плавців мають м'яке блакитне забарвлення. Грудні плавці з жовтим відтінком. Спинний, анальні і черевні плавці облямовані вузькою яскраво-білою смужкою. На спинному плавці за нею йде ще вузька чорна смужка, а між його променями розташовані невиразні темні плями.

У стресовому стані тіло риб стає темно-сірим, зяброві кришки набувають металевого темно-синього кольору. Смуги на боках майже повністю щезають, а на лусках з'являються темно-оранжеві цятки. Черевні і анальний плавці стають темно-сірими. Таке стресове забарвлення є унікальним в межах роду бадіс.

Самки, так само, як і самці, мають темні смуги на тілі, але оранжевих цяток у них менше. Не мають вони й оранжевих або коричневих плям на зябрових кришках. Плавці прозорі, лише спинний має легкий оранжевий відтінок.

Місце в складі роду Badis
Рід Badis включає більше 20 видів риб. За типом забарвлення вони були поділені на декілька груп, і така класифікація отримала підтвердження на основі молекулярних аналізів.

Три нових види із Західної Бенгалії (Badis autumnum, Badis andrewraoi і Badis kyanos) не мають визначальних ознак жодної з груп, що раніше існували. Натомість вони генетично тісно пов'язані один з одним. Це дає підстави для виділення окремої B. autumnum-групи.

Від своїх найближчих родичів B. kyanos відрізняється характером вертикальних смуг на боках, а також своїм унікальним стресовим забарвленням. Крім того, існують деякі морфологічні відмінності, зокрема кількість лусок по висоті тіла і вздовж бокової лінії є різною тощо.

Етимологія назви виду
Ще до появи наукового опису вид був відомий під торговою назвою Badis sp. «Granite».

Так само, як і ця, його наукова назва, що походить від грецького слова κυανός, вказує на характерний колір основного забарвлення риб. Від того ж грецького слова, до речі, походить назва мінералу кіаніт темно-синього кольору.

Джерела
 Stefano Valdesalici, Stefan van der Voort. Four new species of the Indo-Burmese genus Badis from West Bengal, India (Actinopterygii: Perciformes: Badidae), Zootaxa 3985 (3), 2015, pp. 399—402: Badis kyanos
 Five new species of Badis described from West Bengal, India . Seriously Fish

Примітки

Посилання
 Stefano Valdesalici, Stefan van der Voort. Four new species of the Indo-Burmese genus Badis from West Bengal, India (Actinopterygii: Perciformes: Badidae) . Zootaxa, Vol 3985, No 3 (13 Jul. 2015): 391—408

Бадіс
Риби Індії
Тварини, описані 2015


text:
Q: Store checkbox group values as a variable I am trying to store the values of selected checkboxes on a multi page form so I can include these on the final page of the form (and in the email that is sent to the site owner).
I have worked out how to display the values, but saving them for later has got me stumped. I'm learning as I go so I wouldn't be surprised if this is quite easy...
This is the code I'm using:
<?php foreach ($_POST['fooby'] as $key => $entry) {
                if(is_array($entry)){
                    print $key . ": " . implode(',',$entry) . "
";
                }
                else {
                    print $key . ": " . $entry . "
";
                }
            } ?>

And this is the result I get:
1: Minor Service £129
2: plus MOT £35

That's exactly what I'm after - though I don't need the numbers at the beginning. How do I save that information for later?
With the updated code below, I now get the following result:
Minor Service £129
plus MOT £35

That's perfect, but I'm struggling to work out how to store that information to a session variable. I should point out that the values returned from the form are dynamic and unknown beforehand. There might also be 10 items in the array, not just the two shown above.
What I have so far:
<?php if (isset($_POST['fooby'])){
foreach ($_POST['fooby'] as $entry) {
if(is_array($entry)){
$dokval = implode(',',$entry) . "
";
echo $dokval; //echoes the expected result on the page
$_SESSION['dokvalues'] = $dokval; //only stores the last item
}
else {
print $entry . "
"; //not rewritten this part yet
}
}
}   ?>

A: Simply insert these values in hidden input elements in a form on the following pages to use them again:
<input type="hidden" name="<?php echo $key1; ?>" value="<?php echo $value1; ?>" />
<input type="hidden" name="<?php echo $key2; ?>" value="<?php echo $value2; ?>" />
...

A: The foreach loop iterates through the array and for each iteration $key variable is the current index and $entry is the value of that index. The numbers in the list are just representation of index values. If you don't need them, you can go for this:
<?php foreach ($_POST['fooby'] as $entry) {
        if(is_array($entry)){
            print implode(',',$entry) . "
";
        }
        else {
            print $entry . "
";
        }
    } ?>

The keys will still stay in $_POST['fooby'] array.

A: The variable $entry holds one value of the array each time the foreach loop iterates. So the variable $dokval will also hold only one value, which it echos each time the loop iterates. By the time you look at the session variable it's value is going to be the last value that $dokval held. Make $dokval an array and push the $entry value into it. array_push. Also, make sure you start a session at the beginning of every page you want to use the $_SESSION variable.
Jeff

redpajama_set_name: RedPajamaStackExchange


text:
Virtual recognition planned for Class of 2020
Visit iuk.edu.
KOKOMO, Ind. – Indiana University Kokomo applauds the achievements of the Class of 2020, at a virtual recognition ceremony.
While Commencement had been postponed to fall due to the Covid-19 pandemic, Chancellor Susan Sciame-Giesecke said the campus wanted to honor its 651 graduates near the time the ceremony would have been held.
"We are so proud of these students, and everything they have accomplished during their time at IU Kokomo," she said. "I commend them for their perseverance as they finished their degrees under circumstances none of us could have anticipated. We look forward to staying connected with them and hearing about their successes, whether they go to work immediately, become entrepreneurs, or continue on to graduate school. We hope to honor them with our traditional Commencement ceremony later this year, when it is safe to do so."
The Class of 2020 represents 41 Indiana counties, 12 states, and 16 countries. First generation college students make up 34 percent of the class, and 35 are graduating with 4.0 grade point averages.
Seventy-three students are earning master's degrees, 570 are earning their bachelor's, and 11 are receiving associate degrees. Some students completed more than one degree.
The School of Nursing has the largest number of graduates, with 176 earning the Bachelor of Science in Nursing. The second most frequent degree being awarded in the Bachelor of General Studies, with 61.
Each graduate will receive an email from University Events, with a timeline and a link to the Commencement website, which goes live May 5. There, graduates can search by name and find video remarks from IU President Michael A. McRobbie, Sciame-Giesecke, the deans of each IU Kokomo school, and Mattie Tom, student body president. Please go to iuk.edu for links to commencement pages.
Other plans, in addition to the website, include:
Caps and gowns: Herff Jones will ship graduates' caps and gowns to their home so they can wear it with family and friends, take photos and then bring it to their campus-based live event later in the year, or return it via a provided return shipping label. There will also be a cap decoration contest.
At-home delivery: All of IU's 22,000-plus degree recipients will receive two special gifts and a note of congratulations. They'll also receive a campus-specific bicentennial poster commemorating their place as a graduate in our Bicentennial Year. The Indiana University Alumni Association Kokomo Region is also mailing a special gift to each graduate.
Online commencement program: The traditional commencement program booklet – with the elements of the celebration listed, and graduates' names as normally displayed – will be available for download.
Celebratory music: The commencement website will also be home to various selections of IU-connected groups performing specific tunes.
Virtual displays: Even though they're not on campus, graduates will be able to use virtual filters to "photograph" themselves in front of iconic campus locations.

redpajama_set_name: RedPajamaCommonCrawl


text:
Q: Rails/Devise - sign_out not working in Safari I am using the Devise gem for user authentication in my Rails app.  I am finding that the sign_out helper method is not working in Safari, both desktop and mobile - it works just fine in Chrome, etc.
I am trying to force a signed in user to sign out when they hit a particular controller action:
def new
    if user_signed_in?
        sign_out
    end
end

But this has no effect.  The user_signed_in? and destroy_user_session_path functionality work ok in the views, as in
<% if user_signed_in? %>
    <%= link_to('Logout', destroy_user_session_path, :method => :delete) %>
<% end %>

but calling sign_out from the controller is a no-go.
Has anyone experienced these kinds of cross-browser issues with Devise, and is there a way that I can fix this or otherwise achieve the same functionality?

A: Go in config/application.rb and set serve_static_assets by true
config.serve_static_assets = true

Or
Add to routes.rb
match '/logout' => 'devise/sessions#destroy', :via => [:delete]

Then, your link for sign out should be like this
<%= link_to 'Sign Out', logout_path %>

If it doesn't work, try overriding the Devise::SessionsController like this:
class SessionsController < Devise::SessionsController
 def log_out
  user = current_user
  sign_out user
 end
end


Are you looking to find the best quality roofing contractor? Well, if you are, then you should know that there are plenty of important details that you need to focus on so you can choose the right roofing contractor in your area. It is always necessary for you to come up with a great solution. If you are not sure where you can find the best quality roofing contractor in your area, then you will have to start your research online. You can always take recommendations from your friends and neighbors. However, you need to be sure that you are getting the recommendations from the trusted source. There are various important things that you should always consider when you are hiring a roofing contractor.
We will be sharing few important details that you need to focus on so you can come up with a great decision. Let's have a better look at the details that will help you in the best way possible.
When you are looking to find the best roofing contractor in your area, then you will have to be sure that you are going through every single detail that will help you in the best way possible. There are various important things that you should focus on and make sure that you are not hiring an unlicensed roofing contractor in your area. You should always check the license of the roofing contractor that will help you find the professional roofing contractor in your area. You can also check out roofing contractors downriver Michigan to get a better idea of how you can choose the best quality licensed roofing contractor out there. It is the right way to proceed when you are hiring a roofing contractor out there.
Most of the people end up spending a lot of money in the repair or replacement work. If you are also facing multiple issues and you are on a tight budget, then you should start your research and look for the affordable solution. If you are doing your research, then it will become a lot easier for you to get the professional service from the roofing contractor. It is the right way to proceed so you can choose the perfect solution. These are the things that you will have to keep in mind so you can come up with a great solution.