. /** * Stepwise Adobe Connect connection tests for admin Ajax UI. * * @package mod_meetletacbmarketplace * @copyright 2026 MeetLet * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace mod_meetletacbmarketplace\local; defined('MOODLE_INTERNAL') || die(); use mod_meetletacbmarketplace\api\client; use mod_meetletacbmarketplace\api\service; use mod_meetletacbmarketplace\local\usererror; use Throwable; /** * Runs connection checks one step at a time (session-backed). */ class conntest_runner { public const STEPS = ['login', 'folders', 'user', 'sso', 'cleanup']; /** * Reset session scratch data for a new run. */ public static function reset(): void { global $SESSION; $SESSION->meetletacbmarketplace_conntest = [ 'service' => null, 'meetings' => null, 'scoid' => null, 'principalid' => null, 'login' => null, 'failed' => false, ]; } /** * @return array{label:string,ok:bool,detail:string,done:bool,step:string} */ public static function run_step(string $step): array { global $SESSION; \mod_meetletacbmarketplace\registration\manager::require_registered(); if (empty($SESSION->meetletacbmarketplace_conntest) || $step === 'login') { self::reset(); } $state = &$SESSION->meetletacbmarketplace_conntest; if (!config::is_configured()) { return [ 'step' => $step, 'label' => get_string('error', 'meetletacbmarketplace'), 'ok' => false, 'detail' => get_string('notconfigured', 'meetletacbmarketplace'), 'done' => true, 'abort' => true, ]; } try { switch ($step) { case 'login': return self::step_login($state); case 'folders': return self::step_folders($state); case 'user': return self::step_user($state); case 'sso': return self::step_sso($state); case 'cleanup': return self::step_cleanup($state); default: return [ 'step' => $step, 'label' => get_string('error', 'meetletacbmarketplace'), 'ok' => false, 'detail' => 'Unknown step', 'done' => true, 'abort' => true, ]; } } catch (Throwable $e) { $state['failed'] = true; return [ 'step' => $step, 'label' => get_string('error', 'meetletacbmarketplace'), 'ok' => false, 'detail' => $e->getMessage(), 'done' => false, 'abort' => true, ]; } } /** * Rebuild a fresh service (API client is not serializable in session). */ protected static function service(): service { $service = new service(); $service->login_admin(); return $service; } protected static function step_login(array &$state): array { $service = self::service(); $state['ok_login'] = true; // Logout immediately; later steps re-login. $service->logout(); return [ 'step' => 'login', 'label' => get_string('testlogin', 'meetletacbmarketplace'), 'ok' => true, 'detail' => '', 'done' => false, 'abort' => false, ]; } protected static function step_folders(array &$state): array { $service = self::service(); $content = $service->get_folder_scoid('content'); $archives = $service->get_folder_scoid('forced-archives'); $meetings = $service->get_folder_scoid('meetings'); $state['meetings'] = $meetings; $ok = ($content && $archives && $meetings); if (!$ok) { $state['failed'] = true; } $service->logout(); return [ 'step' => 'folders', 'label' => get_string('testfolders', 'meetletacbmarketplace'), 'ok' => (bool)$ok, 'detail' => "content={$content} · archives={$archives} · meetings={$meetings}", 'done' => false, 'abort' => !$ok, ]; } protected static function step_user(array &$state): array { $service = self::service(); $meetings = (int)($state['meetings'] ?? 0); if (!$meetings) { $meetings = (int)$service->get_folder_scoid('meetings'); $state['meetings'] = $meetings; } $meeting = (object)[ 'name' => 'meetlet_test_' . time(), 'starttime' => time(), 'endtime' => time() + HOURSECS, 'meeturl' => '', 'templatescoid' => 0, ]; $scoid = $service->create_meeting($meeting, $meetings); $user = (object)[ 'id' => random_int(900000000, 999999999), 'firstname' => 'MeetLet', 'lastname' => 'Test', 'username' => 'meetlettest' . random_int(1000, 9999), 'email' => 'meetlet.test.' . random_int(1000, 9999) . '@example.com', ]; $principal = $service->ensure_user($user); $assigned = $service->assign_permission($principal->principalid, $scoid, service::ROLE_PRESENTER); $state['scoid'] = $scoid; $state['principalid'] = $principal->principalid; $state['login'] = $principal->login; $service->logout(); if (!$assigned) { $state['failed'] = true; } return [ 'step' => 'user', 'label' => get_string('testuser', 'meetletacbmarketplace'), 'ok' => (bool)$assigned, 'detail' => '', 'done' => false, 'abort' => false, ]; } protected static function step_sso(array &$state): array { $directok = false; $directdetail = ''; try { $sso = new client(); $cookie = $sso->login_external(config::get_admin_login()); $directok = ($cookie !== ''); $directdetail = get_string('testsso_direct_ok', 'meetletacbmarketplace'); } catch (Throwable $e) { $msg = $e->getMessage(); $directdetail = get_string('testsso_direct_fail', 'meetletacbmarketplace', usererror::label('sso_01') . ': ' . $msg); } if ($directok && !empty($state['login'])) { try { $sso2 = new client(); $sso2->login_external($state['login']); $directdetail .= ' · ' . get_string('testsso_userok', 'meetletacbmarketplace'); } catch (Throwable $e) { $directdetail .= ' · ' . get_string('testsso_userwarn', 'meetletacbmarketplace'); } } if (!$directok) { $state['failed'] = true; } return [ 'step' => 'sso', 'label' => get_string('testsso', 'meetletacbmarketplace'), 'ok' => $directok, 'detail' => $directdetail, 'done' => false, 'abort' => false, ]; } protected static function step_cleanup(array &$state): array { $service = self::service(); $client = $service->get_client(); $ok = true; $detail = ''; try { if (!empty($state['scoid'])) { $service->delete_sco((int)$state['scoid']); } if (!empty($state['principalid'])) { $client->request(['action' => 'principals-delete', 'principal-id' => (int)$state['principalid']]); } } catch (Throwable $e) { $ok = false; $detail = $e->getMessage(); } $service->logout(); self::reset(); return [ 'step' => 'cleanup', 'label' => get_string('testmeeting', 'meetletacbmarketplace'), 'ok' => $ok, 'detail' => $detail, 'done' => true, 'abort' => false, ]; } }