Route webinar and conference registration through Ladill Events.
Deploy Ladill Meet / deploy (push) Successful in 1m26s
Deploy Ladill Meet / deploy (push) Successful in 1m26s
Add Events linking UI and service client, remove Meet-native registration and invitation flows for these room types, and redirect attendees to Events registration when joining. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -52,15 +52,8 @@ class ConferenceController extends Controller
|
||||
$this->authorizeAbility($request, 'meetings.create');
|
||||
$organization = $this->organization($request);
|
||||
|
||||
$members = Member::owned($this->ownerRef($request))
|
||||
->where('organization_id', $organization->id)
|
||||
->where('user_ref', '!=', $this->ownerRef($request))
|
||||
->orderBy('user_ref')
|
||||
->get();
|
||||
|
||||
return view('meet.conferences.create', [
|
||||
'organization' => $organization,
|
||||
'members' => $members,
|
||||
'timezones' => timezone_identifiers_list(),
|
||||
'defaultSettings' => config('meet.default_settings'),
|
||||
]);
|
||||
@@ -78,9 +71,6 @@ class ConferenceController extends Controller
|
||||
'duration_minutes' => ['nullable', 'integer', 'min:15', 'max:480'],
|
||||
'timezone' => ['nullable', 'timezone'],
|
||||
'passcode' => ['nullable', 'string', 'max:20'],
|
||||
'invite_emails' => ['nullable', 'string'],
|
||||
'presenter_refs' => ['nullable', 'array'],
|
||||
'presenter_refs.*' => ['string', 'max:64'],
|
||||
'waiting_room' => ['sometimes', 'boolean'],
|
||||
'join_before_host' => ['sometimes', 'boolean'],
|
||||
'mute_on_join' => ['sometimes', 'boolean'],
|
||||
@@ -91,12 +81,6 @@ class ConferenceController extends Controller
|
||||
'panel_discussions' => ['sometimes', 'boolean'],
|
||||
]);
|
||||
|
||||
$presenterRefs = collect($validated['presenter_refs'] ?? [])
|
||||
->filter()
|
||||
->unique()
|
||||
->values()
|
||||
->all();
|
||||
|
||||
$settings = [
|
||||
'waiting_room' => $request->boolean('waiting_room', true),
|
||||
'join_before_host' => $request->boolean('join_before_host'),
|
||||
@@ -109,7 +93,7 @@ class ConferenceController extends Controller
|
||||
'stage_layout' => 'plenary',
|
||||
'panels' => [],
|
||||
'stage_mode' => true,
|
||||
'presenter_refs' => $presenterRefs,
|
||||
'presenter_refs' => [],
|
||||
];
|
||||
|
||||
$data = array_merge($validated, [
|
||||
@@ -121,14 +105,6 @@ class ConferenceController extends Controller
|
||||
|
||||
$room = $this->rooms->create($request->user(), $organization, $data);
|
||||
|
||||
if ($emails = trim((string) ($validated['invite_emails'] ?? ''))) {
|
||||
$invites = collect(preg_split('/[\s,;]+/', $emails))
|
||||
->filter()
|
||||
->map(fn ($email) => ['email' => $email, 'role' => 'panelist'])
|
||||
->all();
|
||||
$this->invitations->inviteToRoom($room, $invites, $request->user());
|
||||
}
|
||||
|
||||
if ($room->scheduled_at) {
|
||||
$this->calendar->syncRoomCreated($room, $request->user());
|
||||
}
|
||||
@@ -144,43 +120,7 @@ class ConferenceController extends Controller
|
||||
|
||||
$room->load(['sessions.participants', 'invitations', 'sessions.recordings', 'sessions.aiSummaries', 'sessionFiles']);
|
||||
|
||||
$presenterRefs = $this->conferences->presenterRefs($room);
|
||||
$speakers = Member::owned($this->ownerRef($request))
|
||||
->where('organization_id', $room->organization_id)
|
||||
->whereIn('user_ref', $presenterRefs)
|
||||
->get();
|
||||
|
||||
$speakerUsers = User::query()
|
||||
->whereIn('public_id', $presenterRefs)
|
||||
->get()
|
||||
->keyBy('public_id');
|
||||
|
||||
$members = Member::owned($this->ownerRef($request))
|
||||
->where('organization_id', $room->organization_id)
|
||||
->where('user_ref', '!=', $this->ownerRef($request))
|
||||
->orderBy('user_ref')
|
||||
->get();
|
||||
|
||||
$memberUsers = User::query()
|
||||
->whereIn('public_id', $members->pluck('user_ref'))
|
||||
->get()
|
||||
->keyBy('public_id');
|
||||
|
||||
$canManageSpeakers = app(\App\Services\Meet\MeetPermissions::class)
|
||||
->can($this->member($request), 'meetings.manage');
|
||||
|
||||
$eventsEventUrl = \App\Support\EventsSourceLink::eventAdminUrl($room);
|
||||
|
||||
return view('meet.conferences.show', compact(
|
||||
'room',
|
||||
'speakers',
|
||||
'speakerUsers',
|
||||
'members',
|
||||
'memberUsers',
|
||||
'presenterRefs',
|
||||
'canManageSpeakers',
|
||||
'eventsEventUrl',
|
||||
));
|
||||
return view('meet.conferences.show', compact('room'));
|
||||
}
|
||||
|
||||
public function updateSpeakers(Request $request, Room $room): RedirectResponse
|
||||
@@ -189,6 +129,17 @@ class ConferenceController extends Controller
|
||||
$this->authorizeOwner($request, $room);
|
||||
abort_unless($room->isConference(), 404);
|
||||
|
||||
if ($room->isEventsLinked()) {
|
||||
if ($request->has('panel_discussions')) {
|
||||
$settings = array_merge($room->settings ?? [], [
|
||||
'panel_discussions' => $request->boolean('panel_discussions'),
|
||||
]);
|
||||
$room->update(['settings' => $settings]);
|
||||
}
|
||||
|
||||
return redirect()->route('meet.conferences.show', $room)->with('success', 'Conference settings updated.');
|
||||
}
|
||||
|
||||
$validated = $request->validate([
|
||||
'presenter_refs' => ['nullable', 'array'],
|
||||
'presenter_refs.*' => ['string', 'max:64'],
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Meet;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Controllers\Meet\Concerns\ScopesToAccount;
|
||||
use App\Models\Room;
|
||||
use App\Services\Integrations\EventsClient;
|
||||
use App\Services\Integrations\EventsLinkService;
|
||||
use App\Support\EventsSourceLink;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
|
||||
class EventsLinkController extends Controller
|
||||
{
|
||||
use ScopesToAccount;
|
||||
|
||||
public function __construct(
|
||||
protected EventsLinkService $links,
|
||||
protected EventsClient $eventsClient,
|
||||
) {}
|
||||
|
||||
public function show(Request $request, Room $room): View|RedirectResponse
|
||||
{
|
||||
$this->authorizeAbility($request, 'meetings.manage');
|
||||
$this->authorizeOwner($request, $room);
|
||||
abort_unless($room->isWebinar() || $room->isConference(), 404);
|
||||
|
||||
if (! $this->eventsClient->isConfigured()) {
|
||||
return redirect()
|
||||
->route($room->isWebinar() ? 'meet.webinars.show' : 'meet.conferences.show', $room)
|
||||
->withErrors(['events' => 'Ladill Events integration is not configured on this Meet instance.']);
|
||||
}
|
||||
|
||||
$events = $this->links->linkableEvents($request->user());
|
||||
$expectedType = $room->isConference() ? 'town_hall' : 'webinar';
|
||||
|
||||
return view('meet.events.link', [
|
||||
'room' => $room,
|
||||
'events' => $events,
|
||||
'expectedType' => $expectedType,
|
||||
'eventsAppUrl' => EventsSourceLink::baseUrl(),
|
||||
'isLinked' => EventsSourceLink::isLinked($room),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request, Room $room): RedirectResponse
|
||||
{
|
||||
$this->authorizeAbility($request, 'meetings.manage');
|
||||
$this->authorizeOwner($request, $room);
|
||||
abort_unless($room->isWebinar() || $room->isConference(), 404);
|
||||
|
||||
$validated = $request->validate([
|
||||
'event_id' => ['required', 'integer', 'min:1'],
|
||||
'virtual_session_id' => ['nullable', 'string', 'max:64'],
|
||||
]);
|
||||
|
||||
try {
|
||||
$this->links->link(
|
||||
$room,
|
||||
$request->user(),
|
||||
(int) $validated['event_id'],
|
||||
$validated['virtual_session_id'] ?? null,
|
||||
);
|
||||
} catch (\RuntimeException $e) {
|
||||
return back()->withErrors(['events' => $e->getMessage()])->withInput();
|
||||
}
|
||||
|
||||
$route = $room->isWebinar() ? 'meet.webinars.show' : 'meet.conferences.show';
|
||||
|
||||
return redirect()->route($route, $room)->with('success', 'Linked to Ladill Events. Registration and invitations are now managed in Events.');
|
||||
}
|
||||
|
||||
public function destroy(Request $request, Room $room): RedirectResponse
|
||||
{
|
||||
$this->authorizeAbility($request, 'meetings.manage');
|
||||
$this->authorizeOwner($request, $room);
|
||||
|
||||
$this->links->unlink($room);
|
||||
|
||||
$route = $room->isWebinar() ? 'meet.webinars.show' : 'meet.conferences.show';
|
||||
|
||||
return redirect()->route($route, $room)->with('success', 'Events link removed.');
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ use App\Models\Invitation;
|
||||
use App\Models\Room;
|
||||
use App\Services\Meet\ContactService;
|
||||
use App\Services\Meet\InvitationService;
|
||||
use App\Support\EventsSourceLink;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -24,11 +25,25 @@ class InvitationController extends Controller
|
||||
protected ContactService $contacts,
|
||||
) {}
|
||||
|
||||
public function index(Request $request, Room $room): View
|
||||
public function index(Request $request, Room $room): View|RedirectResponse
|
||||
{
|
||||
$this->authorizeAbility($request, 'meetings.manage');
|
||||
$this->authorizeOwner($request, $room);
|
||||
|
||||
if ($room->isWebinar() || $room->isConference()) {
|
||||
$adminUrl = EventsSourceLink::eventCommsUrl($room) ?? EventsSourceLink::eventAdminUrl($room);
|
||||
|
||||
if ($adminUrl) {
|
||||
return redirect()->away($adminUrl);
|
||||
}
|
||||
|
||||
$showRoute = $room->isWebinar() ? 'meet.webinars.show' : 'meet.conferences.show';
|
||||
|
||||
return redirect()
|
||||
->route($showRoute, $room)
|
||||
->withErrors(['invitations' => 'Link this session to Ladill Events to send bulk invitations.']);
|
||||
}
|
||||
|
||||
$room->load(['invitations' => fn ($q) => $q->orderByDesc('created_at')]);
|
||||
$groups = ContactGroup::owned($this->ownerRef($request))
|
||||
->where('organization_id', $room->organization_id)
|
||||
|
||||
@@ -11,7 +11,7 @@ use App\Services\Meet\InvitationService;
|
||||
use App\Services\Meet\SecurityPolicyService;
|
||||
use App\Services\Meet\SessionService;
|
||||
use App\Services\Meet\SpaceService;
|
||||
use App\Services\Meet\WebinarService;
|
||||
use App\Support\EventsSourceLink;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
@@ -23,7 +23,6 @@ class JoinController extends Controller
|
||||
public function __construct(
|
||||
protected SessionService $sessions,
|
||||
protected SecurityPolicyService $security,
|
||||
protected WebinarService $webinars,
|
||||
protected SpaceService $spaces,
|
||||
protected ConferenceService $conferences,
|
||||
) {}
|
||||
@@ -165,19 +164,35 @@ class JoinController extends Controller
|
||||
$role = $this->spaces->resolveJoinRole($room, $user, $role);
|
||||
} elseif ($room->isConference() && $role !== 'host') {
|
||||
$role = $this->conferences->resolveJoinRole($room, $user, $email, $role);
|
||||
} elseif ($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.']);
|
||||
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);
|
||||
|
||||
$role = 'attendee';
|
||||
$displayName = $reg->display_name;
|
||||
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)) {
|
||||
|
||||
@@ -65,13 +65,11 @@ class WebinarController extends Controller
|
||||
'duration_minutes' => ['nullable', 'integer', 'min:15', 'max:480'],
|
||||
'timezone' => ['nullable', 'timezone'],
|
||||
'passcode' => ['nullable', 'string', 'max:20'],
|
||||
'invite_emails' => ['nullable', 'string'],
|
||||
'waiting_room' => ['sometimes', 'boolean'],
|
||||
'join_before_host' => ['sometimes', 'boolean'],
|
||||
'mute_on_join' => ['sometimes', 'boolean'],
|
||||
'auto_record' => ['sometimes', 'boolean'],
|
||||
'live_captions' => ['sometimes', 'boolean'],
|
||||
'webinar_auto_approve' => ['sometimes', 'boolean'],
|
||||
'panel_discussions' => ['sometimes', 'boolean'],
|
||||
]);
|
||||
|
||||
@@ -81,7 +79,6 @@ class WebinarController extends Controller
|
||||
'mute_on_join' => $request->boolean('mute_on_join', true),
|
||||
'auto_record' => $request->boolean('auto_record'),
|
||||
'live_captions' => $request->boolean('live_captions'),
|
||||
'webinar_auto_approve' => $request->boolean('webinar_auto_approve'),
|
||||
'panel_discussions' => $request->boolean('panel_discussions'),
|
||||
'stage_layout' => 'plenary',
|
||||
'panels' => [],
|
||||
@@ -97,14 +94,6 @@ class WebinarController extends Controller
|
||||
|
||||
$room = $this->rooms->create($request->user(), $organization, $data);
|
||||
|
||||
if ($emails = trim((string) ($validated['invite_emails'] ?? ''))) {
|
||||
$invites = collect(preg_split('/[\s,;]+/', $emails))
|
||||
->filter()
|
||||
->map(fn ($email) => ['email' => $email])
|
||||
->all();
|
||||
$this->invitations->inviteToRoom($room, $invites, $request->user());
|
||||
}
|
||||
|
||||
if ($room->scheduled_at) {
|
||||
$this->calendar->syncRoomCreated($room, $request->user());
|
||||
}
|
||||
@@ -118,11 +107,9 @@ class WebinarController extends Controller
|
||||
$this->authorizeOwner($request, $room);
|
||||
abort_unless($room->isWebinar(), 404);
|
||||
|
||||
$room->load(['sessions.participants', 'invitations', 'sessions.recordings', 'sessions.aiSummaries', 'sessionFiles', 'webinarRegistrations']);
|
||||
$room->load(['sessions.participants', 'invitations', 'sessions.recordings', 'sessions.aiSummaries', 'sessionFiles']);
|
||||
|
||||
$eventsEventUrl = \App\Support\EventsSourceLink::eventAdminUrl($room);
|
||||
|
||||
return view('meet.webinars.show', compact('room', 'eventsEventUrl'));
|
||||
return view('meet.webinars.show', compact('room'));
|
||||
}
|
||||
|
||||
public function start(Request $request, Room $room): RedirectResponse
|
||||
|
||||
@@ -3,23 +3,12 @@
|
||||
namespace App\Http\Controllers\Meet;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\BreakoutRoom;
|
||||
use App\Models\MeetPoll;
|
||||
use App\Models\QaQuestion;
|
||||
use App\Models\Room;
|
||||
use App\Models\Session;
|
||||
use App\Models\SessionFile;
|
||||
use App\Models\Whiteboard;
|
||||
use App\Services\Meet\BreakoutService;
|
||||
use App\Services\Meet\FileSharingService;
|
||||
use App\Services\Meet\PollService;
|
||||
use App\Services\Meet\QaService;
|
||||
use App\Services\Meet\WebinarService;
|
||||
use App\Services\Meet\WhiteboardService;
|
||||
use App\Support\EventsSourceLink;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\View\View;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
class WebinarRegistrationController extends Controller
|
||||
{
|
||||
@@ -27,62 +16,69 @@ class WebinarRegistrationController extends Controller
|
||||
protected WebinarService $webinars,
|
||||
) {}
|
||||
|
||||
public function show(Room $room): View
|
||||
public function show(Room $room): View|RedirectResponse
|
||||
{
|
||||
abort_unless($room->isWebinar(), 404);
|
||||
|
||||
return view('meet.webinar.register', compact('room'));
|
||||
return $this->redirectToEvents($room);
|
||||
}
|
||||
|
||||
public function store(Request $request, Room $room): View
|
||||
public function store(Request $request, Room $room): View|RedirectResponse
|
||||
{
|
||||
abort_unless($room->isWebinar(), 404);
|
||||
|
||||
$validated = $request->validate([
|
||||
'email' => ['required', 'email'],
|
||||
'display_name' => ['required', 'string', 'max:100'],
|
||||
]);
|
||||
|
||||
$registration = $this->webinars->register(
|
||||
$room,
|
||||
$validated['email'],
|
||||
$validated['display_name'],
|
||||
);
|
||||
|
||||
return view('meet.webinar.register-done', compact('room', 'registration'));
|
||||
return $this->redirectToEvents($room);
|
||||
}
|
||||
|
||||
public function manage(Request $request, Room $room): View
|
||||
public function manage(Request $request, Room $room): View|RedirectResponse
|
||||
{
|
||||
$this->authorizeWebinarHost($request, $room);
|
||||
|
||||
$registrations = $room->webinarRegistrations()->orderByDesc('created_at')->get();
|
||||
|
||||
return view('meet.webinar.registrations', compact('room', 'registrations'));
|
||||
return $this->redirectToEventsAdmin($room);
|
||||
}
|
||||
|
||||
public function approve(Request $request, Room $room, \App\Models\WebinarRegistration $registration): RedirectResponse
|
||||
{
|
||||
$this->authorizeWebinarHost($request, $room);
|
||||
abort_unless($registration->room_id === $room->id, 404);
|
||||
|
||||
$this->webinars->approve($registration, $request->user());
|
||||
|
||||
return back()->with('success', 'Registration approved.');
|
||||
return $this->redirectToEventsAdmin($room);
|
||||
}
|
||||
|
||||
public function reject(Request $request, Room $room, \App\Models\WebinarRegistration $registration): RedirectResponse
|
||||
{
|
||||
$this->authorizeWebinarHost($request, $room);
|
||||
abort_unless($registration->room_id === $room->id, 404);
|
||||
|
||||
$this->webinars->reject($registration);
|
||||
|
||||
return back()->with('success', 'Registration rejected.');
|
||||
return $this->redirectToEventsAdmin($room);
|
||||
}
|
||||
|
||||
protected function authorizeWebinarHost(Request $request, Room $room): void
|
||||
{
|
||||
abort_unless($request->user()?->ownerRef() === $room->host_user_ref, 403);
|
||||
}
|
||||
|
||||
protected function redirectToEvents(Room $room): RedirectResponse
|
||||
{
|
||||
$url = EventsSourceLink::eventRegistrationUrl($room);
|
||||
|
||||
if ($url) {
|
||||
return redirect()->away($url);
|
||||
}
|
||||
|
||||
return redirect()
|
||||
->route('meet.webinars.show', $room)
|
||||
->withErrors(['register' => 'Link this webinar to Ladill Events to enable registration.']);
|
||||
}
|
||||
|
||||
protected function redirectToEventsAdmin(Room $room): RedirectResponse
|
||||
{
|
||||
$url = EventsSourceLink::eventAdminUrl($room);
|
||||
|
||||
if ($url) {
|
||||
return redirect()->away($url);
|
||||
}
|
||||
|
||||
return redirect()
|
||||
->route('meet.webinars.show', $room)
|
||||
->withErrors(['register' => 'Link this webinar to Ladill Events to manage registrations.']);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user