loadMissing('organization'); if ($room->status === 'cancelled') { return view('meet.join.cancelled', [ 'room' => $room, 'organization' => $room->organization, ]); } if ($room->isWebinar() && $request->query('token')) { $request->session()->put("meet.webinar.{$room->uuid}", $request->query('token')); } [$allowed, $reason] = $this->security->canJoin($room, $request->user(), $request->user()?->email); if (! $allowed && $reason !== 'passcode_required') { return view('meet.join.denied', [ 'room' => $room, 'organization' => $room->organization, 'reason' => $reason, ]); } if ($room->passcode && ! $request->session()->get("meet.passcode.{$room->uuid}")) { return view('meet.join.passcode', [ 'room' => $room, 'organization' => $room->organization, ]); } $session = $room->activeSession(); if ($session) { $participantUuid = $request->session()->get("meet.participant.{$session->uuid}"); $participant = $participantUuid ? Participant::where('uuid', $participantUuid)->where('session_id', $session->id)->first() : null; if ($participant?->status === 'waiting') { return redirect()->route('meet.join.waiting', $room); } if ($participant?->status === 'joined') { return redirect()->route('meet.room', $session); } } return view('meet.join.show', [ 'room' => $room, 'organization' => $room->organization, 'user' => $request->user(), 'isWebinar' => $room->isWebinar(), ]); } public function verifyPasscode(Request $request, Room $room): RedirectResponse { $request->validate(['passcode' => ['required', 'string']]); if ($room->passcode !== $request->input('passcode')) { return back()->withErrors(['passcode' => 'Incorrect passcode.']); } $request->session()->put("meet.passcode.{$room->uuid}", true); return redirect()->route('meet.join', $room); } public function enter(Request $request, Room $room): RedirectResponse { if ($room->passcode && ! $request->session()->get("meet.passcode.{$room->uuid}")) { return redirect()->route('meet.join', $room); } $rules = [ 'email' => ['nullable', 'email'], ]; if ($request->user()) { $rules['display_name'] = ['nullable', 'string', 'max:100']; } else { $rules['display_name'] = ['required', 'string', 'max:100']; } try { $validated = $request->validate($rules); } catch (ValidationException $e) { throw $e->redirectTo(route('meet.join', $room)); } $user = $request->user(); $displayName = $user?->name ?? ($validated['display_name'] ?? 'Guest'); $email = $user?->email ?? ($validated['email'] ?? null); $kind = $room->isConference() ? 'conference' : 'meeting'; [$allowed, $reason] = $this->security->canJoin($room, $user, $email ?? $displayName.'@guest.local'); if (! $allowed && $reason !== 'passcode_required') { return back()->withErrors(['join' => $reason ?? "Unable to join {$kind}."]); } try { if ($room->isLive() && $room->activeSession()) { $session = $room->activeSession(); } elseif ($room->isTownHall() && $user && $user->ownerRef() === $room->host_user_ref && $room->setting('green_room')) { $session = app(\App\Services\Meet\TownHallService::class)->startGreenRoom($room, $user); } elseif ($user && $user->ownerRef() === $room->host_user_ref) { $session = $this->sessions->start($room, $user); } elseif ($room->setting('join_before_host') || ($user && $user->ownerRef() === $room->host_user_ref)) { if (! $room->activeSession() && $user && $user->ownerRef() === $room->host_user_ref) { $session = $this->sessions->start($room, $user); } elseif (! $room->activeSession()) { return back()->withErrors(['join' => ucfirst($kind).' has not started yet. Please wait for the host.']); } else { $session = $room->activeSession(); } } else { if (! $room->activeSession()) { return back()->withErrors(['join' => ucfirst($kind).' has not started yet. Please wait for the host.']); } $session = $room->activeSession(); } } catch (\Symfony\Component\HttpKernel\Exception\HttpException $e) { if (in_array($e->getStatusCode(), [402, 503], true)) { return back()->withErrors(['join' => $e->getMessage() ?: "Unable to start {$kind}."]); } throw $e; } $role = ($user && $user->ownerRef() === $room->host_user_ref) ? 'host' : 'guest'; if ($room->isSpace()) { $role = $this->spaces->resolveJoinRole($room, $user, $role); } elseif ($room->isConference() && $role !== 'host') { $role = $this->conferences->resolveJoinRole($room, $user, $email, $role); if ($role === 'attendee' && $room->isEventsLinked()) { $joinEmail = $email ?? $user?->email; if (! $room->hasEventsRegistrationInvite($joinEmail)) { $registrationUrl = EventsSourceLink::eventRegistrationUrl($room); return $registrationUrl ? redirect()->away($registrationUrl) : back()->withErrors(['join' => 'Register for this conference in Ladill Events before joining.']); } } } elseif ($room->isWebinar() && $role !== 'host') { if ($room->isEventsLinked()) { $joinEmail = $email ?? $user?->email; if (! $room->hasEventsRegistrationInvite($joinEmail)) { $registrationUrl = EventsSourceLink::eventRegistrationUrl($room); return $registrationUrl ? redirect()->away($registrationUrl) : back()->withErrors(['join' => 'Register for this webinar in Ladill Events before joining.']); } $role = 'attendee'; $displayName = $displayName ?? $user?->name; } else { return back()->withErrors([ 'join' => 'This webinar must be linked to a Ladill Events event before attendees can register and join.', ]); } } if ($room->isConference() && ! $this->conferences->canJoinSession($room, $session, $role)) { return back()->withErrors([ 'join' => 'The conference has not gone live yet. Assigned speakers can join from the green room link when the host starts.', ]); } $joinStatus = $room->requiresWaitingRoom($user, $role) ? 'waiting' : 'joined'; $participant = $this->sessions->joinParticipant($session, $user, $role, $displayName, $email, $joinStatus); if ($room->isConference() && $session->session_mode === 'green_room') { $this->conferences->registerPresenter($session, $user, $role); } app(InvitationService::class)->markAcceptedOnJoin($room, $user, $email); $this->sessions->rememberParticipantSession($request, $participant, $session); if ($participant->status === 'waiting') { return redirect()->route('meet.join.waiting', $room); } return redirect()->route('meet.room', $session); } public function waiting(Request $request, Room $room): View|RedirectResponse { $room->loadMissing('organization'); $session = $room->activeSession() ?? $this->participantSessionForRoom($request, $room); if (! $session || ! $session->isLive()) { if ($session) { return redirect()->route('meet.ended', $session); } return redirect()->route('meet.join', $room); } $participant = $this->participantFromSession($request, $session); if (! $participant || $participant->status === 'removed') { return redirect()->route('meet.join', $room); } if ($participant->status === 'joined') { return redirect()->route('meet.room', $session); } return view('meet.join.waiting', [ 'room' => $room, 'session' => $session, 'organization' => $room->organization, 'participant' => $participant, ]); } public function waitingStatus(Request $request, Room $room): JsonResponse { $session = $room->activeSession() ?? $this->participantSessionForRoom($request, $room); if (! $session || ! $session->isLive()) { if ($session) { return response()->json([ 'ended' => true, 'redirect' => route('meet.ended', $session), ]); } abort(404); } $participant = $this->participantFromSession($request, $session); return response()->json([ 'status' => $participant?->status ?? 'unknown', 'redirect' => ($participant && $participant->status === 'joined') ? route('meet.room', $session) : null, 'denied' => $participant?->status === 'removed', ]); } protected function participantFromSession(Request $request, Session $session): ?Participant { $participantUuid = $request->session()->get("meet.participant.{$session->uuid}"); if (! $participantUuid) { return null; } return Participant::query() ->where('uuid', $participantUuid) ->where('session_id', $session->id) ->first(); } protected function participantSessionForRoom(Request $request, Room $room): ?Session { foreach ($room->sessions()->orderByDesc('started_at')->get() as $session) { if ($request->session()->has("meet.participant.{$session->uuid}")) { return $session; } } return null; } }