Files
ladill-meet/app/Http/Controllers/Meet/JoinController.php
T
isaaccladandCursor 7ae26a467c
Deploy Ladill Meet / deploy (push) Successful in 47s
Add kiosk join/leave pages, waiting room, and meeting feedback.
Redesign public join flows on the Frontdesk kiosk template, enforce host
admission when waiting room is enabled, and collect star ratings after leave.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-01 13:33:48 +00:00

234 lines
8.4 KiB
PHP

<?php
namespace App\Http\Controllers\Meet;
use App\Http\Controllers\Controller;
use App\Models\Participant;
use App\Models\Room;
use App\Models\Session;
use App\Services\Meet\InvitationService;
use App\Services\Meet\SecurityPolicyService;
use App\Services\Meet\SessionService;
use App\Services\Meet\WebinarService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
use Illuminate\View\View;
class JoinController extends Controller
{
public function __construct(
protected SessionService $sessions,
protected SecurityPolicyService $security,
protected WebinarService $webinars,
) {}
public function show(Request $request, Room $room): View|RedirectResponse
{
$room->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);
[$allowed, $reason] = $this->security->canJoin($room, $user, $email ?? $displayName.'@guest.local');
if (! $allowed && $reason !== 'passcode_required') {
return back()->withErrors(['join' => $reason ?? 'Unable to join meeting.']);
}
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' => 'Meeting has not started yet. Please wait for the host.']);
} else {
$session = $room->activeSession();
}
} else {
if (! $room->activeSession()) {
return back()->withErrors(['join' => 'Meeting has not started yet. Please wait for the host.']);
}
$session = $room->activeSession();
}
$role = ($user && $user->ownerRef() === $room->host_user_ref) ? 'host' : 'guest';
if ($room->isWebinar() && $role !== 'host') {
$token = $request->session()->get("meet.webinar.{$room->uuid}");
$reg = $token
? $this->webinars->findByAccessToken($token)
: ($email ? $this->webinars->findApprovedRegistration($room, $email) : null);
if (! $reg) {
return redirect()->route('meet.webinar.register', $room)
->withErrors(['register' => 'Approved registration is required for this webinar.']);
}
$role = 'attendee';
$displayName = $reg->display_name;
}
$joinStatus = $room->requiresWaitingRoom($user, $role) ? 'waiting' : 'joined';
$participant = $this->sessions->joinParticipant($session, $user, $role, $displayName, $email, $joinStatus);
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();
abort_unless($session?->isLive(), 404);
$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();
abort_unless($session, 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();
}
}