Adobe Connect Bridge Marketplace edition — initial release
This commit is contained in:
+97
@@ -0,0 +1,97 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Ajax: SSO URL to view uploaded meeting content in Adobe Connect.
|
||||
*
|
||||
* @package mod_meetletacbmarketplace
|
||||
* @copyright 2026 MeetLet <info@meetlet.net>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_meetletacbmarketplace\external;
|
||||
|
||||
use core_external\external_api;
|
||||
use core_external\external_function_parameters;
|
||||
use core_external\external_single_structure;
|
||||
use core_external\external_value;
|
||||
use mod_meetletacbmarketplace\api\service;
|
||||
use mod_meetletacbmarketplace\local\config;
|
||||
use mod_meetletacbmarketplace\local\content;
|
||||
use mod_meetletacbmarketplace\local\featureaccess;
|
||||
use mod_meetletacbmarketplace\local\instance;
|
||||
use mod_meetletacbmarketplace\local\roles;
|
||||
use mod_meetletacbmarketplace\local\sso;
|
||||
use mod_meetletacbmarketplace\local\usererror;
|
||||
use moodle_exception;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Prepare Adobe Connect content view URL with session for current user.
|
||||
*/
|
||||
class get_content_launch_url extends external_api {
|
||||
public static function execute_parameters(): external_function_parameters {
|
||||
return new external_function_parameters([
|
||||
'cmid' => new external_value(PARAM_INT, 'Course module id'),
|
||||
'contentscoid' => new external_value(PARAM_INT, 'Content sco id'),
|
||||
'groupid' => new external_value(PARAM_INT, 'Group id', VALUE_DEFAULT, 0),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function execute(int $cmid, int $contentscoid, int $groupid = 0): array {
|
||||
global $USER;
|
||||
|
||||
$params = self::validate_parameters(self::execute_parameters(), [
|
||||
'cmid' => $cmid,
|
||||
'contentscoid' => $contentscoid,
|
||||
'groupid' => $groupid,
|
||||
]);
|
||||
|
||||
\mod_meetletacbmarketplace\registration\manager::require_registered();
|
||||
|
||||
$inst = instance::get_from_cmid($params['cmid'], $params['groupid']);
|
||||
self::validate_context($inst->context);
|
||||
require_capability('mod/meetletacbmarketplace:view', $inst->context);
|
||||
featureaccess::require_feature(featureaccess::CONTENT, $inst->context, $inst->record);
|
||||
|
||||
$service = new service();
|
||||
$service->login_admin();
|
||||
$item = content::find_in_meeting($service, $inst->require_meeting(), (int)$params['contentscoid']);
|
||||
$service->logout();
|
||||
|
||||
if (!$item || empty($item->urlpath)) {
|
||||
throw new moodle_exception('nocontent', 'meetletacbmarketplace');
|
||||
}
|
||||
|
||||
try {
|
||||
$adoberole = roles::resolve_adobe_role($inst->record, $inst->context, $USER->id);
|
||||
$permission = $adoberole ?: service::ROLE_PARTICIPANT;
|
||||
$auth = sso::provision_and_login($USER, (int)$params['contentscoid'], $permission);
|
||||
$url = config::content_view_url((string)$item->urlpath, $auth['cookie']);
|
||||
return usererror::ws_ok($url);
|
||||
} catch (\Throwable $e) {
|
||||
if ($e instanceof moodle_exception && in_array($e->errorcode, ['nopermissions', 'requirelogin', 'invalidparameter'], true)) {
|
||||
throw $e;
|
||||
}
|
||||
return usererror::ws_fail($e, $inst->context);
|
||||
}
|
||||
}
|
||||
|
||||
public static function execute_returns(): external_single_structure {
|
||||
return new external_single_structure(usererror::ws_url_returns());
|
||||
}
|
||||
}
|
||||
Vendored
+126
@@ -0,0 +1,126 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Ajax: build SSO join URL.
|
||||
*
|
||||
* @package mod_meetletacbmarketplace
|
||||
* @copyright 2026 MeetLet
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_meetletacbmarketplace\external;
|
||||
|
||||
use core_external\external_api;
|
||||
use core_external\external_function_parameters;
|
||||
use core_external\external_single_structure;
|
||||
use core_external\external_value;
|
||||
use mod_meetletacbmarketplace\api\service;
|
||||
use mod_meetletacbmarketplace\local\config;
|
||||
use mod_meetletacbmarketplace\local\instance;
|
||||
use mod_meetletacbmarketplace\local\roles;
|
||||
use mod_meetletacbmarketplace\local\sso;
|
||||
use mod_meetletacbmarketplace\local\usererror;
|
||||
use moodle_exception;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Prepare Adobe Connect join URL for current user.
|
||||
*/
|
||||
class get_join_url extends external_api {
|
||||
public static function execute_parameters(): external_function_parameters {
|
||||
return new external_function_parameters([
|
||||
'cmid' => new external_value(PARAM_INT, 'Course module id'),
|
||||
'groupid' => new external_value(PARAM_INT, 'Group id', VALUE_DEFAULT, 0),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function execute(int $cmid, int $groupid = 0): array {
|
||||
global $USER;
|
||||
|
||||
$params = self::validate_parameters(self::execute_parameters(), [
|
||||
'cmid' => $cmid,
|
||||
'groupid' => $groupid,
|
||||
]);
|
||||
|
||||
\mod_meetletacbmarketplace\registration\manager::require_registered();
|
||||
|
||||
$inst = instance::get_from_cmid($params['cmid'], $params['groupid']);
|
||||
self::validate_context($inst->context);
|
||||
require_capability('mod/meetletacbmarketplace:view', $inst->context);
|
||||
if (!\mod_meetletacbmarketplace\local\featureaccess::allowed(
|
||||
\mod_meetletacbmarketplace\local\featureaccess::JOIN,
|
||||
$inst->context,
|
||||
$inst->record
|
||||
)) {
|
||||
throw new moodle_exception('notparticipant', 'meetletacbmarketplace');
|
||||
}
|
||||
|
||||
if ($inst->cm->groupmode != NOGROUPS) {
|
||||
$usrgroups = groups_get_user_groups($inst->course->id, $USER->id);
|
||||
$usrgroups = $usrgroups[0] ?? [];
|
||||
$aag = has_capability('moodle/site:accessallgroups', $inst->context);
|
||||
if (!$aag && $params['groupid'] && !in_array($params['groupid'], $usrgroups, false)) {
|
||||
throw new moodle_exception('usergrouprequired', 'meetletacbmarketplace');
|
||||
}
|
||||
}
|
||||
|
||||
$adoberole = roles::resolve_adobe_role($inst->record, $inst->context, $USER->id);
|
||||
if ($adoberole === null) {
|
||||
throw new moodle_exception('notparticipant', 'meetletacbmarketplace');
|
||||
}
|
||||
|
||||
try {
|
||||
// Gate join before any Adobe user provisioning / SSO.
|
||||
$lifecycle = \mod_meetletacbmarketplace\local\meetingstatus::resolve($inst, (int)$USER->id, true);
|
||||
if (empty($lifecycle['joinable'])) {
|
||||
$code = !empty($lifecycle['errorcode']) ? $lifecycle['errorcode'] : 'meetingended';
|
||||
throw new moodle_exception($code, 'meetletacbmarketplace');
|
||||
}
|
||||
|
||||
$scoid = $inst->require_meeting();
|
||||
$service = new service();
|
||||
$service->login_admin();
|
||||
$info = $service->meeting_info($scoid);
|
||||
$service->logout();
|
||||
if (!$info) {
|
||||
throw new moodle_exception('nomeeting', 'meetletacbmarketplace');
|
||||
}
|
||||
|
||||
$auth = sso::provision_and_login($USER, $scoid, $adoberole);
|
||||
$url = config::with_html_client(config::public_url($info->url, $auth['cookie']));
|
||||
|
||||
$event = \mod_meetletacbmarketplace\event\meeting_joined::create([
|
||||
'context' => $inst->context,
|
||||
'objectid' => $inst->record->id,
|
||||
'relateduserid' => $USER->id,
|
||||
]);
|
||||
$event->trigger();
|
||||
|
||||
return usererror::ws_ok($url);
|
||||
} catch (\Throwable $e) {
|
||||
if ($e instanceof moodle_exception && in_array($e->errorcode, ['nopermissions', 'requirelogin', 'invalidparameter'], true)) {
|
||||
throw $e;
|
||||
}
|
||||
return usererror::ws_fail($e, $inst->context);
|
||||
}
|
||||
}
|
||||
|
||||
public static function execute_returns(): external_single_structure {
|
||||
return new external_single_structure(usererror::ws_url_returns());
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Ajax: poll meeting join status.
|
||||
*
|
||||
* @package mod_meetletacbmarketplace
|
||||
* @copyright 2026 MeetLet
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_meetletacbmarketplace\external;
|
||||
|
||||
use core_external\external_api;
|
||||
use core_external\external_function_parameters;
|
||||
use core_external\external_single_structure;
|
||||
use core_external\external_value;
|
||||
use mod_meetletacbmarketplace\local\featureaccess;
|
||||
use mod_meetletacbmarketplace\local\instance;
|
||||
use mod_meetletacbmarketplace\local\meetingstatus;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Lightweight status poll for the activity hero badge / join gate.
|
||||
*/
|
||||
class get_meeting_status extends external_api {
|
||||
public static function execute_parameters(): external_function_parameters {
|
||||
return new external_function_parameters([
|
||||
'cmid' => new external_value(PARAM_INT, 'Course module id'),
|
||||
'groupid' => new external_value(PARAM_INT, 'Group id', VALUE_DEFAULT, 0),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function execute(int $cmid, int $groupid = 0): array {
|
||||
global $USER;
|
||||
|
||||
$params = self::validate_parameters(self::execute_parameters(), [
|
||||
'cmid' => $cmid,
|
||||
'groupid' => $groupid,
|
||||
]);
|
||||
|
||||
\mod_meetletacbmarketplace\registration\manager::require_registered();
|
||||
|
||||
$inst = instance::get_from_cmid($params['cmid'], $params['groupid']);
|
||||
self::validate_context($inst->context);
|
||||
require_capability('mod/meetletacbmarketplace:view', $inst->context);
|
||||
|
||||
$info = meetingstatus::resolve($inst, (int)$USER->id, true);
|
||||
$featurejoin = featureaccess::allowed(featureaccess::JOIN, $inst->context, $inst->record);
|
||||
$joinable = $featurejoin && !empty($info['joinable']);
|
||||
|
||||
return [
|
||||
'status' => (string)$info['status'],
|
||||
'statuslabel' => (string)$info['statuslabel'],
|
||||
'joinable' => (bool)$joinable,
|
||||
'message' => (string)$info['message'],
|
||||
'errorcode' => (string)$info['errorcode'],
|
||||
'servertime' => time(),
|
||||
];
|
||||
}
|
||||
|
||||
public static function execute_returns(): external_single_structure {
|
||||
return new external_single_structure([
|
||||
'status' => new external_value(PARAM_ALPHANUMEXT, 'Status code'),
|
||||
'statuslabel' => new external_value(PARAM_TEXT, 'Localized status label'),
|
||||
'joinable' => new external_value(PARAM_BOOL, 'Whether join may be requested'),
|
||||
'message' => new external_value(PARAM_TEXT, 'User-facing gate message'),
|
||||
'errorcode' => new external_value(PARAM_ALPHANUMEXT, 'Exception string id when not joinable'),
|
||||
'servertime' => new external_value(PARAM_INT, 'Server unix time'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Ajax: SSO URL to play or edit a recording in Adobe Connect.
|
||||
*
|
||||
* @package mod_meetletacbmarketplace
|
||||
* @copyright 2026 MeetLet <info@meetlet.net>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_meetletacbmarketplace\external;
|
||||
|
||||
use core_external\external_api;
|
||||
use core_external\external_function_parameters;
|
||||
use core_external\external_single_structure;
|
||||
use core_external\external_value;
|
||||
use mod_meetletacbmarketplace\api\service;
|
||||
use mod_meetletacbmarketplace\local\config;
|
||||
use mod_meetletacbmarketplace\local\featureaccess;
|
||||
use mod_meetletacbmarketplace\local\instance;
|
||||
use mod_meetletacbmarketplace\local\roles;
|
||||
use mod_meetletacbmarketplace\local\sso;
|
||||
use mod_meetletacbmarketplace\local\usererror;
|
||||
use moodle_exception;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Prepare Adobe Connect recording play/edit URL with session for current user.
|
||||
*/
|
||||
class get_recording_launch_url extends external_api {
|
||||
public static function execute_parameters(): external_function_parameters {
|
||||
return new external_function_parameters([
|
||||
'cmid' => new external_value(PARAM_INT, 'Course module id'),
|
||||
'recordingscoid' => new external_value(PARAM_INT, 'Recording sco id'),
|
||||
'mode' => new external_value(PARAM_ALPHA, 'play or edit'),
|
||||
'groupid' => new external_value(PARAM_INT, 'Group id', VALUE_DEFAULT, 0),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function execute(int $cmid, int $recordingscoid, string $mode, int $groupid = 0): array {
|
||||
global $DB, $USER;
|
||||
|
||||
$params = self::validate_parameters(self::execute_parameters(), [
|
||||
'cmid' => $cmid,
|
||||
'recordingscoid' => $recordingscoid,
|
||||
'mode' => $mode,
|
||||
'groupid' => $groupid,
|
||||
]);
|
||||
|
||||
$mode = strtolower($params['mode']);
|
||||
if (!in_array($mode, ['play', 'edit'], true)) {
|
||||
throw new moodle_exception('error', 'meetletacbmarketplace');
|
||||
}
|
||||
|
||||
\mod_meetletacbmarketplace\registration\manager::require_registered();
|
||||
|
||||
$inst = instance::get_from_cmid($params['cmid'], $params['groupid']);
|
||||
self::validate_context($inst->context);
|
||||
require_capability('mod/meetletacbmarketplace:view', $inst->context);
|
||||
featureaccess::require_feature(featureaccess::RECORDINGS, $inst->context, $inst->record);
|
||||
|
||||
if ($mode === 'edit') {
|
||||
featureaccess::require_feature(featureaccess::ADOBEEDITOR, $inst->context, $inst->record);
|
||||
}
|
||||
|
||||
$record = $DB->get_record('meetlet_marketplace_recordings', [
|
||||
'instanceid' => $inst->record->id,
|
||||
'recordingscoid' => $params['recordingscoid'],
|
||||
]);
|
||||
if (!$record || empty($record->urlpath)) {
|
||||
throw new moodle_exception('norecordings', 'meetletacbmarketplace');
|
||||
}
|
||||
|
||||
try {
|
||||
// Prefer meeting role when available; fall back to view for play / host for edit.
|
||||
$adoberole = roles::resolve_adobe_role($inst->record, $inst->context, $USER->id);
|
||||
if ($mode === 'edit') {
|
||||
$permission = ($adoberole === service::ROLE_HOST || $adoberole === service::ROLE_PRESENTER)
|
||||
? $adoberole
|
||||
: service::ROLE_HOST;
|
||||
} else {
|
||||
$permission = $adoberole ?: service::ROLE_PARTICIPANT;
|
||||
}
|
||||
|
||||
$auth = sso::provision_and_login($USER, (int)$params['recordingscoid'], $permission);
|
||||
$url = $mode === 'edit'
|
||||
? config::recording_editor_url((string)$record->urlpath, $auth['cookie'])
|
||||
: config::recording_play_url((string)$record->urlpath, $auth['cookie']);
|
||||
|
||||
return usererror::ws_ok($url);
|
||||
} catch (\Throwable $e) {
|
||||
if ($e instanceof moodle_exception && in_array($e->errorcode, ['nopermissions', 'requirelogin', 'invalidparameter'], true)) {
|
||||
throw $e;
|
||||
}
|
||||
return usererror::ws_fail($e, $inst->context);
|
||||
}
|
||||
}
|
||||
|
||||
public static function execute_returns(): external_single_structure {
|
||||
return new external_single_structure(usererror::ws_url_returns());
|
||||
}
|
||||
}
|
||||
Vendored
+346
@@ -0,0 +1,346 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Ajax: load tab HTML/data.
|
||||
*
|
||||
* @package mod_meetletacbmarketplace
|
||||
* @copyright 2026 MeetLet
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_meetletacbmarketplace\external;
|
||||
|
||||
use core_external\external_api;
|
||||
use core_external\external_function_parameters;
|
||||
use core_external\external_single_structure;
|
||||
use core_external\external_value;
|
||||
use mod_meetletacbmarketplace\api\service;
|
||||
use mod_meetletacbmarketplace\local\config;
|
||||
use mod_meetletacbmarketplace\local\content;
|
||||
use mod_meetletacbmarketplace\local\featureaccess;
|
||||
use mod_meetletacbmarketplace\local\icons;
|
||||
use mod_meetletacbmarketplace\local\instance;
|
||||
use mod_meetletacbmarketplace\local\recordings;
|
||||
use moodle_exception;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Return tab payload for the activity UI.
|
||||
*/
|
||||
class get_tab extends external_api {
|
||||
public static function execute_parameters(): external_function_parameters {
|
||||
return new external_function_parameters([
|
||||
'cmid' => new external_value(PARAM_INT, 'Course module id'),
|
||||
'tab' => new external_value(PARAM_ALPHA, 'Tab name'),
|
||||
'groupid' => new external_value(PARAM_INT, 'Group id', VALUE_DEFAULT, 0),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function execute(int $cmid, string $tab, int $groupid = 0): array {
|
||||
global $PAGE;
|
||||
|
||||
$params = self::validate_parameters(self::execute_parameters(), [
|
||||
'cmid' => $cmid,
|
||||
'tab' => $tab,
|
||||
'groupid' => $groupid,
|
||||
]);
|
||||
|
||||
\mod_meetletacbmarketplace\registration\manager::require_registered();
|
||||
|
||||
$inst = instance::get_from_cmid($params['cmid'], $params['groupid']);
|
||||
self::validate_context($inst->context);
|
||||
require_capability('mod/meetletacbmarketplace:view', $inst->context);
|
||||
$PAGE->set_context($inst->context);
|
||||
|
||||
if (!config::is_configured()) {
|
||||
return [
|
||||
'html' => \html_writer::div(get_string('notconfigured', 'meetletacbmarketplace'), 'meetlet-alert'),
|
||||
'tab' => $params['tab'],
|
||||
];
|
||||
}
|
||||
|
||||
$html = '';
|
||||
switch ($params['tab']) {
|
||||
case 'details':
|
||||
// Served locally in the activity page; keep endpoint defensive.
|
||||
$html = \html_writer::div(get_string('emptydetails', 'meetletacbmarketplace'), 'meetlet-empty');
|
||||
break;
|
||||
case 'info':
|
||||
// Legacy tab id — no Adobe call.
|
||||
$html = \html_writer::div(get_string('emptydetails', 'meetletacbmarketplace'), 'meetlet-empty');
|
||||
break;
|
||||
case 'recordings':
|
||||
featureaccess::require_feature(featureaccess::RECORDINGS, $inst->context, $inst->record);
|
||||
$html = self::render_recordings($inst);
|
||||
break;
|
||||
case 'content':
|
||||
featureaccess::require_feature(featureaccess::CONTENT, $inst->context, $inst->record);
|
||||
$html = self::render_content($inst);
|
||||
break;
|
||||
default:
|
||||
throw new moodle_exception('error', 'meetletacbmarketplace');
|
||||
}
|
||||
|
||||
return ['html' => $html, 'tab' => $params['tab']];
|
||||
}
|
||||
|
||||
public static function execute_returns(): external_single_structure {
|
||||
return new external_single_structure([
|
||||
'html' => new external_value(PARAM_RAW, 'Tab HTML'),
|
||||
'tab' => new external_value(PARAM_ALPHA, 'Tab id'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toolbar with refresh for cacheable Adobe tabs.
|
||||
*/
|
||||
protected static function wrap_refreshable(string $html, string $tab, bool $pending = false): string {
|
||||
$refreshlabel = get_string('refreshtab', 'meetletacbmarketplace');
|
||||
$refreshcontent = icons::svg('refresh')
|
||||
. \html_writer::span($refreshlabel, 'meetlet-btn-with-icon__label');
|
||||
$bits = [
|
||||
\html_writer::tag('button', $refreshcontent, [
|
||||
'type' => 'button',
|
||||
'class' => 'meetlet-btn meetlet-btn--ghost meetlet-btn--sm meetlet-btn-with-icon meetlet-tabtoolbar__refresh',
|
||||
'data-action' => 'refresh-tab',
|
||||
'data-tab' => $tab,
|
||||
'title' => $refreshlabel,
|
||||
'aria-label' => $refreshlabel,
|
||||
]),
|
||||
];
|
||||
if ($pending) {
|
||||
$bits[] = \html_writer::div(
|
||||
\html_writer::span(get_string('jobpollwait', 'meetletacbmarketplace'), 'meetlet-jobpoll__label') .
|
||||
\html_writer::div(\html_writer::div('', 'meetlet-jobpoll__bar'), 'meetlet-jobpoll__track'),
|
||||
'meetlet-jobpoll',
|
||||
['aria-live' => 'polite']
|
||||
);
|
||||
}
|
||||
$bar = \html_writer::div(implode('', $bits), 'meetlet-tabtoolbar');
|
||||
$attrs = ['data-meetlet-tab' => $tab];
|
||||
if ($pending) {
|
||||
$attrs['data-meetlet-jobs-pending'] = '1';
|
||||
$attrs['data-meetlet-jobs-poll-ms'] = '60000';
|
||||
}
|
||||
return \html_writer::div($bar . $html, 'meetlet-tabbody', $attrs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toolbar control with icon and visible label (matches refresh button styling).
|
||||
*
|
||||
* @param array $attrs Extra attributes (href makes it a link).
|
||||
*/
|
||||
protected static function labeled_action(string $icon, string $label, array $attrs = []): string {
|
||||
$extraclass = $attrs['class'] ?? '';
|
||||
unset($attrs['class']);
|
||||
$classes = trim(
|
||||
'meetlet-btn meetlet-btn--ghost meetlet-btn--sm meetlet-btn-with-icon ' . $extraclass
|
||||
);
|
||||
$attrs['class'] = $classes;
|
||||
$attrs['title'] = $label;
|
||||
$attrs['aria-label'] = $label;
|
||||
$content = icons::svg($icon)
|
||||
. \html_writer::span($label, 'meetlet-btn-with-icon__label');
|
||||
if (!empty($attrs['href'])) {
|
||||
$url = $attrs['href'];
|
||||
unset($attrs['href']);
|
||||
$attrs['target'] = $attrs['target'] ?? '_blank';
|
||||
if (($attrs['target'] ?? '') === '_self') {
|
||||
unset($attrs['rel']);
|
||||
} else {
|
||||
$attrs['rel'] = $attrs['rel'] ?? 'noopener';
|
||||
}
|
||||
return \html_writer::link($url, $content, $attrs);
|
||||
}
|
||||
$attrs['type'] = $attrs['type'] ?? 'button';
|
||||
return \html_writer::tag('button', $content, $attrs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Icon control with hover tip.
|
||||
*
|
||||
* @param array $attrs Extra attributes (href makes it a link).
|
||||
*/
|
||||
protected static function icon_action(string $icon, string $label, array $attrs = []): string {
|
||||
$classes = trim('meetlet-iconbtn ' . ($attrs['class'] ?? ''));
|
||||
unset($attrs['class']);
|
||||
$attrs['class'] = $classes;
|
||||
$attrs['data-tip'] = $label;
|
||||
$attrs['aria-label'] = $label;
|
||||
$content = icons::svg($icon) . \html_writer::span($label, 'meetlet-sr-only');
|
||||
if (!empty($attrs['href'])) {
|
||||
$url = $attrs['href'];
|
||||
unset($attrs['href']);
|
||||
$attrs['target'] = $attrs['target'] ?? '_blank';
|
||||
if (($attrs['target'] ?? '') === '_self') {
|
||||
unset($attrs['rel']);
|
||||
} else {
|
||||
$attrs['rel'] = $attrs['rel'] ?? 'noopener';
|
||||
}
|
||||
return \html_writer::link($url, $content, $attrs);
|
||||
}
|
||||
$attrs['type'] = $attrs['type'] ?? 'button';
|
||||
return \html_writer::tag('button', $content, $attrs);
|
||||
}
|
||||
|
||||
protected static function format_jobstatus(string $status, bool $mp4ready = false): string {
|
||||
return recordings::jobstatus_label($status, $mp4ready);
|
||||
}
|
||||
|
||||
protected static function render_recordings(instance $inst): string {
|
||||
$rows = recordings::for_tab($inst);
|
||||
if (empty($rows)) {
|
||||
return self::wrap_refreshable(
|
||||
\html_writer::div(get_string('norecordings', 'meetletacbmarketplace'), 'meetlet-empty'),
|
||||
'recordings'
|
||||
);
|
||||
}
|
||||
$canedit = featureaccess::allowed(featureaccess::EDITRECORDING, $inst->context, $inst->record);
|
||||
$canadobeedit = featureaccess::allowed(featureaccess::ADOBEEDITOR, $inst->context, $inst->record);
|
||||
$table = new \html_table();
|
||||
$table->attributes['class'] = 'generaltable meetlet-table meetlet-table--recordings';
|
||||
$table->head = [
|
||||
get_string('recordingname', 'meetletacbmarketplace'),
|
||||
get_string('recordingdate', 'meetletacbmarketplace'),
|
||||
get_string('duration', 'meetletacbmarketplace'),
|
||||
get_string('sessiondetails', 'meetletacbmarketplace'),
|
||||
];
|
||||
foreach ($rows as $r) {
|
||||
$actions = [];
|
||||
$actions[] = self::icon_action('play', get_string('play', 'meetletacbmarketplace'), [
|
||||
'class' => 'meetlet-iconbtn--play',
|
||||
'data-action' => 'launch-recording',
|
||||
'data-mode' => 'play',
|
||||
'data-recording' => $r->recordingscoid,
|
||||
]);
|
||||
if ($canadobeedit && !empty($r->urlpath)) {
|
||||
$actions[] = self::icon_action('adobeedit', get_string('adobeeditor', 'meetletacbmarketplace'), [
|
||||
'class' => 'meetlet-iconbtn--adobeedit',
|
||||
'data-action' => 'launch-recording',
|
||||
'data-mode' => 'edit',
|
||||
'data-recording' => $r->recordingscoid,
|
||||
]);
|
||||
}
|
||||
$namehtml = self::render_recording_name((string)$r->name, (int)$r->recordingscoid, $canedit);
|
||||
$dur = $r->duration ? gmdate('H:i:s', (int)$r->duration) : '-';
|
||||
$recdate = !empty($r->timestart) ? userdate((int)$r->timestart) : '—';
|
||||
$table->data[] = [
|
||||
$namehtml,
|
||||
$recdate,
|
||||
$dur,
|
||||
\html_writer::div(implode('', $actions), 'meetlet-iconactions'),
|
||||
];
|
||||
}
|
||||
return self::wrap_refreshable(\html_writer::div(\html_writer::table($table), 'meetlet-card'), 'recordings', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recording title with optional inline rename control.
|
||||
*/
|
||||
protected static function render_recording_name(string $name, int $recordingscoid, bool $canedit): string {
|
||||
$text = \html_writer::span(format_string($name), 'meetlet-recname__text');
|
||||
if (!$canedit) {
|
||||
return \html_writer::div($text, 'meetlet-recname');
|
||||
}
|
||||
$pencil = self::icon_action('edit', get_string('editrecording', 'meetletacbmarketplace'), [
|
||||
'class' => 'meetlet-iconbtn--edit meetlet-iconbtn--inline',
|
||||
'data-action' => 'inline-rename',
|
||||
]);
|
||||
$input = \html_writer::empty_tag('input', [
|
||||
'type' => 'text',
|
||||
'class' => 'meetlet-recname__input',
|
||||
'value' => $name,
|
||||
'maxlength' => 255,
|
||||
'hidden' => 'hidden',
|
||||
]);
|
||||
return \html_writer::div($text . $pencil . $input, 'meetlet-recname', [
|
||||
'data-recording' => $recordingscoid,
|
||||
'data-name' => $name,
|
||||
]);
|
||||
}
|
||||
|
||||
protected static function render_content(instance $inst): string {
|
||||
global $USER;
|
||||
|
||||
$service = new service();
|
||||
$service->login_admin();
|
||||
$items = $service->list_meeting_content($inst->require_meeting());
|
||||
$service->logout();
|
||||
if (empty($items)) {
|
||||
return self::wrap_refreshable(
|
||||
\html_writer::div(get_string('nocontent', 'meetletacbmarketplace'), 'meetlet-empty'),
|
||||
'content'
|
||||
);
|
||||
}
|
||||
$table = new \html_table();
|
||||
$table->attributes['class'] = 'generaltable meetlet-table meetlet-table--content';
|
||||
$table->head = [
|
||||
get_string('name'),
|
||||
get_string('contenttype', 'meetletacbmarketplace'),
|
||||
get_string('contentsize', 'meetletacbmarketplace'),
|
||||
get_string('contentdate', 'meetletacbmarketplace'),
|
||||
get_string('sessiondetails', 'meetletacbmarketplace'),
|
||||
];
|
||||
foreach ($items as $item) {
|
||||
$scoid = (int)($item->scoid ?? 0);
|
||||
$actions = [
|
||||
self::icon_action('play', get_string('view'), [
|
||||
'class' => 'meetlet-iconbtn--play',
|
||||
'data-action' => 'launch-content',
|
||||
'data-content' => $scoid,
|
||||
]),
|
||||
];
|
||||
if ($scoid > 0 && !empty($item->name)) {
|
||||
$dlurl = content::download_url((int)$USER->id, (int)$inst->cm->id, $scoid)->out(false);
|
||||
$actions[] = self::icon_action('download', get_string('downloadcontent', 'meetletacbmarketplace'), [
|
||||
'href' => $dlurl,
|
||||
'class' => 'meetlet-iconbtn--download',
|
||||
'target' => '_self',
|
||||
'download' => $item->name,
|
||||
'data-action' => 'download-content',
|
||||
]);
|
||||
}
|
||||
$table->data[] = [
|
||||
format_string($item->name),
|
||||
s($item->typelabel ?? service::content_type_label(
|
||||
(string)($item->icon ?? ''),
|
||||
(string)($item->type ?? ''),
|
||||
(string)($item->name ?? '')
|
||||
)),
|
||||
self::format_bytes((int)($item->bytesize ?? 0)),
|
||||
s($item->datecreated_fmt !== '' ? $item->datecreated_fmt : '—'),
|
||||
\html_writer::div(implode('', $actions), 'meetlet-iconactions'),
|
||||
];
|
||||
}
|
||||
return self::wrap_refreshable(\html_writer::div(\html_writer::table($table), 'meetlet-card'), 'content');
|
||||
}
|
||||
|
||||
protected static function format_bytes(int $bytes): string {
|
||||
if ($bytes <= 0) {
|
||||
return '—';
|
||||
}
|
||||
$units = ['B', 'KB', 'MB', 'GB'];
|
||||
$i = 0;
|
||||
$n = (float)$bytes;
|
||||
while ($n >= 1024 && $i < count($units) - 1) {
|
||||
$n /= 1024;
|
||||
$i++;
|
||||
}
|
||||
return format_float($n, $i === 0 ? 0 : 1) . ' ' . $units[$i];
|
||||
}
|
||||
}
|
||||
+108
@@ -0,0 +1,108 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Ajax: rename a recording SCO.
|
||||
*
|
||||
* @package mod_meetletacbmarketplace
|
||||
* @copyright 2026 MeetLet
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_meetletacbmarketplace\external;
|
||||
|
||||
use core_external\external_api;
|
||||
use core_external\external_function_parameters;
|
||||
use core_external\external_single_structure;
|
||||
use core_external\external_value;
|
||||
use mod_meetletacbmarketplace\api\service;
|
||||
use mod_meetletacbmarketplace\local\instance;
|
||||
use moodle_exception;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Rename Adobe Connect recording.
|
||||
*/
|
||||
class rename_recording extends external_api {
|
||||
public static function execute_parameters(): external_function_parameters {
|
||||
return new external_function_parameters([
|
||||
'cmid' => new external_value(PARAM_INT, 'Course module id'),
|
||||
'recordingscoid' => new external_value(PARAM_INT, 'Recording sco id'),
|
||||
'name' => new external_value(PARAM_TEXT, 'New recording name'),
|
||||
'groupid' => new external_value(PARAM_INT, 'Group id', VALUE_DEFAULT, 0),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function execute(int $cmid, int $recordingscoid, string $name, int $groupid = 0): array {
|
||||
global $DB;
|
||||
|
||||
$params = self::validate_parameters(self::execute_parameters(), [
|
||||
'cmid' => $cmid,
|
||||
'recordingscoid' => $recordingscoid,
|
||||
'name' => $name,
|
||||
'groupid' => $groupid,
|
||||
]);
|
||||
|
||||
\mod_meetletacbmarketplace\registration\manager::require_registered();
|
||||
|
||||
$inst = instance::get_from_cmid($params['cmid'], $params['groupid']);
|
||||
self::validate_context($inst->context);
|
||||
require_capability('mod/meetletacbmarketplace:view', $inst->context);
|
||||
\mod_meetletacbmarketplace\local\featureaccess::require_feature(
|
||||
\mod_meetletacbmarketplace\local\featureaccess::EDITRECORDING,
|
||||
$inst->context,
|
||||
$inst->record
|
||||
);
|
||||
|
||||
$name = trim($params['name']);
|
||||
if ($name === '') {
|
||||
throw new moodle_exception('error', 'meetletacbmarketplace');
|
||||
}
|
||||
|
||||
$service = new service();
|
||||
$service->login_admin();
|
||||
$ok = $service->rename_sco((int)$params['recordingscoid'], $name);
|
||||
$service->logout();
|
||||
if (!$ok) {
|
||||
throw new moodle_exception('errorrenamerecording', 'meetletacbmarketplace');
|
||||
}
|
||||
|
||||
$record = $DB->get_record('meetlet_marketplace_recordings', [
|
||||
'instanceid' => $inst->record->id,
|
||||
'recordingscoid' => (int)$params['recordingscoid'],
|
||||
]);
|
||||
if ($record) {
|
||||
$record->name = $name;
|
||||
$record->timemodified = time();
|
||||
$DB->update_record('meetlet_marketplace_recordings', $record);
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'name' => $name,
|
||||
'message' => get_string('recordingrenamed', 'meetletacbmarketplace'),
|
||||
];
|
||||
}
|
||||
|
||||
public static function execute_returns(): external_single_structure {
|
||||
return new external_single_structure([
|
||||
'success' => new external_value(PARAM_BOOL, 'OK'),
|
||||
'name' => new external_value(PARAM_TEXT, 'New name'),
|
||||
'message' => new external_value(PARAM_TEXT, 'Message'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
<?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/>.
|
||||
|
||||
/**
|
||||
* Ajax: search/list existing Adobe meetings for the mod form.
|
||||
*
|
||||
* @package mod_meetletacbmarketplace
|
||||
* @copyright 2026 MeetLet
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
|
||||
namespace mod_meetletacbmarketplace\external;
|
||||
|
||||
use core_external\external_api;
|
||||
use core_external\external_function_parameters;
|
||||
use core_external\external_multiple_structure;
|
||||
use core_external\external_single_structure;
|
||||
use core_external\external_value;
|
||||
use mod_meetletacbmarketplace\api\service;
|
||||
use mod_meetletacbmarketplace\local\config;
|
||||
use moodle_exception;
|
||||
|
||||
defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/**
|
||||
* Return a short list of server meetings (latest / search).
|
||||
*/
|
||||
class search_server_meetings extends external_api {
|
||||
public static function execute_parameters(): external_function_parameters {
|
||||
return new external_function_parameters([
|
||||
'query' => new external_value(PARAM_TEXT, 'Search query', VALUE_DEFAULT, ''),
|
||||
'limit' => new external_value(PARAM_INT, 'Max results', VALUE_DEFAULT, 10),
|
||||
'courseid' => new external_value(PARAM_INT, 'Course id for capability check', VALUE_DEFAULT, 0),
|
||||
]);
|
||||
}
|
||||
|
||||
public static function execute(string $query = '', int $limit = 10, int $courseid = 0): array {
|
||||
$params = self::validate_parameters(self::execute_parameters(), [
|
||||
'query' => $query,
|
||||
'limit' => $limit,
|
||||
'courseid' => $courseid,
|
||||
]);
|
||||
|
||||
\mod_meetletacbmarketplace\registration\manager::require_registered();
|
||||
|
||||
if (!empty($params['courseid'])) {
|
||||
$context = \context_course::instance((int)$params['courseid']);
|
||||
} else {
|
||||
$context = \context_system::instance();
|
||||
}
|
||||
self::validate_context($context);
|
||||
if (!has_capability('moodle/course:manageactivities', $context)
|
||||
&& !has_capability('mod/meetletacbmarketplace:addinstance', $context)) {
|
||||
throw new moodle_exception('nopermissions', 'error', '', 'search meetings');
|
||||
}
|
||||
|
||||
if (!config::is_configured()) {
|
||||
throw new moodle_exception('notconfigured', 'meetletacbmarketplace');
|
||||
}
|
||||
|
||||
$limit = max(1, min(50, (int)$params['limit']));
|
||||
$service = new service();
|
||||
$service->login_admin();
|
||||
$rows = $service->list_server_meetings((string)$params['query'], $limit);
|
||||
$service->logout();
|
||||
|
||||
$out = [];
|
||||
foreach ($rows as $m) {
|
||||
$label = $m->name;
|
||||
if (!empty($m->urlpath)) {
|
||||
$label .= ' (' . $m->urlpath . ')';
|
||||
}
|
||||
$out[] = [
|
||||
'scoid' => (int)$m->scoid,
|
||||
'name' => (string)$m->name,
|
||||
'label' => $label,
|
||||
'urlpath' => trim((string)$m->urlpath, '/'),
|
||||
'starttime' => !empty($m->datebegin) ? (int)strtotime((string)$m->datebegin) : 0,
|
||||
'endtime' => !empty($m->dateend) ? (int)strtotime((string)$m->dateend) : 0,
|
||||
];
|
||||
}
|
||||
return ['meetings' => $out];
|
||||
}
|
||||
|
||||
public static function execute_returns(): external_single_structure {
|
||||
return new external_single_structure([
|
||||
'meetings' => new external_multiple_structure(new external_single_structure([
|
||||
'scoid' => new external_value(PARAM_INT, 'SCO id'),
|
||||
'name' => new external_value(PARAM_TEXT, 'Meeting name'),
|
||||
'label' => new external_value(PARAM_TEXT, 'Select label'),
|
||||
'urlpath' => new external_value(PARAM_TEXT, 'URL path'),
|
||||
'starttime' => new external_value(PARAM_INT, 'Start unix time'),
|
||||
'endtime' => new external_value(PARAM_INT, 'End unix time'),
|
||||
])),
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user