Adobe Connect Bridge free edition (meetletacbfree) v1.1.3

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-01 23:59:56 +03:30
commit 91334a46b5
93 changed files with 21023 additions and 0 deletions
+967
View File
@@ -0,0 +1,967 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Admin settings for MeetLet Adobe Connect Bridge (tabbed).
*
* @package mod_meetletacbfree
* @copyright 2026 MeetLet <info@meetlet.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
use mod_meetletacbfree\admin\settingspage_tabs;
use mod_meetletacbfree\local\config;
use mod_meetletacbfree\local\conntest_runner;
use mod_meetletacbfree\local\privacy_policy;
use mod_meetletacbfree\local\server_guide;
use mod_meetletacbfree\registration\manager as registration_manager;
// Ensure class is available even if Moodle classmap cache is stale after adding this file.
require_once(__DIR__ . '/classes/local/server_guide.php');
require_once(__DIR__ . '/classes/local/privacy_policy.php');
if ($ADMIN->fulltree) {
global $OUTPUT;
$settings = new settingspage_tabs(
$settings->name,
$settings->visiblename,
$settings->req_capability,
$settings->hidden
);
$logo = $OUTPUT->image_url('adobeconnect', 'meetletacbfree');
$stylesurl = new moodle_url('/mod/meetletacbfree/styles.css', ['v' => '2026090146']);
$jsurl = new moodle_url('/mod/meetletacbfree/javascript/settings_conntest.js', ['v' => '2026082135']);
$togglejsurl = new moodle_url('/mod/meetletacbfree/javascript/settings_toggles.js', ['v' => '2026081907']);
$matrixjsurl = new moodle_url('/mod/meetletacbfree/javascript/featureaccess_matrix.js');
$accessresetjsurl = new moodle_url('/mod/meetletacbfree/javascript/settings_access_reset.js', ['v' => '2026090115']);
$resetajaxurl = new moodle_url('/mod/meetletacbfree/ajax/settings_reset.php');
$modalscrolljsurl = new moodle_url('/mod/meetletacbfree/javascript/meetlet_modal_scroll.js', ['v' => '2026082239']);
$registrationjsurl = new moodle_url('/mod/meetletacbfree/javascript/settings_registration.js', ['v' => '2026090144']);
$cronjsurl = null;
$ajaxurl = new moodle_url('/mod/meetletacbfree/ajax/conntest.php');
// Load styles.css explicitly on admin tabs (theme concatenate alone is not enough for switch CSS).
$styles = '
<link rel="stylesheet" href="' . s($stylesurl->out(false)) . '">
<style>
.path-admin #adminsettings .settingsform > h2 {
display: none;
}
.path-admin .meetlet-settings-hero {
display:flex; gap:1rem; align-items:center;
padding:1.15rem 1.25rem; margin:0 0 1rem;
border-radius:16px;
background:
radial-gradient(700px 180px at 100% 0%, rgba(82,204,163,.22), transparent 55%),
linear-gradient(135deg, #00261a 0%, #0a3d2e 60%, #145c45 100%);
color:#fff;
border:1px solid rgba(82,204,163,.25);
}
.path-admin .meetlet-settings-hero img {
width:56px; height:56px; object-fit:contain;
background:rgba(255,255,255,.95); border-radius:12px; padding:6px;
}
.path-admin .meetlet-settings-hero h3 {
margin:0 0 .2rem; font-size:1.2rem; font-weight:700; color:#fff;
display:flex; flex-wrap:wrap; align-items:center; gap:.45rem .55rem;
}
.path-admin .meetlet-settings-hero .meetlet-settings-version {
display:inline-block;
flex:0 0 auto;
align-self:center;
margin-inline-start:auto;
padding:.12rem .55rem;
font-size:.72rem; font-weight:650; line-height:1.35;
letter-spacing:.02em;
border-radius:999px;
background:rgba(82,204,163,.18);
border:1px solid rgba(82,204,163,.35);
color:#b8f5df;
white-space:nowrap;
direction:ltr;
unicode-bidi:isolate;
}
.path-admin .meetlet-settings-hero p {
margin:0; opacity:.85; color:#e6faf3;
white-space:nowrap;
max-width:none;
}
.path-admin .meetlet-settings-copy {
min-width:0; flex:1 1 auto;
}
@media (max-width: 900px) {
.path-admin .meetlet-settings-hero p {
white-space:normal;
}
}
.path-admin #adminsettings .nav-tabs {
border-bottom:2px solid #cfe8de;
gap:.2rem;
}
.path-admin #adminsettings .nav-tabs .nav-link {
border:0 !important;
border-radius:6px !important;
padding:.45rem .9rem;
color:#00261a;
font-weight:650;
box-shadow:none !important;
outline:none !important;
}
.path-admin #adminsettings .nav-tabs .nav-link:hover {
background:#e6faf3; color:#00261a;
}
.path-admin #adminsettings .nav-tabs .nav-link.active {
background:#52cca3; color:#00261a;
}
.path-admin #adminsettings .nav-tabs .nav-link:focus,
.path-admin #adminsettings .nav-tabs .nav-link:focus-visible,
.path-admin #adminsettings .nav-tabs .nav-link:active {
outline:none !important;
box-shadow:none !important;
border:0 !important;
}
.path-admin .meetlet-feature-group {
margin:1rem 0 .35rem;
padding:1rem 1.1rem .35rem;
border:1px solid #cfe8de;
border-radius:12px;
background:linear-gradient(180deg, #f3fbf8 0%, #ffffff 55%);
}
.path-admin .meetlet-feature-group h4 {
margin:0 0 .25rem; color:#00261a; font-size:1.02rem; font-weight:700;
}
.path-admin .meetlet-feature-group p {
margin:0 0 .5rem; color:#4a6b5f; font-size:.9rem;
}
.path-admin .meetlet-settings-card {
background:#fff; border:1px solid #cfe8de; border-radius:14px;
padding:1rem 1.15rem; margin:.35rem 0 1rem;
}
.meetlet-license-badge {
font-weight: 700;
border-radius: 999px;
padding: .35rem .65rem;
}
.meetlet-license-badge--active {
background: var(--ml-green, #52cca3);
color: var(--ml-green-deep, #00261a);
}
.meetlet-license-badge--inactive {
background: #fff4d6;
color: #5a4700;
}
.path-admin #meetlet-license-modal.meetlet-modal,
.path-admin #meetlet-registration-modal.meetlet-modal,
.path-admin #meetlet-cronmon-modal.meetlet-modal,
.path-admin #meetlet-access-reset-modal.meetlet-modal {
z-index: 20000 !important;
}
.path-admin .meetlet-access-reset-toolbar {
display: flex;
justify-content: flex-end;
margin: 0 0 .65rem;
}
html[dir="rtl"] .path-admin .meetlet-access-reset-toolbar {
justify-content: flex-start;
}
/* Cron videos ops cell: no extra cell/wrapper border (icon buttons keep theirs). */
.path-admin .meetlet-cronmon__table td.meetlet-cronmon__dl {
border-left: 0 !important;
border-right: 0 !important;
border-top: 0 !important;
background: transparent !important;
box-shadow: none !important;
}
.path-admin .meetlet-cronmon__dl .meetlet-iconactions {
border: 0 !important;
background: transparent !important;
box-shadow: none !important;
padding: 0 !important;
margin: 0 !important;
}
.path-admin .meetlet-modal--confirm .meetlet-modal__dialog {
width: min(480px, 100%);
max-height: min(86vh, 640px);
overflow: hidden;
display: flex;
flex-direction: column;
}
.path-admin .meetlet-modal--confirm .meetlet-modal__head {
padding: 1.2rem 1.15rem 1rem;
border-radius: 18px 18px 0 0;
}
.path-admin .meetlet-modal__lead {
margin: 0 0 .85rem;
font-size: 1rem;
font-weight: 650;
line-height: 1.65;
color: #00261a;
}
.path-admin .meetlet-modal__callout {
margin: 0 0 .9rem;
padding: .85rem 1rem .7rem;
border-radius: 12px;
border: 1px solid #e6c56a;
background: #fff8e6;
color: #5a4700;
}
.path-admin .meetlet-modal__callout-title {
margin: 0 0 .4rem;
font-weight: 700;
}
.path-admin .meetlet-modal__limits {
margin: 0 0 .55rem;
padding-inline-start: 1.2rem;
}
.path-admin .meetlet-modal__keep {
margin: 0;
color: #334942;
line-height: 1.65;
}
.path-admin .meetlet-modal__foot {
display: flex;
justify-content: flex-end;
align-items: center;
gap: .6rem;
flex-wrap: wrap;
padding: .9rem 1.15rem 1.05rem;
border-top: 1px solid #cfe8de;
background: linear-gradient(180deg, #f7fcfa 0%, #ffffff 70%);
}
.path-admin .meetlet-modal__foot .btn {
border-radius: 10px;
font-weight: 700;
padding: .5rem 1.15rem;
}
.path-admin .meetlet-modal__foot .btn-success {
background: #52cca3 !important;
border-color: #3db892 !important;
color: #00261a !important;
}
.path-admin .meetlet-modal__foot .btn-outline-danger {
color: #a33b3b !important;
border-color: #d9a0a0 !important;
background: #fff !important;
}
.path-admin .meetlet-modal__foot .btn-outline-danger:hover,
.path-admin .meetlet-modal__foot .btn-outline-danger:focus {
color: #fff !important;
background: #c0392b !important;
border-color: #c0392b !important;
}
.path-admin .meetlet-about {
display:grid; gap:1.1rem;
}
.path-admin .meetlet-about__brand {
display:flex; justify-content:center; align-items:center;
padding:1.35rem 1.2rem;
border-radius:14px;
background:#f0f2f4;
border:1px solid #e2e6ea;
}
.path-admin .meetlet-about__brand img {
max-width:240px; width:100%; height:auto; display:block; margin:0 auto;
}
.path-admin .meetlet-about h4 { margin:0 0 .55rem; color:#00261a; }
.path-admin .meetlet-about p {
margin:0 0 .85rem; color:#334942; line-height:1.75; text-align:justify;
}
.path-admin .meetlet-guide { display:grid; gap:1rem; }
.path-admin .meetlet-guide__heading { margin:0; color:#00261a; font-size:1.15rem; }
.path-admin .meetlet-guide__intro {
margin:0; color:#334942; line-height:1.7;
}
.path-admin .meetlet-guide__callout {
padding:.85rem 1rem; border-radius:10px; line-height:1.65;
border:1px solid #e6c56a; background:#fff8e6; color:#5a4700;
}
.path-admin .meetlet-guide__section {
padding:1rem 1.05rem; border-radius:12px;
border:1px solid #cfe8de; background:#f7fcfa;
}
.path-admin .meetlet-guide__section--current { background:#fff; }
.path-admin .meetlet-guide__section h4 {
margin:0 0 .55rem; color:#00261a; font-size:1.02rem; font-weight:700;
}
.path-admin .meetlet-guide__section h5 {
margin:.85rem 0 .35rem; color:#0a3d2e; font-size:.95rem; font-weight:700;
}
.path-admin .meetlet-guide__body,
.path-admin .meetlet-guide__body p,
.path-admin .meetlet-guide__body li {
color:#334942; line-height:1.7;
}
.path-admin .meetlet-guide__body p { margin:0 0 .55rem; }
.path-admin .meetlet-guide__body ol,
.path-admin .meetlet-guide__body ul { margin:0 0 .35rem; padding-inline-start:1.25rem; }
.path-admin .meetlet-guide__body li { margin:0 0 .35rem; }
.path-admin .meetlet-guide__body code,
.path-admin .meetlet-guide__table code {
font-size:.88em; padding:.05rem .3rem; border-radius:4px;
background:#e6faf3; color:#00261a;
}
.path-admin .meetlet-guide__table {
width:100%; margin:.35rem 0 0; border-collapse:collapse;
}
.path-admin .meetlet-guide__table th,
.path-admin .meetlet-guide__table td {
padding:.45rem .55rem; border:1px solid #cfe8de; vertical-align:top;
text-align:start;
}
.path-admin .meetlet-guide__table thead th,
.path-admin .meetlet-guide__table tbody th {
background:#e6faf3; color:#00261a; font-weight:650;
}
.path-admin .meetlet-guide__code {
margin:.35rem 0 0; padding:.75rem .9rem; overflow-x:auto;
border-radius:10px; border:1px solid #cfe8de;
background:#0a3d2e; color:#e6faf3;
font-size:.82rem; line-height:1.45; direction:ltr; text-align:left;
white-space:pre;
}
.path-admin .meetlet-guide__code-cell code {
font-weight:800; letter-spacing:.02em; direction:ltr; unicode-bidi:isolate;
}
.path-admin .meetlet-guide__table td ol,
.path-admin .meetlet-guide__table td ul {
margin:.15rem 0 0; padding-inline-start:1.15rem;
}
.path-admin .meetlet-guide__table td li { margin:0 0 .25rem; }
.path-admin .meetlet-hourpicker { max-width: 42rem; }
.path-admin .meetlet-hourpicker.is-dimmed,
.path-admin #adminsettings .form-item.is-dimmed {
opacity: .45;
pointer-events: none;
}
.path-admin .meetlet-hourpicker__tz {
margin: 0 0 .55rem; color:#4a6b5f; font-size:.88rem;
}
.path-admin .meetlet-hourpresets {
display:flex; flex-wrap:wrap; gap:.4rem; margin:0 0 .65rem;
}
.path-admin .meetlet-hourpreset {
border:1px solid #9fdcc4; background:#fff; color:#00261a;
border-radius:999px; padding:.28rem .7rem; font-size:.8rem; font-weight:650;
cursor:pointer;
}
.path-admin .meetlet-hourpreset:hover { background:#e6faf3; }
.path-admin .meetlet-hourpreset--ghost { border-color:#cfe8de; color:#4a6b5f; }
.path-admin .meetlet-hourgrid {
display:grid; grid-template-columns:repeat(8, minmax(0,1fr)); gap:.35rem;
}
.path-admin .meetlet-hourchip {
border:1px solid #cfe8de; background:#f7fcfa; color:#00261a;
border-radius:10px; padding:.45rem 0; font-size:.82rem; font-weight:750;
cursor:pointer;
}
.path-admin .meetlet-hourchip.is-on {
background:#52cca3; border-color:#0d9f6e; color:#00261a;
}
.path-admin .meetlet-hourchip.is-now { box-shadow:0 0 0 2px rgba(13,159,110,.35); }
@media (max-width: 720px) {
.path-admin .meetlet-hourgrid { grid-template-columns:repeat(6, minmax(0,1fr)); }
}
.path-admin .meetlet-about__cta-wrap {
display:flex; justify-content:center; margin-top:.35rem; padding-top:.35rem;
}
.path-admin .meetlet-about__cta {
display:inline-flex; align-items:center; justify-content:center;
min-width:220px; padding:.7rem 1.35rem; border-radius:10px;
background:#52cca3; color:#00261a !important;
text-decoration:none !important; font-weight:700;
border:1px solid #3db892;
box-shadow:0 6px 16px rgba(0,38,26,.08);
}
.path-admin .meetlet-about__cta:hover {
background:#6fd9b5; color:#00261a !important;
}
.path-admin .meetlet-settings-btn {
display:inline-flex; align-items:center; justify-content:center; gap:.45rem;
padding:.6rem 1.15rem; border-radius:10px; border:0; cursor:pointer;
background:#52cca3; color:#00261a !important;
text-decoration:none !important; font-weight:700;
}
.path-admin .meetlet-settings-btn:hover { background:#6fd9b5; color:#00261a !important; }
.path-admin .meetlet-settings-btn:disabled { opacity:.65; cursor:wait; }
.path-admin .meetlet-settings-btn svg {
width:1.1rem; height:1.1rem; flex:0 0 auto; display:block;
}
.path-admin .meetlet-license-actions {
display:flex; flex-wrap:wrap; gap:.55rem; align-items:center;
}
.path-admin .meetlet-license-actions .btn {
display:inline-flex; align-items:center; justify-content:center; gap:.45rem;
}
.path-admin .meetlet-license-actions .btn svg {
width:1.05rem; height:1.05rem; flex:0 0 auto; display:block;
}
.path-admin .meetlet-license-btn--free {
background: linear-gradient(180deg, #e6faf3 0%, #d4f5ea 100%) !important;
border: 2px solid #52cca3 !important;
color: #00261a !important;
border-radius: 10px !important;
font-weight: 700 !important;
padding: .6rem 1.15rem !important;
box-shadow: 0 4px 12px rgba(82, 204, 163, 0.18) !important;
}
.path-admin .meetlet-license-btn--free:hover,
.path-admin .meetlet-license-btn--free:focus {
background: linear-gradient(180deg, #6fd9b5 0%, #52cca3 100%) !important;
border-color: #3db892 !important;
color: #00261a !important;
box-shadow: 0 6px 16px rgba(82, 204, 163, 0.28) !important;
}
.path-admin .meetlet-conntest { max-width:none; margin:0; }
.path-admin .meetlet-conntest__actions {
display:flex; flex-wrap:wrap; gap:.75rem; align-items:center;
margin:.85rem 0 1rem;
}
.path-admin .meetlet-conntest__status { color:#4a6b5f; font-size:.9rem; }
.path-admin .meetlet-conntest__progress {
margin:0 0 1rem; padding:.85rem 1rem;
border:1px solid #cfe8de; border-radius:12px; background:#f7fcfa;
}
.path-admin .meetlet-conntest__progress-label {
display:flex; justify-content:space-between; gap:.75rem;
margin:0 0 .45rem; color:#00261a; font-weight:650; font-size:.92rem;
}
.path-admin .meetlet-conntest__progress-track {
height:10px; border-radius:999px; background:#d9eee5; overflow:hidden;
}
.path-admin .meetlet-conntest__progress-fill {
height:100%; width:0; border-radius:999px;
background:linear-gradient(90deg, #2f9f7a, #52cca3);
transition:width .28s ease;
}
.path-admin .meetlet-conntest__num {
min-width:1.7rem; height:1.7rem; border-radius:8px;
display:inline-flex; align-items:center; justify-content:center;
background:#e6faf3; color:#00261a; font-weight:750; font-size:.85rem;
}
.path-admin .meetlet-conntest__card--pending { opacity:.78; background:#fbfefe; }
.path-admin .meetlet-conntest__card--running {
border-color:#e6b422;
box-shadow:0 0 0 2px rgba(230,180,34,.22);
background:#fff8e6;
}
.path-admin .meetlet-conntest__card--running .meetlet-conntest__num {
background:#ffe08a; color:#5c4700;
}
.path-admin .meetlet-conntest__card--ok {
border-color:#9fdcc4; background:#f3fbf8;
}
.path-admin .meetlet-conntest__card--fail {
border-color:#ead3d0; background:#fff8f7;
}
.path-admin .meetlet-conntest__card--skipped { opacity:.55; }
.path-admin .meetlet-dot--pending { background:#9bb7ac; box-shadow:0 0 0 4px rgba(155,183,172,.18); }
.path-admin .meetlet-dot--running {
background:#e6b422; box-shadow:0 0 0 4px rgba(230,180,34,.28);
animation:meetletPulse 1s ease-in-out infinite;
}
.path-admin .meetlet-dot--skipped { background:#b7c4be; box-shadow:none; }
.path-admin .meetlet-pill--pending { background:#e8efec; color:#456257; }
.path-admin .meetlet-pill--running { background:#ffe08a; color:#5c4700; }
.path-admin .meetlet-pill--skipped { background:#eef1f0; color:#667a72; }
@keyframes meetletPulse {
0%, 100% { transform:scale(1); opacity:1; }
50% { transform:scale(1.15); opacity:.75; }
}
.path-admin #adminsettings form.settingsform {
padding-bottom:1rem;
}
/* Single save row: spacing only — no stacked border/gradient bands. */
.path-admin #adminsettings .settingsform > .row:has(.btn-primary),
.path-admin #adminsettings .form-buttons,
.path-admin #adminsettings .btns-group {
margin-top:1.25rem !important;
padding:0;
border:0;
background:transparent;
border-radius:0;
}
.path-admin #adminsettings .meetlet-savebar {
margin-top: 1.25rem !important;
margin-right: 0 !important;
margin-left: 0 !important;
margin-inline: 0 !important;
flex: 0 0 100% !important;
max-width: 100% !important;
width: 100% !important;
padding: .35rem 0 !important;
border: 0 !important;
background: transparent !important;
border-radius: 0 !important;
}
@media (min-width: 576px) {
.path-admin #adminsettings .meetlet-savebar.offset-sm-3,
.path-admin #adminsettings .meetlet-savebar {
margin-right: 0 !important;
margin-left: 0 !important;
}
}
.path-admin #adminsettings .btn-primary,
.path-admin #adminsettings input[type="submit"].btn-primary,
.path-admin #adminsettings button[type="submit"].btn-primary,
.path-admin #adminsettings .meetlet-btn-with-icon.btn-primary {
background:#52cca3 !important;
border-color:#3db892 !important;
color:#00261a !important;
font-weight:700 !important;
border-radius:10px !important;
padding:.55rem 1.35rem !important;
box-shadow:none !important;
}
.path-admin #adminsettings .btn-primary:hover,
.path-admin #adminsettings input[type="submit"].btn-primary:hover,
.path-admin #adminsettings button[type="submit"].btn-primary:hover {
background:#6fd9b5 !important;
border-color:#52cca3 !important;
color:#00261a !important;
}
.path-admin .meetlet-btn-with-icon {
display:inline-flex !important;
align-items:center;
justify-content:center;
gap:.45rem;
}
.path-admin .meetlet-btn-with-icon svg {
width:1.1rem;
height:1.1rem;
flex:0 0 auto;
display:block;
}
.path-admin .meetlet-btn-with-icon__label {
line-height:1.2;
}
.path-admin .meetlet-server-group {
margin:1.1rem 0 .4rem; padding:1rem 1.1rem .2rem;
border:1px solid #cfe8de; border-radius:12px;
background:linear-gradient(180deg, #f3fbf8 0%, #ffffff 60%);
}
.path-admin .meetlet-server-group h4 {
margin:0 0 .25rem; color:#00261a; font-size:1.02rem; font-weight:700;
}
.path-admin .meetlet-server-group p {
margin:0 0 .55rem; color:#4a6b5f; font-size:.9rem; line-height:1.55;
}
/* RTL alignment for Persian/Arabic admin settings */
html[dir="rtl"] .path-admin #adminsettings,
html[dir="rtl"] .path-admin #adminsettings .settingsform,
html[dir="rtl"] .path-admin #adminsettings .tab-content,
html[dir="rtl"] .path-admin #adminsettings .form-item {
direction: rtl;
}
html[dir="rtl"] .path-admin #adminsettings .form-item .form-label,
html[dir="rtl"] .path-admin #adminsettings .form-item .form-label.label,
html[dir="rtl"] .path-admin #adminsettings .form-item .form-label > label,
html[dir="rtl"] .path-admin #adminsettings .form-item .form-label .form-shortname,
html[dir="rtl"] .path-admin #adminsettings .form-description,
html[dir="rtl"] .path-admin #adminsettings .form-defaultinfo,
html[dir="rtl"] .path-admin #adminsettings .form-setting .form-check,
html[dir="rtl"] .path-admin #adminsettings .form-setting .checkbox,
html[dir="rtl"] .path-admin #adminsettings .form-setting label,
html[dir="rtl"] .path-admin #adminsettings h3.main,
html[dir="rtl"] .path-admin #adminsettings .form-item .form-label.text-sm-end,
html[dir="rtl"] .path-admin #adminsettings .form-item .form-label.text-end {
text-align: right !important;
direction: rtl;
}
html[dir="rtl"] .path-admin #adminsettings .meetlet-feature-group,
html[dir="rtl"] .path-admin #adminsettings .meetlet-feature-group h4,
html[dir="rtl"] .path-admin #adminsettings .meetlet-feature-group p,
html[dir="rtl"] .path-admin #adminsettings .meetlet-settings-card,
html[dir="rtl"] .path-admin #adminsettings .meetlet-about,
html[dir="rtl"] .path-admin #adminsettings .meetlet-about p,
html[dir="rtl"] .path-admin #adminsettings .meetlet-about h4,
html[dir="rtl"] .path-admin #adminsettings .meetlet-privacy,
html[dir="rtl"] .path-admin #adminsettings .meetlet-privacy p,
html[dir="rtl"] .path-admin #adminsettings .meetlet-privacy h3,
html[dir="rtl"] .path-admin #adminsettings .meetlet-privacy h4,
html[dir="rtl"] .path-admin #adminsettings .meetlet-conntest,
html[dir="rtl"] .path-admin #adminsettings .meetlet-conntest__intro,
html[dir="rtl"] .path-admin #adminsettings .meetlet-conntest__title,
html[dir="rtl"] .path-admin #adminsettings .meetlet-conntest__status,
html[dir="rtl"] .path-admin #adminsettings .meetlet-conntest__progress-label,
html[dir="rtl"] .path-admin .meetlet-settings-hero,
html[dir="rtl"] .path-admin .meetlet-settings-hero h3,
html[dir="rtl"] .path-admin .meetlet-settings-hero .meetlet-settings-version,
html[dir="rtl"] .path-admin .meetlet-settings-hero p,
html[dir="rtl"] .path-admin .meetlet-settings-copy {
direction: rtl;
text-align: right;
}
html[dir="rtl"] .path-admin .meetlet-settings-hero {
flex-direction: row;
}
html[dir="rtl"] .path-admin #adminsettings .nav-tabs {
direction: rtl;
}
html[dir="rtl"] .path-admin #adminsettings .form-setting input[type="text"],
html[dir="rtl"] .path-admin #adminsettings .form-setting input[type="password"],
html[dir="rtl"] .path-admin #adminsettings .form-setting input[type="url"],
html[dir="rtl"] .path-admin #adminsettings .form-setting input[type="number"],
html[dir="rtl"] .path-admin #adminsettings .form-setting select,
html[dir="rtl"] .path-admin #adminsettings .form-setting textarea {
direction: ltr;
text-align: left;
unicode-bidi: isolate;
}
html[dir="rtl"] .path-admin #adminsettings .form-setting .form-check,
html[dir="rtl"] .path-admin #adminsettings .form-setting .checkbox,
html[dir="rtl"] .path-admin #adminsettings .form-setting .form-checkbox {
display: flex;
flex-direction: row-reverse;
justify-content: flex-end;
align-items: center;
gap: .5rem;
text-align: right;
}
html[dir="rtl"] .path-admin #adminsettings .form-item .form-label .form-shortname {
direction: ltr;
unicode-bidi: isolate;
text-align: right !important;
display: block;
}
html[dir="rtl"] .path-admin #adminsettings .form-setting .form-checkbox {
direction: rtl;
text-align: right;
}
html[dir="rtl"] .path-admin #adminsettings .form-setting .defaultsnext {
text-align: right;
}
html[dir="rtl"] .path-admin #adminsettings .meetlet-savebar,
html[dir="rtl"] .path-admin #adminsettings .form-buttons {
text-align: right;
}
</style>
<script src="' . s($modalscrolljsurl->out(false)) . '" defer></script>
<script src="' . s($registrationjsurl->out(false)) . '" defer></script>
<script src="' . s($jsurl->out(false)) . '" defer></script>
<script src="' . s($togglejsurl->out(false)) . '" defer></script>
<script src="' . s($matrixjsurl->out(false)) . '" defer></script>
<script src="' . s($accessresetjsurl->out(false)) . '" defer></script>';
$plugin = new stdClass();
include(__DIR__ . '/version.php');
$release = $plugin->release ?? '';
if ($release !== '' && strpos(current_language(), 'fa') === 0) {
$release = strtr($release, [
'0' => '۰', '1' => '۱', '2' => '۲', '3' => '۳', '4' => '۴',
'5' => '۵', '6' => '۶', '7' => '۷', '8' => '۸', '9' => '۹',
]);
}
$versionbadge = $release !== ''
? html_writer::span(get_string('settingsversion', 'meetletacbfree', $release), 'meetlet-settings-version')
: '';
$hero = $styles . html_writer::div(
html_writer::img($logo, 'Adobe Connect', ['alt' => 'Adobe Connect']) .
html_writer::div(
html_writer::tag('h3', get_string('pluginname', 'meetletacbfree')) .
html_writer::tag('p', get_string('settingsintro', 'meetletacbfree')),
'meetlet-settings-copy'
) .
$versionbadge,
'meetlet-settings-hero'
);
$settings->set_hero($hero);
// Tab: Online activation (required before other features work).
$page = new admin_settingpage('meetletacbfree_registration', get_string('tab_registration', 'meetletacbfree'));
$page->add(new admin_setting_heading(
'meetletacbfree/registrationheading',
get_string('registration_heading', 'meetletacbfree'),
get_string('registration_heading_desc', 'meetletacbfree')
));
$page->add(new admin_setting_configtext(
'meetletacbfree/registration_api_url',
get_string('registration_api_url', 'meetletacbfree'),
get_string('registration_api_url_desc', 'meetletacbfree'),
registration_manager::DEFAULT_API_URL,
PARAM_URL
));
$page->add(new \mod_meetletacbfree\admin\setting_registration());
$settings->add($page);
// Tab: Privacy (always visible — read policy before activating).
$page = new admin_settingpage('meetletacbfree_privacy', get_string('tab_privacy', 'meetletacbfree'));
$page->add(new admin_setting_heading(
'meetletacbfree/privacycard',
'',
privacy_policy::render()
));
$settings->add($page);
$registered = registration_manager::is_registered();
if ($registered) {
// Tab: Server setup guide.
$page = new admin_settingpage('meetletacbfree_serverguide', get_string('tab_serverguide', 'meetletacbfree'));
$page->add(new admin_setting_heading(
'meetletacbfree/serverguidecard',
'',
server_guide::render()
));
$settings->add($page);
// Tab 2: Server settings (API / admin / offline listener).
$page = new admin_settingpage('meetletacbfree_connection', get_string('settings_connection', 'meetletacbfree'));
$page->add(new admin_setting_heading(
'meetletacbfree/connectionheading',
get_string('settings_connection', 'meetletacbfree'),
get_string('settings_connection_desc', 'meetletacbfree')
));
$page->add(new admin_setting_heading(
'meetletacbfree/server_api_group',
'',
html_writer::div(
html_writer::tag('h4', get_string('server_group_api', 'meetletacbfree')) .
html_writer::tag('p', get_string('server_group_api_desc', 'meetletacbfree')),
'meetlet-server-group'
)
));
$page->add(new admin_setting_configtext(
'meetletacbfree/host', get_string('host', 'meetletacbfree'), get_string('host_desc', 'meetletacbfree'),
'https://connect.example.edu/api/xml', PARAM_URL
));
$page->add(new admin_setting_configtext(
'meetletacbfree/meethost', get_string('meethost', 'meetletacbfree'), get_string('meethost_desc', 'meetletacbfree'),
'connect.example.edu', PARAM_HOST
));
$page->add(new admin_setting_configtext(
'meetletacbfree/port', get_string('port', 'meetletacbfree'), get_string('port_desc', 'meetletacbfree'),
'443', PARAM_INT
));
$page->add(new admin_setting_configcheckbox(
'meetletacbfree/https', get_string('https', 'meetletacbfree'), get_string('https_desc', 'meetletacbfree'), 1
));
$page->add(new admin_setting_heading(
'meetletacbfree/server_admin_group',
'',
html_writer::div(
html_writer::tag('h4', get_string('server_group_admin', 'meetletacbfree')) .
html_writer::tag('p', get_string('server_group_admin_desc', 'meetletacbfree')),
'meetlet-server-group'
)
));
$page->add(new admin_setting_configtext(
'meetletacbfree/admin_login', get_string('admin_login', 'meetletacbfree'), get_string('admin_login_desc', 'meetletacbfree'),
'', PARAM_RAW_TRIMMED
));
$page->add(new admin_setting_configpasswordunmask(
'meetletacbfree/admin_password', get_string('admin_password', 'meetletacbfree'), get_string('admin_password_desc', 'meetletacbfree'), ''
));
$page->add(new admin_setting_configtext(
'meetletacbfree/admin_httpauth', get_string('admin_httpauth', 'meetletacbfree'), get_string('admin_httpauth_desc', 'meetletacbfree'),
'Generate-Safe-Random-Token', PARAM_TEXT
));
$page->add(new admin_setting_configcheckbox(
'meetletacbfree/email_login', get_string('email_login', 'meetletacbfree'), get_string('email_login_desc', 'meetletacbfree'), 1
));
$settings->add($page);
// Tab 4: Connection test (Ajax).
$page = new admin_settingpage('meetletacbfree_test', get_string('tab_connectiontest', 'meetletacbfree'));
$steplabels = [
'login' => get_string('testlogin', 'meetletacbfree'),
'folders' => get_string('testfolders', 'meetletacbfree'),
'user' => get_string('testuser', 'meetletacbfree'),
'sso' => get_string('testsso', 'meetletacbfree'),
'cleanup' => get_string('testcleanup', 'meetletacbfree'),
];
$testui = html_writer::div(
html_writer::start_div('meetlet-conntest__hero') .
html_writer::img($logo, 'Adobe Connect', ['class' => 'meetlet-conntest__logo']) .
html_writer::tag('h2', get_string('conntesttitle', 'meetletacbfree'), ['class' => 'meetlet-conntest__title']) .
html_writer::tag('p', get_string('conntestintro', 'meetletacbfree'), ['class' => 'meetlet-conntest__intro']) .
html_writer::end_div() .
html_writer::div(
html_writer::tag('button',
'<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">'
. '<path fill="currentColor" d="M8 5v14l11-7z"/></svg>'
. html_writer::span(get_string('runtest', 'meetletacbfree'), 'meetlet-btn-with-icon__label'),
[
'type' => 'button',
'class' => 'meetlet-settings-btn meetlet-btn-with-icon',
'data-meetlet-run-test' => '1',
]
) .
html_writer::span('', 'meetlet-conntest__status', ['data-meetlet-test-status' => '1']),
'meetlet-conntest__actions'
) .
html_writer::div(
html_writer::div(
html_writer::span('', 'meetlet-conntest__progress-text', ['data-meetlet-progress-label' => '1']),
'meetlet-conntest__progress-label'
) .
html_writer::div(
html_writer::div('', 'meetlet-conntest__progress-fill', ['data-meetlet-progress-fill' => '1']),
'meetlet-conntest__progress-track'
),
'meetlet-conntest__progress',
['data-meetlet-progress' => '1', 'hidden' => 'hidden']
) .
html_writer::div('', 'meetlet-conntest__grid', ['data-meetlet-test-grid' => '1']),
'meetlet-conntest',
[
'id' => 'meetlet-acb-conntest',
'data-endpoint' => $ajaxurl->out(false),
'data-sesskey' => sesskey(),
'data-steps' => implode(',', conntest_runner::STEPS),
'data-step-labels' => json_encode($steplabels, JSON_UNESCAPED_UNICODE),
'data-str-ok' => get_string('testok', 'meetletacbfree'),
'data-str-fail' => get_string('testfail', 'meetletacbfree'),
'data-str-running' => get_string('testrunning', 'meetletacbfree'),
'data-str-idle' => '',
'data-str-waiting' => get_string('testwaiting', 'meetletacbfree'),
'data-str-runningstep' => get_string('testrunningstep', 'meetletacbfree'),
'data-str-skipped' => get_string('testskipped', 'meetletacbfree'),
'data-str-progress' => get_string('testprogress', 'meetletacbfree'),
]
);
$page->add(new admin_setting_heading('meetletacbfree/testui', '', $testui));
$settings->add($page);
// Tab 4: Meeting settings (defaults + feature visibility).
$page = new admin_settingpage('meetletacbfree_meetings', get_string('tab_meetingsettings', 'meetletacbfree'));
$page->add(new admin_setting_heading(
'meetletacbfree/roomdefaultsheading',
get_string('roomdefaults', 'meetletacbfree'),
get_string('roomdefaults_desc', 'meetletacbfree')
));
$page->add(new admin_setting_configselect(
'meetletacbfree/default_meetinglang',
get_string('default_meetinglang', 'meetletacbfree'),
get_string('default_meetinglang_desc', 'meetletacbfree'),
'',
config::meeting_lang_options()
));
$page->add(new admin_setting_configcheckbox(
'meetletacbfree/default_waitforhost',
get_string('default_waitforhost', 'meetletacbfree'),
get_string('default_waitforhost_desc', 'meetletacbfree'),
0
));
$page->add(new admin_setting_heading(
'meetletacbfree/featuresheading',
get_string('featuresheading', 'meetletacbfree'),
get_string('featuresheading_desc', 'meetletacbfree')
));
$featuregroups = ['recordingstab', 'contenttab'];
foreach ($featuregroups as $feature) {
$grouphtml = html_writer::div(
html_writer::tag('h4', get_string('featuregroup_' . $feature, 'meetletacbfree')) .
html_writer::tag('p', get_string('featuregroup_' . $feature . '_desc', 'meetletacbfree')),
'meetlet-feature-group'
);
$page->add(new admin_setting_heading(
'meetletacbfree/featuregroup_' . $feature,
'',
$grouphtml
));
// ZIP download is off by default; other features stay on.
$enabledefault = ($feature === 'zip') ? 0 : 1;
$page->add(new admin_setting_configcheckbox(
'meetletacbfree/enable_' . $feature,
get_string('enable_' . $feature, 'meetletacbfree'),
get_string('enable_' . $feature . '_desc', 'meetletacbfree'),
$enabledefault
));
$page->add(new admin_setting_configcheckbox(
'meetletacbfree/allow_override_' . $feature,
get_string('allow_override_' . $feature, 'meetletacbfree'),
get_string('allow_override_' . $feature . '_desc', 'meetletacbfree'),
1
));
}
$settings->add($page);
// Tab 5: Role feature access + Adobe room role mapping.
$page = new admin_settingpage('meetletacbfree_access', get_string('tab_accesssettings', 'meetletacbfree'));
$page->add(new admin_setting_heading(
'meetletacbfree/rolemappingheading',
get_string('siterolemapping', 'meetletacbfree'),
get_string('siterolemapping_desc', 'meetletacbfree')
));
$page->add(new \mod_meetletacbfree\admin\setting_rolemapping());
$page->add(new admin_setting_heading(
'meetletacbfree/accessheading',
get_string('featureaccess', 'meetletacbfree'),
get_string('featureaccess_desc', 'meetletacbfree')
));
$page->add(new admin_setting_configcheckbox(
'meetletacbfree/activity_featureaccess',
get_string('activity_featureaccess', 'meetletacbfree'),
get_string('activity_featureaccess_desc', 'meetletacbfree'),
1
));
$page->add(new \mod_meetletacbfree\admin\setting_featureaccess());
$page->add(new admin_setting_heading(
'meetletacbfree/accessresetassets',
'',
\mod_meetletacbfree\admin\access_reset_ui::page_assets($resetajaxurl)
));
$settings->add($page);
} // End tabs unlocked after online activation.
// Tab: About developer / MeetLet (read-only; always visible).
$page = new admin_settingpage('meetletacbfree_about', get_string('tab_aboutdev', 'meetletacbfree'));
$meetletlogo = $OUTPUT->image_url('meetlet_logo', 'meetletacbfree');
$abouturl = new moodle_url('https://meetlet.net/', [
'utm_source' => 'moodle',
'utm_medium' => 'plugin',
'utm_campaign' => 'meetletacbfree',
'utm_content' => 'about_developer_tab',
]);
$about = html_writer::div(
html_writer::div(
html_writer::img($meetletlogo, 'MeetLet', ['alt' => 'MeetLet']),
'meetlet-about__brand'
) .
html_writer::tag('h4', get_string('about_heading', 'meetletacbfree')) .
html_writer::tag('p', get_string('about_body_p1', 'meetletacbfree')) .
html_writer::tag('p', get_string('about_body_p2', 'meetletacbfree')) .
html_writer::tag('p', get_string('about_body_p3', 'meetletacbfree')) .
html_writer::tag('p', get_string('about_body_p4', 'meetletacbfree')) .
html_writer::tag('p', get_string('marketplace_commercial_note', 'meetletacbfree'), [
'class' => 'meetlet-about__commercial',
]) .
html_writer::div(
html_writer::link($abouturl, get_string('about_visit_website', 'meetletacbfree'), [
'class' => 'meetlet-about__cta',
'target' => '_blank',
'rel' => 'noopener',
]),
'meetlet-about__cta-wrap'
),
'meetlet-about meetlet-settings-card'
);
$page->add(new admin_setting_heading('meetletacbfree/aboutcard', '', $about));
$settings->add($page);
}