Adobe Connect Bridge free edition (meetletacbfree) v1.1.3
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+464
@@ -0,0 +1,464 @@
|
||||
<?php
|
||||
// This file is part of Moodle - http://moodle.org/
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
require_once($CFG->dirroot . '/course/moodleform_mod.php');
|
||||
|
||||
use mod_meetletacbfree\api\service;
|
||||
use mod_meetletacbfree\local\config;
|
||||
use mod_meetletacbfree\local\featureaccess;
|
||||
use mod_meetletacbfree\local\roles;
|
||||
use mod_meetletacbfree\registration\manager as registration_manager;
|
||||
|
||||
/**
|
||||
* MeetLet mod form.
|
||||
*
|
||||
* @package mod_meetletacbfree
|
||||
* @copyright 2026 MeetLet
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class mod_meetletacbfree_mod_form extends moodleform_mod {
|
||||
public function definition() {
|
||||
global $OUTPUT, $PAGE, $USER;
|
||||
|
||||
$mform = $this->_form;
|
||||
$context = context_course::instance($this->_course->id);
|
||||
$current = empty($this->current->instance) ? null : $this->current;
|
||||
$isnew = empty($this->current->instance);
|
||||
|
||||
if (!registration_manager::is_registered()) {
|
||||
$mform->addElement('static', 'registrationlock', '',
|
||||
registration_manager::admin_gate_notice_html());
|
||||
$this->standard_hidden_coursemodule_elements();
|
||||
$this->add_action_buttons(false);
|
||||
return;
|
||||
}
|
||||
|
||||
$PAGE->requires->js(new moodle_url('/mod/meetletacbfree/javascript/modform.js'));
|
||||
$PAGE->requires->js(new moodle_url('/mod/meetletacbfree/javascript/featureaccess_matrix.js'));
|
||||
$PAGE->requires->js(new moodle_url('/mod/meetletacbfree/javascript/settings_toggles.js'));
|
||||
$PAGE->requires->css(new moodle_url('/mod/meetletacbfree/styles.css', ['v' => '2026081913']));
|
||||
|
||||
$mform->addElement('header', 'general', get_string('general', 'form'));
|
||||
$mform->addElement('text', 'name', get_string('meetingname', 'meetletacbfree'), ['size' => '64']);
|
||||
$mform->setType('name', PARAM_TEXT);
|
||||
$mform->addRule('name', null, 'required', null, 'client');
|
||||
$mform->addRule('name', get_string('maximumchars', '', 255), 'maxlength', 255, 'client');
|
||||
$this->standard_intro_elements(get_string('meetingintro', 'meetletacbfree'));
|
||||
|
||||
$mform->addElement('header', 'meetletroom', get_string('roomsettings', 'meetletacbfree'));
|
||||
|
||||
// Create-new is default; existing meetings are loaded lazily via Ajax.
|
||||
$linkmodes = [
|
||||
0 => get_string('linkmode_create', 'meetletacbfree'),
|
||||
1 => get_string('linkmode_existing', 'meetletacbfree'),
|
||||
];
|
||||
$mform->addElement('select', 'linkmode', get_string('linkmode', 'meetletacbfree'), $linkmodes);
|
||||
$mform->setDefault('linkmode', 0);
|
||||
$mform->addHelpButton('linkmode', 'linkmode', 'meetletacbfree');
|
||||
if (!$isnew) {
|
||||
$mform->hardFreeze('linkmode');
|
||||
}
|
||||
|
||||
$meetingsmeta = [];
|
||||
$selectedsco = (int)($current->existingscoid ?? 0);
|
||||
$selectedlabel = get_string('chooseservermeeting', 'meetletacbfree');
|
||||
$readonly = !$isnew;
|
||||
// On edit of a linked activity, keep the selected meeting visible without listing all.
|
||||
if (!$isnew && !empty($current->linkmode) && $selectedsco && config::is_configured()) {
|
||||
try {
|
||||
$service = new service();
|
||||
$service->login_admin();
|
||||
$info = $service->meeting_info($selectedsco);
|
||||
$service->logout();
|
||||
if ($info) {
|
||||
$selectedlabel = (string)$info->name;
|
||||
$urlpath = trim((string)($info->url ?? ''), '/');
|
||||
if ($urlpath !== '') {
|
||||
$selectedlabel .= ' (/' . $urlpath . '/)';
|
||||
}
|
||||
$meetingsmeta[$selectedsco] = [
|
||||
'name' => (string)$info->name,
|
||||
'urlpath' => $urlpath,
|
||||
'starttime' => !empty($info->starttime) ? (int)strtotime((string)$info->starttime) : 0,
|
||||
'endtime' => !empty($info->endtime) ? (int)strtotime((string)$info->endtime) : 0,
|
||||
];
|
||||
}
|
||||
} catch (Throwable $e) {
|
||||
$selectedlabel = 'SCO ' . $selectedsco;
|
||||
}
|
||||
}
|
||||
|
||||
$mform->addElement('hidden', 'existingscoid', $selectedsco);
|
||||
$mform->setType('existingscoid', PARAM_INT);
|
||||
|
||||
$listhtml = '';
|
||||
if ($selectedsco) {
|
||||
$listhtml = html_writer::tag('li',
|
||||
html_writer::tag('button', s($selectedlabel), [
|
||||
'type' => 'button',
|
||||
'class' => 'meetlet-combobox__option is-selected',
|
||||
'data-scoid' => $selectedsco,
|
||||
'data-name' => $meetingsmeta[$selectedsco]['name'] ?? $selectedlabel,
|
||||
'data-urlpath' => $meetingsmeta[$selectedsco]['urlpath'] ?? '',
|
||||
'data-starttime' => (string)($meetingsmeta[$selectedsco]['starttime'] ?? 0),
|
||||
'data-endtime' => (string)($meetingsmeta[$selectedsco]['endtime'] ?? 0),
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
$toggleattrs = [
|
||||
'type' => 'button',
|
||||
'class' => 'meetlet-combobox__toggle',
|
||||
'data-meeting-toggle' => '1',
|
||||
'aria-expanded' => 'false',
|
||||
];
|
||||
if ($readonly) {
|
||||
$toggleattrs['disabled'] = 'disabled';
|
||||
}
|
||||
|
||||
$picker = html_writer::div(
|
||||
html_writer::tag('button',
|
||||
html_writer::span(s($selectedlabel), 'meetlet-combobox__value', ['data-meeting-label' => '1']) .
|
||||
html_writer::span('▾', 'meetlet-combobox__caret', ['aria-hidden' => 'true']),
|
||||
$toggleattrs
|
||||
) .
|
||||
html_writer::div(
|
||||
html_writer::empty_tag('input', [
|
||||
'type' => 'search',
|
||||
'class' => 'meetlet-combobox__search',
|
||||
'data-meeting-search' => '1',
|
||||
'placeholder' => get_string('searchservermeetings', 'meetletacbfree'),
|
||||
'autocomplete' => 'off',
|
||||
]) .
|
||||
html_writer::tag('ul', $listhtml, [
|
||||
'class' => 'meetlet-combobox__list',
|
||||
'data-meeting-list' => '1',
|
||||
]) .
|
||||
html_writer::div('', 'meetlet-combobox__hint', ['data-meeting-hint' => '1']),
|
||||
'meetlet-combobox__panel',
|
||||
['data-meeting-panel' => '1', 'hidden' => 'hidden']
|
||||
),
|
||||
'meetlet-combobox' . ($readonly ? ' is-readonly' : ''),
|
||||
[
|
||||
'data-meeting-picker' => '1',
|
||||
'data-courseid' => (int)$this->_course->id,
|
||||
'data-readonly' => $readonly ? '1' : '0',
|
||||
'data-str-loading' => get_string('loadingmeetings', 'meetletacbfree'),
|
||||
'data-str-empty' => get_string('nomeetingsfound', 'meetletacbfree'),
|
||||
'data-str-choose' => get_string('chooseservermeeting', 'meetletacbfree'),
|
||||
]
|
||||
);
|
||||
|
||||
$mform->addElement('static', 'existingscoid_ui', get_string('existingscoid', 'meetletacbfree'), $picker);
|
||||
$mform->hideIf('existingscoid_ui', 'linkmode', 'neq', 1);
|
||||
$mform->addElement('html', html_writer::div('', 'd-none', [
|
||||
'data-meetings-meta' => json_encode($meetingsmeta, JSON_UNESCAPED_UNICODE),
|
||||
]));
|
||||
|
||||
$mform->addElement('text', 'meeturl', get_string('meeturl', 'meetletacbfree'), ['size' => 40]);
|
||||
$mform->setType('meeturl', PARAM_PATH);
|
||||
$mform->addHelpButton('meeturl', 'meeturl', 'meetletacbfree');
|
||||
$mform->hideIf('meeturl', 'linkmode', 'eq', 1);
|
||||
|
||||
$mform->addElement('select', 'meetingpublic', get_string('meetingtype', 'meetletacbfree'), [
|
||||
0 => get_string('private', 'meetletacbfree'),
|
||||
1 => get_string('public', 'meetletacbfree'),
|
||||
]);
|
||||
$mform->setDefault('meetingpublic', 0);
|
||||
|
||||
$langs = config::meeting_lang_options();
|
||||
$mform->addElement('select', 'meetinglang', get_string('meetinglang', 'meetletacbfree'), $langs);
|
||||
$mform->setType('meetinglang', PARAM_ALPHANUMEXT);
|
||||
$mform->addHelpButton('meetinglang', 'meetinglang', 'meetletacbfree');
|
||||
$mform->setDefault('meetinglang', config::default_meeting_lang());
|
||||
|
||||
$waitstate = $this->resolve_toggle_state($current, 'waitforhost', $isnew);
|
||||
$mform->addElement('static', 'waitforhost_state_ui', get_string('waitforhost', 'meetletacbfree'),
|
||||
$this->render_override_chip_group(
|
||||
'waitforhost_state',
|
||||
$waitstate,
|
||||
get_string('waitforhost_help', 'meetletacbfree'),
|
||||
[
|
||||
config::OVERRIDE_INHERIT => get_string(
|
||||
config::default_wait_for_host() ? 'settingstate_inherit_on' : 'settingstate_inherit_off',
|
||||
'meetletacbfree'
|
||||
),
|
||||
]
|
||||
)
|
||||
);
|
||||
$mform->addElement('hidden', 'waitforhost', $waitstate === config::OVERRIDE_ON ? 1 : 0);
|
||||
$mform->setType('waitforhost', PARAM_INT);
|
||||
|
||||
$templates = [0 => get_string('defaulttemplate', 'meetletacbfree')];
|
||||
if (config::is_configured()) {
|
||||
try {
|
||||
$service = new service();
|
||||
$service->login_admin();
|
||||
$templates = $templates + $service->get_templates();
|
||||
$service->logout();
|
||||
} catch (Throwable $e) {
|
||||
// Ignore.
|
||||
}
|
||||
}
|
||||
$mform->addElement('select', 'templatescoid', get_string('meettemplates', 'meetletacbfree'), $templates);
|
||||
$mform->setType('templatescoid', PARAM_INT);
|
||||
$mform->hideIf('templatescoid', 'linkmode', 'eq', 1);
|
||||
|
||||
$mform->addElement('date_time_selector', 'starttime', get_string('starttime', 'meetletacbfree'));
|
||||
$mform->addRule('starttime', null, 'required', null, 'client');
|
||||
$mform->addElement('advcheckbox', 'endtimeenable', get_string('endtimeenable', 'meetletacbfree'));
|
||||
$mform->addElement('date_time_selector', 'endtime', get_string('endtime', 'meetletacbfree'));
|
||||
$mform->disabledIf('endtime', 'endtimeenable');
|
||||
$mform->setDefault('endtime', time() + 2 * HOURSECS);
|
||||
|
||||
// BBB-style participant mapping.
|
||||
$participantlist = roles::get_participant_list($current, $context);
|
||||
$participantselection = roles::get_participant_selection_data();
|
||||
$mform->addElement('header', 'permissions', get_string('rolemapping', 'meetletacbfree'));
|
||||
$mform->setExpanded('permissions');
|
||||
$mform->addElement('static', 'rolemappingdesc', '', get_string('rolemapping_desc', 'meetletacbfree'));
|
||||
$participantsmode = $this->resolve_participants_mode($current, $isnew);
|
||||
$mform->addElement('static', 'participantsmode_ui', get_string('rolemapping_mode', 'meetletacbfree'),
|
||||
$this->render_override_chip_group(
|
||||
'participantsmode_state',
|
||||
$participantsmode,
|
||||
get_string('rolemapping_mode_help', 'meetletacbfree'),
|
||||
[],
|
||||
[
|
||||
roles::MODE_INHERIT => get_string('settingstate_inherit', 'meetletacbfree'),
|
||||
roles::MODE_OVERRIDE => get_string('rolemapping_mode_override_short', 'meetletacbfree'),
|
||||
]
|
||||
)
|
||||
);
|
||||
$mform->addElement('hidden', 'participantsmode', $participantsmode === roles::MODE_OVERRIDE ? 1 : 0);
|
||||
$mform->setType('participantsmode', PARAM_INT);
|
||||
$mform->addElement('hidden', 'participants', json_encode($participantlist));
|
||||
$mform->setType('participants', PARAM_RAW);
|
||||
|
||||
$selectiontype = new single_select(new moodle_url(qualified_me()),
|
||||
'meetlet_participant_selection_type',
|
||||
$participantselection['type_options'],
|
||||
$participantselection['type_selected']);
|
||||
$selectionparticipants = new single_select(new moodle_url(qualified_me()),
|
||||
'meetlet_participant_selection',
|
||||
$participantselection['options'],
|
||||
$participantselection['selected']);
|
||||
$action = new single_button(new moodle_url(qualified_me()),
|
||||
get_string('participant_add', 'meetletacbfree'),
|
||||
'post',
|
||||
single_button::BUTTON_SECONDARY,
|
||||
['name' => 'meetlet_participant_selection_add']
|
||||
);
|
||||
$html = $OUTPUT->render_from_template('mod_meetletacbfree/participant_form', [
|
||||
'selectionType' => $selectiontype->export_for_template($OUTPUT),
|
||||
'selectionParticipant' => $selectionparticipants->export_for_template($OUTPUT),
|
||||
'action' => $action->export_for_template($OUTPUT),
|
||||
]);
|
||||
$mform->addElement('static', 'static_participant_list', get_string('participant_list', 'meetletacbfree'), $html);
|
||||
|
||||
$mform->addElement('html', html_writer::div('', 'd-none', [
|
||||
'data-participant-data' => json_encode(roles::get_participant_data($context, $current ?: (object)['id' => 0])),
|
||||
]));
|
||||
|
||||
if (featureaccess::activity_override_enabled()) {
|
||||
$mform->addElement('header', 'meetletaccess', get_string('featureaccess_activity', 'meetletacbfree'));
|
||||
$mform->addElement('advcheckbox', 'featureaccess_override', get_string('featureaccess_override', 'meetletacbfree'),
|
||||
get_string('featureaccess_override_help', 'meetletacbfree'));
|
||||
$hascustom = $current && !empty($current->featureaccess);
|
||||
$mform->setDefault('featureaccess_override', $hascustom ? 1 : 0);
|
||||
$matrix = featureaccess::matrix_for_activity($current);
|
||||
$mform->addElement('html', featureaccess::render_matrix_html($matrix, 'featureaccess_matrix'));
|
||||
$mform->addElement('hidden', 'featureaccess', '');
|
||||
$mform->setType('featureaccess', PARAM_RAW);
|
||||
}
|
||||
|
||||
if (config::offline_render_active()) {
|
||||
$mform->addElement('header', 'meetletoffline', get_string('offlinesettings', 'meetletacbfree'));
|
||||
$autostate = $this->resolve_toggle_state($current, 'autooffline', $isnew);
|
||||
$mform->addElement('static', 'autooffline_state_ui', get_string('autooffline', 'meetletacbfree'),
|
||||
$this->render_override_chip_group(
|
||||
'autooffline_state',
|
||||
$autostate,
|
||||
get_string('autooffline_help', 'meetletacbfree'),
|
||||
[
|
||||
config::OVERRIDE_INHERIT => get_string(
|
||||
config::auto_offline_default() ? 'settingstate_inherit_on' : 'settingstate_inherit_off',
|
||||
'meetletacbfree'
|
||||
),
|
||||
]
|
||||
)
|
||||
);
|
||||
$mform->addElement('hidden', 'autooffline', $autostate === config::OVERRIDE_ON ? 1 : 0);
|
||||
$mform->setType('autooffline', PARAM_INT);
|
||||
}
|
||||
|
||||
$mform->addElement('header', 'meetletfeatures', get_string('activityfeatures', 'meetletacbfree'));
|
||||
$featurefields = [
|
||||
'zip' => 'showzip',
|
||||
'recordingstab' => 'showrecordingstab',
|
||||
'contenttab' => 'showcontenttab',
|
||||
'mp4' => 'showmp4',
|
||||
'makeoffline' => 'showmakeoffline',
|
||||
];
|
||||
foreach ($featurefields as $feature => $field) {
|
||||
if (!config::feature_enabled($feature)) {
|
||||
continue;
|
||||
}
|
||||
if (!config::feature_allow_override($feature)) {
|
||||
continue;
|
||||
}
|
||||
$state = $this->resolve_toggle_state($current, $field, $isnew);
|
||||
$inheritkey = config::feature_enabled($feature) ? 'settingstate_inherit_on' : 'settingstate_inherit_off';
|
||||
$help = ($field === 'showzip') ? get_string('showzip_help', 'meetletacbfree') : '';
|
||||
$mform->addElement('static', $field . '_state_ui', get_string($field, 'meetletacbfree'),
|
||||
$this->render_override_chip_group(
|
||||
$field . '_state',
|
||||
$state,
|
||||
$help,
|
||||
[config::OVERRIDE_INHERIT => get_string($inheritkey, 'meetletacbfree')]
|
||||
)
|
||||
);
|
||||
$mform->addElement('hidden', $field, $state === config::OVERRIDE_ON ? 1 : 0);
|
||||
$mform->setType($field, PARAM_INT);
|
||||
if ($field === 'showzip') {
|
||||
$mform->removeElement($field . '_state_ui');
|
||||
$mform->addElement('static', $field . '_state_ui', get_string($field, 'meetletacbfree'),
|
||||
$this->render_override_chip_group(
|
||||
$field . '_state',
|
||||
$state,
|
||||
get_string('showzip_help', 'meetletacbfree'),
|
||||
[config::OVERRIDE_INHERIT => get_string($inheritkey, 'meetletacbfree')]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$this->standard_grading_coursemodule_elements();
|
||||
$this->standard_coursemodule_elements();
|
||||
$this->add_action_buttons();
|
||||
}
|
||||
|
||||
public function definition_after_data() {
|
||||
if (!registration_manager::is_registered()) {
|
||||
return;
|
||||
}
|
||||
parent::definition_after_data();
|
||||
}
|
||||
|
||||
public function data_preprocessing(&$defaultvalues) {
|
||||
parent::data_preprocessing($defaultvalues);
|
||||
$defaultvalues['endtimeenable'] = !empty($defaultvalues['endtime']) ? 1 : 0;
|
||||
foreach (\meetletacbfree_activity_override_fields() as $field) {
|
||||
$defaultvalues[$field . '_state'] = $this->resolve_toggle_state((object)$defaultvalues, $field, false);
|
||||
}
|
||||
$defaultvalues['participantsmode'] = $this->resolve_participants_mode((object)$defaultvalues, false);
|
||||
if (!empty($defaultvalues['participants'])) {
|
||||
$rules = json_decode($defaultvalues['participants'], true);
|
||||
if (is_array($rules)) {
|
||||
$defaultvalues['participants'] = json_encode(roles::get_participant_list(
|
||||
(object)$defaultvalues,
|
||||
context_course::instance($this->_course->id)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function validation($data, $files) {
|
||||
$errors = parent::validation($data, $files);
|
||||
if (!registration_manager::is_registered()) {
|
||||
$errors['registrationlock'] = get_string('registration_required', 'meetletacbfree');
|
||||
return $errors;
|
||||
}
|
||||
if (!empty($data['endtimeenable']) && (int)$data['endtime'] <= (int)$data['starttime']) {
|
||||
$errors['endtime'] = get_string('greaterstarttime', 'meetletacbfree');
|
||||
}
|
||||
if (!empty($data['meeturl']) && empty($data['linkmode'])
|
||||
&& !preg_match('/^[a-zA-Z][a-zA-Z0-9\-]*$/', $data['meeturl'])) {
|
||||
$errors['meeturl'] = get_string('invalidurl', 'meetletacbfree');
|
||||
}
|
||||
if (!empty($data['linkmode']) && empty($data['existingscoid'])) {
|
||||
$errors['existingscoid_ui'] = get_string('errorexistingsco', 'meetletacbfree');
|
||||
}
|
||||
return $errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a compact chip-style radio group inside the activity form.
|
||||
*
|
||||
* @param array<int,string> $descriptions
|
||||
* @param array<int,string>|null $labels
|
||||
*/
|
||||
protected function render_override_chip_group(
|
||||
string $name,
|
||||
int $selected,
|
||||
string $help = '',
|
||||
array $descriptions = [],
|
||||
?array $labels = null
|
||||
): string {
|
||||
$labels = $labels ?? config::override_state_options();
|
||||
$chips = '';
|
||||
foreach ($labels as $value => $label) {
|
||||
$id = 'id_' . preg_replace('/[^a-zA-Z0-9_-]/', '_', $name . '_' . $value);
|
||||
$description = $descriptions[$value] ?? '';
|
||||
$attrs = [
|
||||
'type' => 'radio',
|
||||
'name' => $name,
|
||||
'id' => $id,
|
||||
'value' => (int)$value,
|
||||
'class' => 'meetlet-rolechip__input meetlet-settingchip__input',
|
||||
'data-meetlet-setting-radio' => '1',
|
||||
];
|
||||
if ((int)$selected === (int)$value) {
|
||||
$attrs['checked'] = 'checked';
|
||||
}
|
||||
if ($description !== '') {
|
||||
$attrs['aria-label'] = $label . ' - ' . $description;
|
||||
}
|
||||
$body = \html_writer::empty_tag('input', $attrs)
|
||||
. \html_writer::span(
|
||||
\html_writer::span(s($label), 'meetlet-rolechip__text')
|
||||
. ($description !== ''
|
||||
? \html_writer::span(s($description), 'meetlet-settingchip__meta', ['aria-hidden' => 'true'])
|
||||
. \html_writer::span(s($description), 'meetlet-sr-only')
|
||||
: ''),
|
||||
'meetlet-settingchip__content'
|
||||
);
|
||||
$chips .= \html_writer::tag('label', $body, [
|
||||
'class' => 'meetlet-rolechip meetlet-settingchip',
|
||||
'for' => $id,
|
||||
]);
|
||||
}
|
||||
$groupclasses = 'meetlet-rolechip-group meetlet-settingchip-group';
|
||||
if (count($labels) === 2) {
|
||||
$groupclasses .= ' meetlet-settingchip-group--binary';
|
||||
}
|
||||
$html = \html_writer::div($chips, $groupclasses, [
|
||||
'role' => 'radiogroup',
|
||||
]);
|
||||
if ($help !== '') {
|
||||
$html .= \html_writer::div(s($help), 'meetlet-settinghint');
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
|
||||
protected function resolve_toggle_state(?object $current, string $field, bool $isnew): int {
|
||||
if ($isnew || $current === null) {
|
||||
return config::OVERRIDE_INHERIT;
|
||||
}
|
||||
if (property_exists($current, 'settingoverrides') || property_exists($current, 'participantsmode')) {
|
||||
return config::activity_override_state($current, $field);
|
||||
}
|
||||
return !empty($current->{$field}) ? config::OVERRIDE_ON : config::OVERRIDE_OFF;
|
||||
}
|
||||
|
||||
protected function resolve_participants_mode(?object $current, bool $isnew): int {
|
||||
if ($isnew || $current === null) {
|
||||
return roles::MODE_INHERIT;
|
||||
}
|
||||
if (property_exists($current, 'participantsmode')) {
|
||||
return roles::activity_inherits_defaults($current) ? roles::MODE_INHERIT : roles::MODE_OVERRIDE;
|
||||
}
|
||||
return roles::MODE_OVERRIDE;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user