. /** * Meeting lifecycle status (schedule + live presence + wait-for-host). * * @package mod_meetletacbfree * @copyright 2026 MeetLet * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace mod_meetletacbfree\local; use mod_meetletacbfree\api\service; defined('MOODLE_INTERNAL') || die(); /** * Resolve whether the activity meeting can accept join requests. */ class meetingstatus { public const OPEN = 'open'; public const LIVE = 'live'; public const SCHEDULED = 'scheduled'; public const ENDED = 'ended'; public const WAITINGHOST = 'waitinghost'; /** Local-only placeholder until Adobe is polled from the browser. */ public const CHECKING = 'checking'; /** * Fast local status for first paint — no Adobe API calls. * * Join stays blocked until the client polls {@see resolve()} unless the * schedule alone already forbids join (and the user cannot bypass). * * @return array{ * status:string, * statuslabel:string, * joinable:bool, * message:string, * errorcode:string, * starttime:int, * endtime:int, * waitforhost:bool * } */ public static function resolve_local(instance $inst, ?int $userid = null): array { global $USER; $userid = $userid ?? (int)$USER->id; $rec = $inst->record; $now = time(); $startts = !empty($rec->starttime) ? (int)$rec->starttime : 0; $endts = !empty($rec->endtime) ? (int)$rec->endtime : 0; $waitforhost = config::wait_for_host($rec); $canbypass = featureaccess::allowed( featureaccess::ENTERBEFOREHOST, $inst->context, $rec, $userid ); $status = self::CHECKING; $errorcode = ''; $message = get_string('statuschecking_help', 'meetletacbfree'); // Never allow join before Adobe confirms room state. $joinable = false; if ($endts > 0 && $endts < $now) { $status = self::ENDED; $message = get_string('meetingended', 'meetletacbfree'); $errorcode = 'meetingended'; $joinable = $canbypass; } else if ($startts > $now) { $status = self::SCHEDULED; $message = get_string('meetingnotstarted', 'meetletacbfree'); $errorcode = 'meetingnotstarted'; $joinable = $canbypass; } else if ($waitforhost && !$canbypass) { // Provisional: assume waiting until Adobe poll confirms a live/open room. $status = self::WAITINGHOST; $message = get_string('joinblocked_waitinghost', 'meetletacbfree'); $errorcode = 'waitingforhost'; $joinable = false; } else if (!$canbypass) { // Until Adobe confirms the room is live, keep Join disabled for participants. $status = self::CHECKING; $message = get_string('statuschecking_help', 'meetletacbfree'); $joinable = false; } return [ 'status' => $status, 'statuslabel' => self::label($status), 'joinable' => $joinable, 'message' => $message, 'errorcode' => $errorcode, 'starttime' => $startts, 'endtime' => $endts, 'waitforhost' => $waitforhost, ]; } /** * Full status including Adobe live presence and host-hold. * * Priority: * 1) Live room presence on Adobe Connect * 2) Moodle schedule (start / end) * 3) Wait-for-host (Moodle flag and/or Adobe ACL) * * @return array{ * status:string, * statuslabel:string, * joinable:bool, * message:string, * errorcode:string, * starttime:int, * endtime:int, * waitforhost:bool * } */ public static function resolve(instance $inst, ?int $userid = null, bool $checkhost = true): array { global $USER; $userid = $userid ?? (int)$USER->id; $rec = $inst->record; $now = time(); $startts = !empty($rec->starttime) ? (int)$rec->starttime : 0; $endts = !empty($rec->endtime) ? (int)$rec->endtime : 0; $service = null; $scoid = 0; $islive = false; $adobehold = false; if (config::is_configured()) { $scoid = $inst->meetingscoid(); if ($scoid) { try { $service = new service(); $service->login_admin(); $info = $service->meeting_info($scoid); if ($info && !empty($info->starttime) && $startts <= 0) { $adobestart = strtotime((string)$info->starttime); if ($adobestart) { $startts = $adobestart; } } $islive = $service->meeting_is_live($scoid); if ($checkhost) { $adobehold = $service->meeting_requires_host_first($scoid); } } catch (\Throwable $e) { $service = null; $islive = false; $adobehold = false; } } } $waitforhost = config::wait_for_host($rec) || $adobehold; $status = self::OPEN; $errorcode = ''; $message = ''; $joinable = true; $canbypass = featureaccess::allowed( featureaccess::ENTERBEFOREHOST, $inst->context, $rec, $userid ); if ($islive) { $status = self::LIVE; $joinable = true; } else if ($endts > 0 && $endts < $now) { $status = self::ENDED; if (!$canbypass) { $joinable = false; $errorcode = 'meetingended'; $message = get_string('meetingended', 'meetletacbfree'); } } else if ($startts > $now) { $status = self::SCHEDULED; if (!$canbypass) { $joinable = false; $errorcode = 'meetingnotstarted'; $message = get_string('meetingnotstarted', 'meetletacbfree'); } } else if (!$islive && $checkhost && !$canbypass) { // Nobody is in the Adobe room yet. Participants must not get a join URL — // Connect would only show "Meeting Host Expected". Moderators with // enterbeforehost may still join to start the session. $status = self::WAITINGHOST; $joinable = false; $errorcode = 'waitingforhost'; $message = get_string('joinblocked_waitinghost', 'meetletacbfree'); } else if (!$islive && $canbypass && ($waitforhost || $adobehold)) { // Host/moderator may enter; still show waiting-for-host for clarity. $status = self::WAITINGHOST; $joinable = true; $message = get_string('joinblocked_waitinghost', 'meetletacbfree'); } if ($service instanceof service) { try { $service->logout(); } catch (\Throwable $e) { // Ignore. } } if ($inst->meetingmap) { try { if (roomsession::note_presence($inst, $islive)) { recordings::sync($inst); } } catch (\Throwable $e) { debugging('MeetLet room session observe failed: ' . $e->getMessage(), DEBUG_DEVELOPER); } } return [ 'status' => $status, 'statuslabel' => self::label($status), 'joinable' => $joinable, 'message' => $message !== '' ? $message : get_string('statushelp', 'meetletacbfree'), 'errorcode' => $errorcode, 'starttime' => $startts, 'endtime' => $endts, 'waitforhost' => $waitforhost, ]; } public static function label(string $status): string { switch ($status) { case self::LIVE: return get_string('statuslive', 'meetletacbfree'); case self::ENDED: return get_string('statusended', 'meetletacbfree'); case self::SCHEDULED: return get_string('statusscheduled', 'meetletacbfree'); case self::WAITINGHOST: return get_string('statuswaitinghost', 'meetletacbfree'); case self::CHECKING: return get_string('statuschecking', 'meetletacbfree'); default: return get_string('statusopen', 'meetletacbfree'); } } }