first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
@@ -0,0 +1,43 @@
<?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/>.
/**
* Allows the admin to manage extension plugins
*
* @copyright 2023 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent@call-learning.fr)
*/
use mod_bigbluebuttonbn\local\plugins\admin_plugin_manager;
require_once(__DIR__ . '/../../config.php');
global $PAGE;
require_login();
$action = optional_param('action', null, PARAM_PLUGIN);
$plugin = optional_param('plugin', null, PARAM_PLUGIN);
if (!empty($plugin)) {
require_sesskey();
}
// Create the class for this controller.
$pluginmanager = new admin_plugin_manager();
$PAGE->set_context(context_system::instance());
// Execute the controller.
$pluginmanager->execute($action, $plugin);
+3
View File
@@ -0,0 +1,3 @@
define("mod_bigbluebuttonbn/actions",["./repository","core/notification","./events","core/str"],(function(_repository,_notification,_events,_str){let listening=!1;listening||((()=>{document.addEventListener("click",(e=>{const actionButton=e.target.closest('.bbb-btn-action[data-action="end"]');if(!actionButton)return;e.preventDefault();const bbbId=actionButton.dataset.bbbId,groupId=actionButton.dataset.groupId?actionButton.dataset.groupId:0;var title,question,saveLabel;(title=(0,_str.getString)("end_session_confirm_title","mod_bigbluebuttonbn"),question=(0,_str.getString)("end_session_confirm","mod_bigbluebuttonbn"),saveLabel=(0,_str.getString)("yes","moodle"),new Promise((resolve=>{(0,_notification.saveCancel)(title,question,saveLabel,resolve)}))).then((()=>(0,_repository.endMeeting)(bbbId,groupId))).then((()=>{(0,_events.notifySessionEnded)(bbbId,groupId)})).catch(_notification.exception)}))})(),listening=!0)}));
//# sourceMappingURL=actions.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"actions.min.js","sources":["../src/actions.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n/**\n * JS actions.\n *\n * @module mod_bigbluebuttonbn/actions\n * @copyright 2021 Blindside Networks Inc\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {endMeeting as requestEndMeeting} from './repository';\nimport {\n exception as displayException,\n saveCancel,\n} from 'core/notification';\nimport {notifySessionEnded} from './events';\nimport {getString} from 'core/str';\n\nconst confirmedPromise = (title, question, saveLabel) => new Promise(resolve => {\n saveCancel(title, question, saveLabel, resolve);\n});\n\nconst registerEventListeners = () => {\n document.addEventListener('click', e => {\n const actionButton = e.target.closest('.bbb-btn-action[data-action=\"end\"]');\n if (!actionButton) {\n return;\n }\n\n e.preventDefault();\n\n const bbbId = actionButton.dataset.bbbId;\n const groupId = actionButton.dataset.groupId ? actionButton.dataset.groupId : 0;\n\n confirmedPromise(\n getString('end_session_confirm_title', 'mod_bigbluebuttonbn'),\n getString('end_session_confirm', 'mod_bigbluebuttonbn'),\n getString('yes', 'moodle')\n )\n .then(() => requestEndMeeting(bbbId, groupId))\n .then(() => {\n notifySessionEnded(bbbId, groupId);\n\n return;\n })\n .catch(displayException);\n });\n};\n\nlet listening = false;\nif (!listening) {\n registerEventListeners();\n listening = true;\n}\n"],"names":["listening","document","addEventListener","e","actionButton","target","closest","preventDefault","bbbId","dataset","groupId","title","question","saveLabel","Promise","resolve","then","catch","displayException","registerEventListeners"],"mappings":"sJA6DIA,WAAY,EACXA,YA5B0B,MAC3BC,SAASC,iBAAiB,SAASC,UACzBC,aAAeD,EAAEE,OAAOC,QAAQ,0CACjCF,oBAILD,EAAEI,uBAEIC,MAAQJ,aAAaK,QAAQD,MAC7BE,QAAUN,aAAaK,QAAQC,QAAUN,aAAaK,QAAQC,QAAU,EAd7D,IAACC,MAAOC,SAAUC,WAAjBF,OAiBd,kBAAU,4BAA6B,uBAjBlBC,UAkBrB,kBAAU,sBAAuB,uBAlBFC,WAmB/B,kBAAU,MAAO,UAnB4B,IAAIC,SAAQC,uCACtDJ,MAAOC,SAAUC,UAAWE,aAoBlCC,MAAK,KAAM,0BAAkBR,MAAOE,WACpCM,MAAK,oCACiBR,MAAOE,YAI7BO,MAAMC,6BAMXC,GACAnB,WAAY"}
+11
View File
@@ -0,0 +1,11 @@
define("mod_bigbluebuttonbn/events",["exports","core/event_dispatcher"],(function(_exports,_event_dispatcher){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.notifySessionEnded=_exports.notifyCurrentSessionEnded=_exports.eventTypes=void 0;
/**
* Events for the mod_bigbluebuttonbn plugin.
*
* @module mod_bigbluebuttonbn/events
* @copyright 2021 Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
const eventTypes={sessionEnded:"mod_bigbluebuttonbn/sessionEnded",currentSessionEnded:"mod_bigbluebuttonbn/currentSessionEnded"};_exports.eventTypes=eventTypes;_exports.notifySessionEnded=(bbbId,groupId)=>(0,_event_dispatcher.dispatchEvent)(eventTypes.sessionEnded,{bbbId:bbbId,groupId:groupId});_exports.notifyCurrentSessionEnded=container=>(0,_event_dispatcher.dispatchEvent)(eventTypes.currentSessionEnded,{},container)}));
//# sourceMappingURL=events.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"events.min.js","sources":["../src/events.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Events for the mod_bigbluebuttonbn plugin.\n *\n * @module mod_bigbluebuttonbn/events\n * @copyright 2021 Blindside Networks Inc\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {dispatchEvent} from 'core/event_dispatcher';\n\nexport const eventTypes = {\n /**\n * Fired when a session has been ended.\n *\n * @event mod_bigbluebuttonbn/sessionEnded\n * @type CustomEvent\n * @property {object} detail\n * @property {number} detail.bbbId\n * @property {number} detail.groupId\n */\n sessionEnded: 'mod_bigbluebuttonbn/sessionEnded',\n\n /**\n * Fired when the current session has been ended.\n *\n * @event mod_bigbluebuttonbn/currentSessionEnded\n * @type CustomEvent\n * @property {object} detail\n */\n currentSessionEnded: 'mod_bigbluebuttonbn/currentSessionEnded',\n};\n\n/**\n * Trigger the sessionEnded event.\n *\n * @param {number} bbbId\n * @param {number} groupId\n * @returns {CustomEvent}\n * @fires event:mod_bigbluebuttonbn/sessionEnded\n */\nexport const notifySessionEnded = (bbbId, groupId) => dispatchEvent(eventTypes.sessionEnded, {\n bbbId,\n groupId,\n});\n\n/**\n * Trigger the currentSessionEnded event.\n *\n * @param {Element} container\n * @returns {CustomEvent}\n * @fires event:mod_bigbluebuttonbn/currentSessionEnded\n */\nexport const notifyCurrentSessionEnded = container => dispatchEvent(\n eventTypes.currentSessionEnded,\n {},\n container\n);\n"],"names":["eventTypes","sessionEnded","currentSessionEnded","bbbId","groupId","container"],"mappings":";;;;;;;;MAyBaA,WAAa,CAUtBC,aAAc,mCASdC,oBAAqB,sGAWS,CAACC,MAAOC,WAAY,mCAAcJ,WAAWC,aAAc,CACzFE,MAAAA,MACAC,QAAAA,6CAUqCC,YAAa,mCAClDL,WAAWE,oBACX,GACAG"}
+10
View File
@@ -0,0 +1,10 @@
define("mod_bigbluebuttonbn/guest_access_modal",["exports","core/str","core_form/modalform","core/toast","core/notification"],(function(_exports,_str,_modalform,_toast,_notification){var obj;
/**
* Javascript module for importing presets.
*
* @module mod_bigbluebuttonbn/guest_access_modal
* @copyright 2022 Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_modalform=(obj=_modalform)&&obj.__esModule?obj:{default:obj};const selectors_showGuestAccessButton='[data-action="show-guest-access"]';_exports.init=guestInfo=>{const showGuestAccessButton=document.querySelector(selectors_showGuestAccessButton);if(null===showGuestAccessButton)return;const modalForm=new _modalform.default({modalConfig:{title:(0,_str.getString)("guestaccess_title","mod_bigbluebuttonbn"),large:!0},args:guestInfo,saveButtonText:(0,_str.getString)("ok","core_moodle"),formClass:"mod_bigbluebuttonbn\\form\\guest_add"});showGuestAccessButton.addEventListener("click",(event=>{modalForm.show().then((()=>((0,_toast.addToastRegion)(modalForm.modal.getRoot()[0]),!0))).catch(_notification.exception),modalForm.addEventListener(modalForm.events.FORM_SUBMITTED,(e=>{modalForm.modal.getRoot()[0].querySelectorAll(".toast-wrapper").forEach((reg=>reg.remove())),e.detail.result?e.detail.emailcount>0&&(0,_toast.add)((0,_str.getString)("guestaccess_invite_success","mod_bigbluebuttonbn",e.detail),{type:"success"}):(0,_toast.add)((0,_str.getString)("guestaccess_invite_failure","mod_bigbluebuttonbn",e.detail),{type:"warning"})}),{once:!0}),event.stopPropagation()}))}}));
//# sourceMappingURL=guest_access_modal.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"guest_access_modal.min.js","sources":["../src/guest_access_modal.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Javascript module for importing presets.\n *\n * @module mod_bigbluebuttonbn/guest_access_modal\n * @copyright 2022 Blindside Networks Inc\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nimport {getString} from 'core/str';\nimport ModalForm from 'core_form/modalform';\nimport {add as toastAdd, addToastRegion} from 'core/toast';\nimport {\n exception as displayException,\n} from 'core/notification';\nconst selectors = {\n showGuestAccessButton: '[data-action=\"show-guest-access\"]',\n};\n\n/**\n * Intialise the object and click event to show the popup form\n *\n * @param {object} guestInfo\n * @param {string} guestInfo.id\n * @param {string} guestInfo.groupid\n * @param {string} guestInfo.guestjoinurl\n * @param {string} guestInfo.guestpassword\n */\nexport const init = (guestInfo) => {\n const showGuestAccessButton = document.querySelector(selectors.showGuestAccessButton);\n if (showGuestAccessButton === null) {\n return;\n }\n\n const modalForm = new ModalForm({\n modalConfig: {\n title: getString('guestaccess_title', 'mod_bigbluebuttonbn'),\n large: true,\n },\n args: guestInfo,\n saveButtonText: getString('ok', 'core_moodle'),\n formClass: 'mod_bigbluebuttonbn\\\\form\\\\guest_add',\n });\n showGuestAccessButton.addEventListener('click', event => {\n modalForm.show().then(() => {\n addToastRegion(modalForm.modal.getRoot()[0]);\n return true;\n }).catch(displayException);\n modalForm.addEventListener(modalForm.events.FORM_SUBMITTED, (e) => {\n // Remove toast region as if not it will be displayed on the closed modal.\n const modalElement = modalForm.modal.getRoot()[0];\n const regions = modalElement.querySelectorAll('.toast-wrapper');\n regions.forEach((reg) => reg.remove());\n if (e.detail.result) {\n if (e.detail.emailcount > 0) {\n toastAdd(getString('guestaccess_invite_success', 'mod_bigbluebuttonbn', e.detail),\n {\n type: 'success',\n }\n );\n }\n } else {\n toastAdd(getString('guestaccess_invite_failure', 'mod_bigbluebuttonbn', e.detail),\n {\n type: 'warning',\n }\n );\n }\n }, {once: true});\n event.stopPropagation();\n });\n};\n"],"names":["selectors","guestInfo","showGuestAccessButton","document","querySelector","modalForm","ModalForm","modalConfig","title","large","args","saveButtonText","formClass","addEventListener","event","show","then","modal","getRoot","catch","displayException","events","FORM_SUBMITTED","e","querySelectorAll","forEach","reg","remove","detail","result","emailcount","type","once","stopPropagation"],"mappings":";;;;;;;sJA4BMA,gCACqB,kDAYNC,kBACXC,sBAAwBC,SAASC,cAAcJ,oCACvB,OAA1BE,mCAIEG,UAAY,IAAIC,mBAAU,CAC5BC,YAAa,CACTC,OAAO,kBAAU,oBAAqB,uBACtCC,OAAO,GAEXC,KAAMT,UACNU,gBAAgB,kBAAU,KAAM,eAChCC,UAAW,yCAEfV,sBAAsBW,iBAAiB,SAASC,QAC5CT,UAAUU,OAAOC,MAAK,+BACHX,UAAUY,MAAMC,UAAU,KAClC,KACRC,MAAMC,yBACTf,UAAUQ,iBAAiBR,UAAUgB,OAAOC,gBAAiBC,IAEpClB,UAAUY,MAAMC,UAAU,GAClBM,iBAAiB,kBACtCC,SAASC,KAAQA,IAAIC,WACzBJ,EAAEK,OAAOC,OACLN,EAAEK,OAAOE,WAAa,mBACb,kBAAU,6BAA8B,sBAAuBP,EAAEK,QACtE,CACIG,KAAM,4BAKT,kBAAU,6BAA8B,sBAAuBR,EAAEK,QACtE,CACIG,KAAM,cAInB,CAACC,MAAM,IACVlB,MAAMmB"}
+3
View File
@@ -0,0 +1,3 @@
define("mod_bigbluebuttonbn/index",["exports","./actions","./events"],(function(_exports,_actions,_events){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0;_exports.init=()=>{document.addEventListener(_events.eventTypes.sessionEnded,(()=>{window.location.reload()}))}}));
//# sourceMappingURL=index.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"index.min.js","sources":["../src/index.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Index page for the mod_bigbluebuttonbn plugin.\n *\n * @module mod_bigbluebuttonbn/index\n * @copyright 2021 Blindside Networks Inc\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport './actions';\nimport {eventTypes} from './events';\n\nexport const init = () => {\n document.addEventListener(eventTypes.sessionEnded, () => {\n window.location.reload();\n });\n};\n"],"names":["document","addEventListener","eventTypes","sessionEnded","window","location","reload"],"mappings":"sMA0BoB,KAChBA,SAASC,iBAAiBC,mBAAWC,cAAc,KAC/CC,OAAOC,SAASC"}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+3
View File
@@ -0,0 +1,3 @@
define("mod_bigbluebuttonbn/repository",["exports","core/ajax"],(function(_exports,_ajax){Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.updateRecording=_exports.getMeetingInfo=_exports.fetchRecordingsToImport=_exports.fetchRecordings=_exports.endMeeting=_exports.completionValidate=void 0;_exports.fetchRecordings=(bigbluebuttonbnid,tools,groupid)=>{const args={bigbluebuttonbnid:bigbluebuttonbnid,tools:tools};return groupid&&(args.groupid=groupid),(0,_ajax.call)([{methodname:"mod_bigbluebuttonbn_get_recordings",args:args}])[0]};_exports.fetchRecordingsToImport=(destinationinstanceid,sourcebigbluebuttonbnid,sourcecourseid,tools,groupid)=>{const args={destinationinstanceid:destinationinstanceid,sourcebigbluebuttonbnid:sourcebigbluebuttonbnid,sourcecourseid:sourcecourseid,tools:tools};return groupid&&(args.groupid=groupid),(0,_ajax.call)([{methodname:"mod_bigbluebuttonbn_get_recordings_to_import",args:args}])[0]};_exports.updateRecording=args=>(0,_ajax.call)([{methodname:"mod_bigbluebuttonbn_update_recording",args:args}])[0];_exports.endMeeting=(bigbluebuttonbnid,groupid)=>(0,_ajax.call)([{methodname:"mod_bigbluebuttonbn_end_meeting",args:{bigbluebuttonbnid:bigbluebuttonbnid,groupid:groupid}}])[0];_exports.completionValidate=bigbluebuttonbnid=>(0,_ajax.call)([{methodname:"mod_bigbluebuttonbn_completion_validate",args:{bigbluebuttonbnid:bigbluebuttonbnid}}])[0];_exports.getMeetingInfo=function(bigbluebuttonbnid,groupid){let updatecache=arguments.length>2&&void 0!==arguments[2]&&arguments[2];return(0,_ajax.call)([{methodname:"mod_bigbluebuttonbn_meeting_info",args:{bigbluebuttonbnid:bigbluebuttonbnid,groupid:groupid,updatecache:updatecache}}])[0]}}));
//# sourceMappingURL=repository.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"repository.min.js","sources":["../src/repository.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * Repository to perform WS calls for mod_bigbluebuttonbn.\n *\n * @module mod_bigbluebuttonbn/repository\n * @copyright 2021 Blindside Networks Inc\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {call as fetchMany} from 'core/ajax';\n\n/**\n * Fetch the list of recordings from the server.\n *\n * @param {Number} bigbluebuttonbnid The instance ID\n * @param {String} tools the set of tools to display\n * @param {number} groupid\n * @returns {Promise}\n */\nexport const fetchRecordings = (bigbluebuttonbnid, tools, groupid) => {\n const args = {\n bigbluebuttonbnid,\n tools,\n };\n\n if (groupid) {\n args.groupid = groupid;\n }\n\n return fetchMany([{methodname: 'mod_bigbluebuttonbn_get_recordings', args}])[0];\n};\n\n/**\n * Fetch the list of recordings from the server that can be imported.\n *\n * @param {Number} destinationinstanceid The destination instance ID\n * @param {Number} sourcebigbluebuttonbnid The original instance ID\n * @param {Number} sourcecourseid The destination instance ID\n * @param {String} tools the set of tools to display\n * @param {number} groupid\n * @returns {Promise}\n */\nexport const fetchRecordingsToImport = (\n destinationinstanceid,\n sourcebigbluebuttonbnid,\n sourcecourseid,\n tools,\n groupid\n) => {\n const args = {\n destinationinstanceid,\n sourcebigbluebuttonbnid,\n sourcecourseid,\n tools,\n };\n\n if (groupid) {\n args.groupid = groupid;\n }\n\n return fetchMany([{methodname: 'mod_bigbluebuttonbn_get_recordings_to_import', args}])[0];\n};\n\n/**\n * Perform an update on a single recording.\n *\n * @param {object} args The instance ID\n * @returns {Promise}\n */\nexport const updateRecording = args => fetchMany([\n {\n methodname: 'mod_bigbluebuttonbn_update_recording',\n args,\n }\n])[0];\n\n/**\n * End the Meeting\n *\n * @param {number} bigbluebuttonbnid\n * @param {number} groupid\n * @returns {Promise}\n */\nexport const endMeeting = (bigbluebuttonbnid, groupid) => fetchMany([\n {\n methodname: 'mod_bigbluebuttonbn_end_meeting',\n args: {\n bigbluebuttonbnid,\n groupid\n },\n }\n])[0];\n\n/**\n * Validate completion.\n *\n * @param {number} bigbluebuttonbnid\n * @returns {Promise}\n */\nexport const completionValidate = (bigbluebuttonbnid) => fetchMany([\n {\n methodname: 'mod_bigbluebuttonbn_completion_validate',\n args: {\n bigbluebuttonbnid\n },\n }\n])[0];\n\n\n/**\n * Fetch meeting info for the specified meeting.\n *\n * @param {number} bigbluebuttonbnid\n * @param {number} groupid\n * @param {boolean} [updatecache=false]\n * @returns {Promise}\n */\nexport const getMeetingInfo = (bigbluebuttonbnid, groupid, updatecache = false) => fetchMany([\n {\n methodname: 'mod_bigbluebuttonbn_meeting_info',\n args: {\n bigbluebuttonbnid,\n groupid,\n updatecache,\n },\n }\n])[0];\n"],"names":["bigbluebuttonbnid","tools","groupid","args","methodname","destinationinstanceid","sourcebigbluebuttonbnid","sourcecourseid","updatecache"],"mappings":"6UAiC+B,CAACA,kBAAmBC,MAAOC,iBAChDC,KAAO,CACTH,kBAAAA,kBACAC,MAAAA,cAGAC,UACAC,KAAKD,QAAUA,UAGZ,cAAU,CAAC,CAACE,WAAY,qCAAsCD,KAAAA,QAAQ,qCAa1C,CACnCE,sBACAC,wBACAC,eACAN,MACAC,iBAEMC,KAAO,CACTE,sBAAAA,sBACAC,wBAAAA,wBACAC,eAAAA,eACAN,MAAAA,cAGAC,UACAC,KAAKD,QAAUA,UAGZ,cAAU,CAAC,CAACE,WAAY,+CAAgDD,KAAAA,QAAQ,6BAS5DA,OAAQ,cAAU,CAC7C,CACIC,WAAY,uCACZD,KAAAA,QAEL,uBASuB,CAACH,kBAAmBE,WAAY,cAAU,CAChE,CACIE,WAAY,kCACZD,KAAM,CACFH,kBAAAA,kBACAE,QAAAA,YAGT,+BAQgCF,oBAAsB,cAAU,CAC/D,CACII,WAAY,0CACZD,KAAM,CACFH,kBAAAA,sBAGT,2BAW2B,SAACA,kBAAmBE,aAASM,2EAAwB,cAAU,CACzF,CACIJ,WAAY,mCACZD,KAAM,CACFH,kBAAAA,kBACAE,QAAAA,QACAM,YAAAA,gBAGT"}
+10
View File
@@ -0,0 +1,10 @@
define("mod_bigbluebuttonbn/rooms",["exports","./actions","./repository","./roomupdater","core/notification","core/pending","core/str","core/toast","./events"],(function(_exports,_actions,repository,roomUpdater,_notification,_pending,_str,_toast,_events){var obj;function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireWildcard(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}return newObj.default=obj,cache&&cache.set(obj,newObj),newObj}
/**
* JS actions for the rooms page for mod_bigbluebuttonbn.
*
* @module mod_bigbluebuttonbn/rooms
* @copyright 2021 Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.setupWindowAutoClose=_exports.init=void 0,repository=_interopRequireWildcard(repository),roomUpdater=_interopRequireWildcard(roomUpdater),_pending=(obj=_pending)&&obj.__esModule?obj:{default:obj};_exports.init=(bigbluebuttonbnid,pollInterval)=>{const completionElement=document.querySelector("a[href*=completion_validate]");completionElement&&completionElement.addEventListener("click",(event=>{event.preventDefault();const pendingPromise=new _pending.default("mod_bigbluebuttonbn/completion:validate");repository.completionValidate(bigbluebuttonbnid).then((()=>(0,_str.getString)("completionvalidatestatetriggered","mod_bigbluebuttonbn"))).then((str=>(0,_toast.add)(str))).then((()=>pendingPromise.resolve())).catch(_notification.exception)})),document.addEventListener("click",(e=>{const joinButton=e.target.closest('[data-action="join"]');joinButton&&(window.open(joinButton.href,"bigbluebutton_conference"),e.preventDefault(),setTimeout((()=>{roomUpdater.updateRoom(!0)}),pollInterval))})),document.addEventListener(_events.eventTypes.sessionEnded,(()=>{roomUpdater.stop(),roomUpdater.updateRoom(),(0,_notification.fetchNotifications)()})),window.addEventListener(_events.eventTypes.currentSessionEnded,(()=>{roomUpdater.stop(),roomUpdater.updateRoom(),(0,_notification.fetchNotifications)()})),roomUpdater.start(pollInterval)};_exports.setupWindowAutoClose=function(){let closeDelay=arguments.length>0&&void 0!==arguments[0]?arguments[0]:2e3;(0,_events.notifyCurrentSessionEnded)(window.opener),window.addEventListener("onbeforeunload",(()=>{window.opener.setTimeout((()=>{roomUpdater.updateRoom(!0)}),closeDelay)}),{once:!0}),window.close()}}));
//# sourceMappingURL=rooms.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"rooms.min.js","sources":["../src/rooms.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * JS actions for the rooms page for mod_bigbluebuttonbn.\n *\n * @module mod_bigbluebuttonbn/rooms\n * @copyright 2021 Blindside Networks Inc\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport './actions';\nimport * as repository from './repository';\nimport * as roomUpdater from './roomupdater';\nimport {\n exception as displayException,\n fetchNotifications,\n} from 'core/notification';\nimport Pending from 'core/pending';\nimport {getString} from 'core/str';\nimport {add as addToast} from 'core/toast';\nimport {eventTypes, notifyCurrentSessionEnded} from './events';\n\n/**\n * Init the room\n *\n * @param {Number} bigbluebuttonbnid bigblubeutton identifier\n * @param {Number} pollInterval poll interval in miliseconds\n */\nexport const init = (bigbluebuttonbnid, pollInterval) => {\n const completionElement = document.querySelector('a[href*=completion_validate]');\n if (completionElement) {\n completionElement.addEventListener(\"click\", event => {\n event.preventDefault();\n\n const pendingPromise = new Pending('mod_bigbluebuttonbn/completion:validate');\n\n repository.completionValidate(bigbluebuttonbnid)\n .then(() => getString('completionvalidatestatetriggered', 'mod_bigbluebuttonbn'))\n .then(str => addToast(str))\n .then(() => pendingPromise.resolve())\n .catch(displayException);\n });\n }\n\n document.addEventListener('click', e => {\n const joinButton = e.target.closest('[data-action=\"join\"]');\n if (joinButton) {\n window.open(joinButton.href, 'bigbluebutton_conference');\n e.preventDefault();\n // Gives the user a bit of time to go into the meeting before polling the room.\n setTimeout(() => {\n roomUpdater.updateRoom(true);\n }, pollInterval);\n }\n });\n\n document.addEventListener(eventTypes.sessionEnded, () => {\n roomUpdater.stop();\n roomUpdater.updateRoom();\n fetchNotifications();\n });\n\n window.addEventListener(eventTypes.currentSessionEnded, () => {\n roomUpdater.stop();\n roomUpdater.updateRoom();\n fetchNotifications();\n });\n // Room update.\n roomUpdater.start(pollInterval);\n};\n\n/**\n * Auto close child windows when clicking the End meeting button.\n * @param {Number} closeDelay time to wait in miliseconds before closing the window\n */\nexport const setupWindowAutoClose = (closeDelay = 2000) => {\n notifyCurrentSessionEnded(window.opener);\n window.addEventListener('onbeforeunload', () => {\n window.opener.setTimeout(() => {\n roomUpdater.updateRoom(true);\n }, closeDelay);\n },\n {\n once: true\n });\n window.close(); // This does not work as scripts can only close windows that are opened by themselves.\n};\n"],"names":["bigbluebuttonbnid","pollInterval","completionElement","document","querySelector","addEventListener","event","preventDefault","pendingPromise","Pending","repository","completionValidate","then","str","resolve","catch","displayException","e","joinButton","target","closest","window","open","href","setTimeout","roomUpdater","updateRoom","eventTypes","sessionEnded","stop","currentSessionEnded","start","closeDelay","opener","once","close"],"mappings":";;;;;;;wRAyCoB,CAACA,kBAAmBC,sBAC9BC,kBAAoBC,SAASC,cAAc,gCAC7CF,mBACAA,kBAAkBG,iBAAiB,SAASC,QACxCA,MAAMC,uBAEAC,eAAiB,IAAIC,iBAAQ,2CAEnCC,WAAWC,mBAAmBX,mBACzBY,MAAK,KAAM,kBAAU,mCAAoC,yBACzDA,MAAKC,MAAO,cAASA,OACrBD,MAAK,IAAMJ,eAAeM,YAC1BC,MAAMC,4BAInBb,SAASE,iBAAiB,SAASY,UACzBC,WAAaD,EAAEE,OAAOC,QAAQ,wBAChCF,aACAG,OAAOC,KAAKJ,WAAWK,KAAM,4BAC7BN,EAAEV,iBAEFiB,YAAW,KACPC,YAAYC,YAAW,KACxBzB,kBAIXE,SAASE,iBAAiBsB,mBAAWC,cAAc,KAC/CH,YAAYI,OACZJ,YAAYC,uDAIhBL,OAAOhB,iBAAiBsB,mBAAWG,qBAAqB,KACpDL,YAAYI,OACZJ,YAAYC,uDAIhBD,YAAYM,MAAM9B,6CAOc,eAAC+B,kEAAa,0CACpBX,OAAOY,QACjCZ,OAAOhB,iBAAiB,kBAAkB,KAClCgB,OAAOY,OAAOT,YAAW,KACrBC,YAAYC,YAAW,KACxBM,cAEP,CACIE,MAAM,IAEdb,OAAOc"}
+10
View File
@@ -0,0 +1,10 @@
define("mod_bigbluebuttonbn/roomupdater",["exports","core/pending","core/templates","core/notification","./repository"],(function(_exports,_pending,_templates,_notification,_repository){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}
/**
* JS room updater.
*
* @module mod_bigbluebuttonbn/roomupdater
* @copyright 2021 Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.updateRoom=_exports.stop=_exports.start=void 0,_pending=_interopRequireDefault(_pending),_templates=_interopRequireDefault(_templates);let timerReference=null,timerRunning=!1,pollInterval=0,pollIntervalFactor=1;const resetValues=()=>{timerRunning=!1,timerReference=null,pollInterval=0,pollIntervalFactor=1};_exports.start=interval=>{resetValues(),timerRunning=!0,pollInterval=interval,poll()};_exports.stop=()=>{timerReference&&clearTimeout(timerReference),resetValues()};const poll=()=>{timerRunning&&pollInterval&&updateRoom().then((updateOk=>(updateOk||(pollIntervalFactor=pollIntervalFactor<10?pollIntervalFactor+1:10),timerReference=setTimeout((()=>poll()),pollInterval*pollIntervalFactor),!0))).catch()},updateRoom=function(){let updatecache=arguments.length>0&&void 0!==arguments[0]&&arguments[0];const bbbRoomViewElement=document.getElementById("bigbluebuttonbn-room-view");if(null===bbbRoomViewElement)return Promise.resolve(!1);const bbbId=bbbRoomViewElement.dataset.bbbId,groupId=bbbRoomViewElement.dataset.groupId,pendingPromise=new _pending.default("mod_bigbluebuttonbn/roomupdater:updateRoom");return(0,_repository.getMeetingInfo)(bbbId,groupId,updatecache).then((data=>(data.haspresentations=!(!data.presentations||!data.presentations.length),_templates.default.renderForPromise("mod_bigbluebuttonbn/room_view",data)))).then((_ref=>{let{html:html,js:js}=_ref;return _templates.default.replaceNode(bbbRoomViewElement,html,js)})).then((()=>pendingPromise.resolve())).catch(_notification.exception)};_exports.updateRoom=updateRoom}));
//# sourceMappingURL=roomupdater.min.js.map
@@ -0,0 +1 @@
{"version":3,"file":"roomupdater.min.js","sources":["../src/roomupdater.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see <http://www.gnu.org/licenses/>.\n\n/**\n * JS room updater.\n *\n * @module mod_bigbluebuttonbn/roomupdater\n * @copyright 2021 Blindside Networks Inc\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Pending from 'core/pending';\nimport Templates from \"core/templates\";\nimport {exception as displayException} from 'core/notification';\nimport {getMeetingInfo} from './repository';\n\nlet timerReference = null;\nlet timerRunning = false;\nlet pollInterval = 0;\nlet pollIntervalFactor = 1;\nconst MAX_POLL_INTERVAL_FACTOR = 10;\n\nconst resetValues = () => {\n timerRunning = false;\n timerReference = null;\n pollInterval = 0;\n pollIntervalFactor = 1;\n};\n\n/**\n * Start the information poller.\n * @param {Number} interval interval in miliseconds between each poll action.\n */\nexport const start = (interval) => {\n resetValues();\n timerRunning = true;\n pollInterval = interval;\n poll();\n};\n\n/**\n * Stop the room updater.\n */\nexport const stop = () => {\n if (timerReference) {\n clearTimeout(timerReference);\n }\n resetValues();\n};\n\n/**\n * Start the information poller.\n */\nconst poll = () => {\n if (!timerRunning || !pollInterval) {\n // The poller has been stopped.\n return;\n }\n updateRoom()\n .then((updateOk) => {\n if (!updateOk) {\n pollIntervalFactor = (pollIntervalFactor < MAX_POLL_INTERVAL_FACTOR) ?\n pollIntervalFactor + 1 : MAX_POLL_INTERVAL_FACTOR;\n // We make sure if there is an error that we do not try too often.\n }\n timerReference = setTimeout(() => poll(), pollInterval * pollIntervalFactor);\n return true;\n })\n .catch();\n};\n\n/**\n * Update the room information.\n *\n * @param {boolean} [updatecache=false] should we update cache\n * @returns {Promise}\n */\nexport const updateRoom = (updatecache = false) => {\n const bbbRoomViewElement = document.getElementById('bigbluebuttonbn-room-view');\n if (bbbRoomViewElement === null) {\n return Promise.resolve(false);\n }\n\n const bbbId = bbbRoomViewElement.dataset.bbbId;\n const groupId = bbbRoomViewElement.dataset.groupId;\n\n const pendingPromise = new Pending('mod_bigbluebuttonbn/roomupdater:updateRoom');\n\n return getMeetingInfo(bbbId, groupId, updatecache)\n .then(data => {\n // Just make sure we have the right information for the template.\n data.haspresentations = !!(data.presentations && data.presentations.length);\n return Templates.renderForPromise('mod_bigbluebuttonbn/room_view', data);\n })\n .then(({html, js}) => Templates.replaceNode(bbbRoomViewElement, html, js))\n .then(() => pendingPromise.resolve())\n .catch(displayException);\n};\n"],"names":["timerReference","timerRunning","pollInterval","pollIntervalFactor","resetValues","interval","poll","clearTimeout","updateRoom","then","updateOk","setTimeout","catch","updatecache","bbbRoomViewElement","document","getElementById","Promise","resolve","bbbId","dataset","groupId","pendingPromise","Pending","data","haspresentations","presentations","length","Templates","renderForPromise","_ref","html","js","replaceNode","displayException"],"mappings":";;;;;;;iNA4BIA,eAAiB,KACjBC,cAAe,EACfC,aAAe,EACfC,mBAAqB,QAGnBC,YAAc,KAChBH,cAAe,EACfD,eAAiB,KACjBE,aAAe,EACfC,mBAAqB,kBAOHE,WAClBD,cACAH,cAAe,EACfC,aAAeG,SACfC,sBAMgB,KACZN,gBACAO,aAAaP,gBAEjBI,qBAMEE,KAAO,KACJL,cAAiBC,cAItBM,aACKC,MAAMC,WACEA,WACDP,mBAAsBA,mBAzCL,GA0CbA,mBAAqB,EA1CR,IA6CrBH,eAAiBW,YAAW,IAAML,QAAQJ,aAAeC,qBAClD,KAEVS,SASIJ,WAAa,eAACK,0EACjBC,mBAAqBC,SAASC,eAAe,gCACxB,OAAvBF,0BACOG,QAAQC,SAAQ,SAGrBC,MAAQL,mBAAmBM,QAAQD,MACnCE,QAAUP,mBAAmBM,QAAQC,QAErCC,eAAiB,IAAIC,iBAAQ,qDAE5B,8BAAeJ,MAAOE,QAASR,aACjCJ,MAAKe,OAEFA,KAAKC,oBAAsBD,KAAKE,gBAAiBF,KAAKE,cAAcC,QAC7DC,mBAAUC,iBAAiB,gCAAiCL,SAEtEf,MAAKqB,WAACC,KAACA,KAADC,GAAOA,gBAAQJ,mBAAUK,YAAYnB,mBAAoBiB,KAAMC,OACrEvB,MAAK,IAAMa,eAAeJ,YAC1BN,MAAMsB"}
+66
View File
@@ -0,0 +1,66 @@
// 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/>.
/**
* JS actions.
*
* @module mod_bigbluebuttonbn/actions
* @copyright 2021 Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {endMeeting as requestEndMeeting} from './repository';
import {
exception as displayException,
saveCancel,
} from 'core/notification';
import {notifySessionEnded} from './events';
import {getString} from 'core/str';
const confirmedPromise = (title, question, saveLabel) => new Promise(resolve => {
saveCancel(title, question, saveLabel, resolve);
});
const registerEventListeners = () => {
document.addEventListener('click', e => {
const actionButton = e.target.closest('.bbb-btn-action[data-action="end"]');
if (!actionButton) {
return;
}
e.preventDefault();
const bbbId = actionButton.dataset.bbbId;
const groupId = actionButton.dataset.groupId ? actionButton.dataset.groupId : 0;
confirmedPromise(
getString('end_session_confirm_title', 'mod_bigbluebuttonbn'),
getString('end_session_confirm', 'mod_bigbluebuttonbn'),
getString('yes', 'moodle')
)
.then(() => requestEndMeeting(bbbId, groupId))
.then(() => {
notifySessionEnded(bbbId, groupId);
return;
})
.catch(displayException);
});
};
let listening = false;
if (!listening) {
registerEventListeners();
listening = true;
}
+72
View File
@@ -0,0 +1,72 @@
// 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/>.
/**
* Events for the mod_bigbluebuttonbn plugin.
*
* @module mod_bigbluebuttonbn/events
* @copyright 2021 Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {dispatchEvent} from 'core/event_dispatcher';
export const eventTypes = {
/**
* Fired when a session has been ended.
*
* @event mod_bigbluebuttonbn/sessionEnded
* @type CustomEvent
* @property {object} detail
* @property {number} detail.bbbId
* @property {number} detail.groupId
*/
sessionEnded: 'mod_bigbluebuttonbn/sessionEnded',
/**
* Fired when the current session has been ended.
*
* @event mod_bigbluebuttonbn/currentSessionEnded
* @type CustomEvent
* @property {object} detail
*/
currentSessionEnded: 'mod_bigbluebuttonbn/currentSessionEnded',
};
/**
* Trigger the sessionEnded event.
*
* @param {number} bbbId
* @param {number} groupId
* @returns {CustomEvent}
* @fires event:mod_bigbluebuttonbn/sessionEnded
*/
export const notifySessionEnded = (bbbId, groupId) => dispatchEvent(eventTypes.sessionEnded, {
bbbId,
groupId,
});
/**
* Trigger the currentSessionEnded event.
*
* @param {Element} container
* @returns {CustomEvent}
* @fires event:mod_bigbluebuttonbn/currentSessionEnded
*/
export const notifyCurrentSessionEnded = container => dispatchEvent(
eventTypes.currentSessionEnded,
{},
container
);
@@ -0,0 +1,85 @@
// 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/>.
/**
* Javascript module for importing presets.
*
* @module mod_bigbluebuttonbn/guest_access_modal
* @copyright 2022 Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {getString} from 'core/str';
import ModalForm from 'core_form/modalform';
import {add as toastAdd, addToastRegion} from 'core/toast';
import {
exception as displayException,
} from 'core/notification';
const selectors = {
showGuestAccessButton: '[data-action="show-guest-access"]',
};
/**
* Intialise the object and click event to show the popup form
*
* @param {object} guestInfo
* @param {string} guestInfo.id
* @param {string} guestInfo.groupid
* @param {string} guestInfo.guestjoinurl
* @param {string} guestInfo.guestpassword
*/
export const init = (guestInfo) => {
const showGuestAccessButton = document.querySelector(selectors.showGuestAccessButton);
if (showGuestAccessButton === null) {
return;
}
const modalForm = new ModalForm({
modalConfig: {
title: getString('guestaccess_title', 'mod_bigbluebuttonbn'),
large: true,
},
args: guestInfo,
saveButtonText: getString('ok', 'core_moodle'),
formClass: 'mod_bigbluebuttonbn\\form\\guest_add',
});
showGuestAccessButton.addEventListener('click', event => {
modalForm.show().then(() => {
addToastRegion(modalForm.modal.getRoot()[0]);
return true;
}).catch(displayException);
modalForm.addEventListener(modalForm.events.FORM_SUBMITTED, (e) => {
// Remove toast region as if not it will be displayed on the closed modal.
const modalElement = modalForm.modal.getRoot()[0];
const regions = modalElement.querySelectorAll('.toast-wrapper');
regions.forEach((reg) => reg.remove());
if (e.detail.result) {
if (e.detail.emailcount > 0) {
toastAdd(getString('guestaccess_invite_success', 'mod_bigbluebuttonbn', e.detail),
{
type: 'success',
}
);
}
} else {
toastAdd(getString('guestaccess_invite_failure', 'mod_bigbluebuttonbn', e.detail),
{
type: 'warning',
}
);
}
}, {once: true});
event.stopPropagation();
});
};
+31
View File
@@ -0,0 +1,31 @@
// 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/>.
/**
* Index page for the mod_bigbluebuttonbn plugin.
*
* @module mod_bigbluebuttonbn/index
* @copyright 2021 Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import './actions';
import {eventTypes} from './events';
export const init = () => {
document.addEventListener(eventTypes.sessionEnded, () => {
window.location.reload();
});
};
+372
View File
@@ -0,0 +1,372 @@
// 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/>.
/**
* JS for the mod_form page on mod_bigbluebuttonbn plugin.
*
* @module mod_bigbluebuttonbn/modform
* @copyright 2021 Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {getString} from 'core/str';
import Notification from 'core/notification';
import Templates from "core/templates";
/**
* Get all selectors in one place.
*
*/
const ELEMENT_SELECTOR = {
instanceTypeSelection: () => document.querySelector('select#id_type'),
instanceTypeProfiles: () => document.querySelector('[data-profile-types]'),
participantData: () => document.querySelector('[data-participant-data]'),
participantList: () => document.getElementsByName('participants')[0],
participantTable: () => document.getElementById('participant_list_table'),
participantSelectionType: () => document.getElementsByName('bigbluebuttonbn_participant_selection_type')[0],
participantSelection: () => document.getElementsByName('bigbluebuttonbn_participant_selection')[0],
participantAddButton: () => document.getElementsByName('bigbluebuttonbn_participant_selection_add')[0],
};
/**
* Initialise the moodle form code.
*
* This will help hide or show items depending on the selection of the instance type.
*
* @method init
* @param {object} info
*/
export const init = (info) => {
const selectedType = ELEMENT_SELECTOR.instanceTypeSelection();
const instanceTypeProfiles = JSON.parse(ELEMENT_SELECTOR.instanceTypeProfiles().dataset.profileTypes);
let profileType = info.instanceTypeDefault;
if (selectedType !== null && selectedType.selectedIndex !== -1) {
profileType = selectedType.options[selectedType.selectedIndex].value;
}
const isFeatureEnabled = (profileType, feature) => {
const features = instanceTypeProfiles[profileType].features;
return (features.indexOf(feature) !== -1);
};
applyInstanceTypeProfile(profileType, isFeatureEnabled);
// Change form visible fields depending on the selection.
selectedType.addEventListener('change', (e) => {
applyInstanceTypeProfile(e.target.value, isFeatureEnabled);
});
ELEMENT_SELECTOR.participantSelectionType().addEventListener('change', (e) => {
const currentTypeSelect = e.target;
updateSelectionFromType(currentTypeSelect);
});
ELEMENT_SELECTOR.participantAddButton().addEventListener('click', (e) => {
e.stopPropagation();
e.preventDefault();
participantAddFromCurrentSelection();
});
participantListInit();
};
/**
* Show or hide form element depending on the selected profile
*
* @param {string} profileType
* @param {function} isFeatureEnabled
*/
const applyInstanceTypeProfile = (profileType, isFeatureEnabled) => {
let showAll = isFeatureEnabled(profileType, 'all');
const showFieldset = (id, show) => {
// Show room settings validation.
const node = document.querySelector('#' + id);
if (!node) {
return;
}
if (show) {
node.style.display = 'block';
return;
}
node.style.display = 'none';
};
const showInput = (id, show) => {
// Show room settings validation.
const node = document.querySelector('#' + id);
if (!node) {
return;
}
var ancestor = node.closest('div').closest('div');
if (show) {
ancestor.style.display = 'block';
return;
}
ancestor.style.display = 'none';
};
const showFormGroup = (id, show) => {
// Show room settings validation.
const node = document.querySelector('#fgroup_id_' + id);
if (!node) {
return;
}
if (show) {
node.classList.remove('hidden');
return;
}
node.classList.add('hidden');
};
// Show room settings validation.
showFieldset('id_room', showAll ||
isFeatureEnabled(profileType, 'showroom'));
showInput('id_record', showAll ||
isFeatureEnabled(profileType, 'showroom'));
// Show recordings settings validation.
showFieldset('id_recordings', showAll ||
isFeatureEnabled(profileType, 'showrecordings'));
// Show recordings imported settings validation.
showInput('id_recordings_imported', showAll ||
isFeatureEnabled(profileType, 'showrecordings'));
// Show lock settings validation.
showFieldset('id_lock', showAll ||
isFeatureEnabled(profileType, 'lock'));
// Show guest settings validation.
showFieldset('id_guestaccess', showAll ||
isFeatureEnabled(profileType, 'showroom'));
// Preuploadpresentation feature validation.
showFieldset('id_preuploadpresentation', showAll ||
isFeatureEnabled(profileType, 'preuploadpresentation'));
// Participants feature validation.
showFieldset('id_permissions', showAll ||
isFeatureEnabled(profileType, 'permissions'));
// Schedule feature validation.
showFieldset('id_schedule', showAll ||
isFeatureEnabled(profileType, 'schedule'));
// Common module settings validation.
showFieldset('id_modstandardelshdr', showAll ||
isFeatureEnabled(profileType, 'modstandardelshdr'));
// Restrict access validation.
showFieldset('id_availabilityconditionsheader', showAll ||
isFeatureEnabled(profileType, 'availabilityconditionsheader'));
// Tags validation.
showFieldset('id_tagshdr', showAll || isFeatureEnabled(profileType, 'tagshdr'));
// Competencies validation.
showFieldset('id_competenciessection', showAll ||
isFeatureEnabled(profileType, 'competenciessection'));
// Completion validation.
showFormGroup('completionattendancegroup', showAll ||
isFeatureEnabled(profileType, 'completionattendance'));
// Completion validation.
showFormGroup('completionengagementgroup', showAll ||
isFeatureEnabled(profileType, 'completionengagement'));
};
/**
* Init the participant list
*/
const participantListInit = () => {
const participantData = JSON.parse(ELEMENT_SELECTOR.participantData().dataset.participantData);
const participantList = getParticipantList();
participantList.forEach(participant => {
const selectionTypeValue = participant.selectiontype;
const selectionValue = participant.selectionid;
const selectionRole = participant.role;
if (participant.selectiontype === 'all' ||
typeof participantData[participant.selectiontype].children[participant.selectionid] !== 'undefined') {
// Add it to the form, but don't add the delete button if it is the first item.
participantAddToForm(selectionTypeValue, selectionValue, selectionRole, true).then();
}
});
};
/**
* Add rows to the participant list depending on the current selection.
*
* @param {string} selectionTypeValue
* @param {string} selectionValue
* @param {string} selectedRole
* @param {boolean} canRemove
* @returns {Promise<void>}
*/
const participantAddToForm = async(selectionTypeValue, selectionValue, selectedRole, canRemove) => {
const participantData = JSON.parse(ELEMENT_SELECTOR.participantData().dataset.participantData);
const sviewer = await getString('mod_form_field_participant_bbb_role_viewer', 'mod_bigbluebuttonbn');
const smoderator = await getString('mod_form_field_participant_bbb_role_moderator', 'mod_bigbluebuttonbn');
let roles = {
viewer: {'id': 'viewer', label: sviewer},
moderator: {'id': 'moderator', label: smoderator}
};
roles[selectedRole].isselected = true;
try {
const listTable = document.querySelector('#participant_list_table tbody');
const templateContext = {
'selectiontypevalue': selectionTypeValue,
'selectionvalue': selectionValue,
'participanttype': participantData[selectionTypeValue].name,
'participantvalue':
(selectionTypeValue !== 'all') ?
participantData[selectionTypeValue].children[selectionValue].name : null,
'roles': Object.values(roles),
'canRemove': canRemove
};
const {html, js} = await Templates.renderForPromise('mod_bigbluebuttonbn/participant_form_add', templateContext);
const newNode = Templates.appendNodeContents(listTable, html, js)[0];
newNode.querySelector('.participant-select').addEventListener('change', () => {
participantListRoleUpdate(selectionTypeValue, selectionValue);
});
// Now add the callbacks: participantListRoleUpdate() and participantRemove().
const removeNode = newNode.querySelector('.remove-button');
if (removeNode) {
removeNode
.addEventListener('click', () => {
participantRemove(selectionTypeValue, selectionValue);
});
}
} catch (e) {
Notification.exception(e);
}
};
/*
*/
/**
* Update the related form element with the list value.
*
* @param {object} list
*/
const participantListUpdate = (list) => {
const participantList = ELEMENT_SELECTOR.participantList();
participantList.value = JSON.stringify(list);
};
/**
*
* @returns {any}
*/
const getParticipantList = () => {
const participantListValue = ELEMENT_SELECTOR.participantList().value;
if (participantListValue) {
return JSON.parse(participantListValue);
}
return [];
};
/**
* Remove participant both in the table/form and in the form element.
*
* @param {string} selectionTypeValue
* @param {string} selectionValue
*/
const participantRemove = (selectionTypeValue, selectionValue) => {
const pList = getParticipantList();
const id = 'participant_list_tr_' + selectionTypeValue + '-' + selectionValue;
const participantListTable = ELEMENT_SELECTOR.participantTable();
const selectionid = (selectionValue === '' ? null : selectionValue);
for (let i = 0; i < pList.length; i++) {
if (pList[i].selectiontype === selectionTypeValue &&
pList[i].selectionid === selectionid) {
pList.splice(i, 1);
}
}
// Remove from the form.
for (let i = 0; i < participantListTable.rows.length; i++) {
if (participantListTable.rows[i].id === id) {
participantListTable.deleteRow(i);
}
}
// Update value in the form.
participantListUpdate(pList);
};
/**
* Role update
*
* @param {string} type
* @param {string} id
*/
const participantListRoleUpdate = (type, id) => {
// Update in memory.
const participantListRoleSelection = document.querySelector(`#participant_list_tr_${type}-${id} .participant-select`);
const pList = getParticipantList();
for (var i = 0; i < pList.length; i++) {
if (pList[i].selectiontype === type && pList[i].selectionid === id) {
pList[i].role = participantListRoleSelection.value;
}
}
// Update in the form.
participantListUpdate(pList);
};
/**
* Add participant from the currently selected options
*/
const participantAddFromCurrentSelection = () => {
let selectionType = ELEMENT_SELECTOR.participantSelectionType();
let selection = ELEMENT_SELECTOR.participantSelection();
const pList = getParticipantList();
// Lookup to see if it has been added already.
for (var i = 0; i < pList.length; i++) {
if (pList[i].selectiontype === selectionType.value &&
pList[i].selectionid === selection.value) {
return;
}
}
pList.push({
"selectiontype": selectionType.value,
"selectionid": selection.value,
"role": "viewer"
});
// Add it to the form.
participantAddToForm(selectionType.value, selection.value, 'viewer', true).then();
// Update in the form.
participantListUpdate(pList);
};
/**
* Update selectable options when changing types
*
* @param {HTMLNode} currentTypeSelect
*/
const updateSelectionFromType = (currentTypeSelect) => {
const createNewOption = (selectItem, label, value) => {
const option = document.createElement('option');
option.text = label;
option.value = value;
selectItem.add(option);
};
const participantData = JSON.parse(ELEMENT_SELECTOR.participantData().dataset.participantData);
// Clear all selection items.
const participantSelect = ELEMENT_SELECTOR.participantSelection();
while (participantSelect.firstChild) {
participantSelect.removeChild(participantSelect.firstChild);
}
// Add options depending on the selection.
if (currentTypeSelect.selectedIndex !== -1) {
const options = Object.values(participantData[currentTypeSelect.value].children);
options.forEach(option => {
createNewOption(participantSelect, option.name, option.id);
});
if (currentTypeSelect.value === 'all') {
createNewOption(participantSelect, '---------------', 'all');
participantSelect.disabled = true;
} else {
participantSelect.disabled = false;
}
}
};
+400
View File
@@ -0,0 +1,400 @@
// 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/>.
/**
* JS for the recordings page on mod_bigbluebuttonbn plugin.
*
* @module mod_bigbluebuttonbn/recordings
* @copyright 2021 Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import * as repository from './repository';
import {exception as displayException, saveCancelPromise} from 'core/notification';
import {prefetchStrings} from 'core/prefetch';
import {getString, getStrings} from 'core/str';
import {addIconToContainerWithPromise} from 'core/loadingicon';
import Pending from 'core/pending';
const stringsWithKeys = {
first: 'view_recording_yui_first',
prev: 'view_recording_yui_prev',
next: 'view_recording_yui_next',
last: 'view_recording_yui_last',
goToLabel: 'view_recording_yui_page',
goToAction: 'view_recording_yui_go',
perPage: 'view_recording_yui_rows',
showAll: 'view_recording_yui_show_all',
};
// Load global strings.
prefetchStrings('bigbluebuttonbn', Object.entries(stringsWithKeys).map((entry) => entry[1]));
const getStringsForYui = () => {
const stringMap = Object.keys(stringsWithKeys).map(key => {
return {
key: stringsWithKeys[key],
component: 'mod_bigbluebuttonbn',
};
});
// Return an object with the matching string keys (we want an object with {<stringkey>: <stringvalue>...}).
return getStrings(stringMap)
.then((stringArray) => Object.assign(
{},
...Object.keys(stringsWithKeys).map(
(key, index) => ({[key]: stringArray[index]})
)
));
};
const getYuiInstance = lang => new Promise(resolve => {
// eslint-disable-next-line
YUI({
lang,
}).use('intl', 'datatable', 'datatable-sort', 'datatable-paginator', 'datatype-number', Y => {
resolve(Y);
});
});
/**
* Format the supplied date per the specified locale.
*
* @param {string} locale
* @param {number} date
* @returns {array}
*/
const formatDate = (locale, date) => {
const realDate = new Date(date);
return realDate.toLocaleDateString(locale, {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
});
};
/**
* Format response data for the table.
*
* @param {string} response JSON-encoded table data
* @returns {array}
*/
const getFormattedData = response => {
const recordingData = response.tabledata;
return JSON.parse(recordingData.data);
};
const getTableNode = tableSelector => document.querySelector(tableSelector);
const fetchRecordingData = tableSelector => {
const tableNode = getTableNode(tableSelector);
if (tableNode === null) {
return Promise.resolve(false);
}
if (tableNode.dataset.importMode) {
return repository.fetchRecordingsToImport(
tableNode.dataset.bbbid,
tableNode.dataset.bbbSourceInstanceId,
tableNode.dataset.bbbSourceCourseId,
tableNode.dataset.tools,
tableNode.dataset.groupId
);
} else {
return repository.fetchRecordings(
tableNode.dataset.bbbid,
tableNode.dataset.tools,
tableNode.dataset.groupId
);
}
};
/**
* Fetch the data table functinos for the specified table.
*
* @param {String} tableId in which we will display the table
* @param {String} searchFormId The Id of the relate.
* @param {Object} dataTable
* @returns {Object}
* @private
*/
const getDataTableFunctions = (tableId, searchFormId, dataTable) => {
const tableNode = getTableNode(tableId);
const bbbid = tableNode.dataset.bbbid;
const updateTableFromResponse = response => {
if (!response || !response.status) {
// There was no output at all.
return;
}
dataTable.get('data').reset(getFormattedData(response));
dataTable.set(
'currentData',
dataTable.get('data')
);
const currentFilter = dataTable.get('currentFilter');
if (currentFilter) {
filterByText(currentFilter);
}
};
const refreshTableData = () => fetchRecordingData(tableId).then(updateTableFromResponse);
const filterByText = value => {
const dataModel = dataTable.get('currentData');
dataTable.set('currentFilter', value);
const escapedRegex = value.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
const rsearch = new RegExp(`<span>.*?${escapedRegex}.*?</span>`, 'i');
dataTable.set('data', dataModel.filter({asList: true}, item => {
const name = item.get('recording');
if (name && rsearch.test(name)) {
return true;
}
const description = item.get('description');
return description && rsearch.test(description);
}));
};
const requestAction = async(element) => {
const getDataFromAction = (element, dataType) => {
const dataElement = element.closest(`[data-${dataType}]`);
if (dataElement) {
return dataElement.dataset[dataType];
}
return null;
};
const elementData = element.dataset;
const payload = {
bigbluebuttonbnid: bbbid,
recordingid: getDataFromAction(element, 'recordingid'),
additionaloptions: getDataFromAction(element, 'additionaloptions'),
action: elementData.action,
};
// Slight change for import, for additional options.
if (!payload.additionaloptions) {
payload.additionaloptions = {};
}
if (elementData.action === 'import') {
const bbbsourceid = getDataFromAction(element, 'source-instance-id');
const bbbcourseid = getDataFromAction(element, 'source-course-id');
if (!payload.additionaloptions) {
payload.additionaloptions = {};
}
payload.additionaloptions.sourceid = bbbsourceid ? bbbsourceid : 0;
payload.additionaloptions.bbbcourseid = bbbcourseid ? bbbcourseid : 0;
}
// Now additional options should be a json string.
payload.additionaloptions = JSON.stringify(payload.additionaloptions);
if (element.dataset.requireConfirmation === "1") {
// Create the confirmation dialogue.
try {
await saveCancelPromise(
getString('confirm'),
recordingConfirmationMessage(payload),
getString('ok', 'moodle'),
);
} catch {
// User cancelled the dialogue.
return;
}
}
return repository.updateRecording(payload);
};
const recordingConfirmationMessage = async(data) => {
const playbackElement = document.querySelector(`#playbacks-${data.recordingid}`);
const recordingType = await getString(
playbackElement.dataset.imported === 'true' ? 'view_recording_link' : 'view_recording',
'bigbluebuttonbn'
);
const confirmation = await getString(`view_recording_${data.action}_confirmation`, 'bigbluebuttonbn', recordingType);
if (data.action === 'import') {
return confirmation;
}
// If it has associated links imported in a different course/activity, show that in confirmation dialog.
const associatedLinkCount = document.querySelector(`a#recording-${data.action}-${data.recordingid}`)?.dataset?.links;
if (!associatedLinkCount || associatedLinkCount === 0) {
return confirmation;
}
const confirmationWarning = await getString(
associatedLinkCount === 1
? `view_recording_${data.action}_confirmation_warning_p`
: `view_recording_${data.action}_confirmation_warning_s`,
'bigbluebuttonbn',
associatedLinkCount
);
return confirmationWarning + '\n\n' + confirmation;
};
/**
* Process an action event.
*
* @param {Event} e
*/
const processAction = e => {
const popoutLink = e.target.closest('[data-action="play"]');
if (popoutLink) {
e.preventDefault();
const videoPlayer = window.open('', '_blank');
videoPlayer.opener = null;
videoPlayer.location.href = popoutLink.href;
// TODO send a recording viewed event when this event will be implemented.
return;
}
// Fetch any clicked anchor.
const clickedLink = e.target.closest('a[data-action]');
if (clickedLink && !clickedLink.classList.contains('disabled')) {
e.preventDefault();
// Create a spinning icon on the table.
const iconPromise = addIconToContainerWithPromise(dataTable.get('boundingBox').getDOMNode());
requestAction(clickedLink)
.then(refreshTableData)
.then(iconPromise.resolve)
.catch(displayException);
}
};
const processSearchSubmission = e => {
// Prevent the default action.
e.preventDefault();
const parentNode = e.target.closest('div[role=search]');
const searchInput = parentNode.querySelector('input[name=search]');
filterByText(searchInput.value);
};
const registerEventListeners = () => {
// Add event listeners to the table boundingBox.
const boundingBox = dataTable.get('boundingBox').getDOMNode();
boundingBox.addEventListener('click', processAction);
// Setup the search from handlers.
const searchForm = document.querySelector(searchFormId);
if (searchForm) {
const searchButton = document.querySelector(searchFormId + ' button');
searchButton.addEventListener('click', processSearchSubmission);
}
};
return {
filterByText,
refreshTableData,
registerEventListeners,
};
};
/**
* Setup the data table for the specified BBB instance.
*
* @param {String} tableId in which we will display the table
* @param {String} searchFormId The Id of the relate.
* @param {object} response The response from the data request
* @returns {Promise}
*/
const setupDatatable = (tableId, searchFormId, response) => {
if (!response) {
return Promise.resolve();
}
if (!response.status) {
// Something failed. Continue to show the plain output.
return Promise.resolve();
}
const recordingData = response.tabledata;
const pendingPromise = new Pending('mod_bigbluebuttonbn/recordings/setupDatatable');
return Promise.all([getYuiInstance(recordingData.locale), getStringsForYui()])
.then(([yuiInstance, strings]) => {
// Here we use a custom formatter for date.
// See https://clarle.github.io/yui3/yui/docs/api/classes/DataTable.BodyView.Formatters.html
// Inspired from examples here: https://clarle.github.io/yui3/yui/docs/datatable/
// Normally formatter have the prototype: (col) => (cell) => <computed value>, see:
// https://clarle.github.io/yui3/yui/docs/api/files/datatable_js_formatters.js.html#l100 .
const dateCustomFormatter = () => (cell) => formatDate(recordingData.locale, cell.value);
// Add the fetched strings to the YUI Instance.
yuiInstance.Intl.add('datatable-paginator', yuiInstance.config.lang, {...strings});
yuiInstance.DataTable.BodyView.Formatters.customDate = dateCustomFormatter;
return yuiInstance;
})
.then(yuiInstance => {
const tableData = getFormattedData(response);
yuiInstance.RecordsPaginatorView = Y.Base.create('my-paginator-view', yuiInstance.DataTable.Paginator.View, [], {
_modelChange: function(e) {
var changed = e.changed,
totalItems = (changed && changed.totalItems);
if (totalItems) {
this._updateControlsUI(e.target.get('page'));
}
}
});
return new yuiInstance.DataTable({
paginatorView: "RecordsPaginatorView",
width: "1195px",
columns: recordingData.columns,
data: tableData,
rowsPerPage: 10,
paginatorLocation: ['header', 'footer'],
autoSync: true
});
})
.then(dataTable => {
dataTable.render(tableId);
const {registerEventListeners} = getDataTableFunctions(
tableId,
searchFormId,
dataTable);
registerEventListeners();
return dataTable;
})
.then(dataTable => {
pendingPromise.resolve();
return dataTable;
});
};
/**
* Initialise recordings code.
*
* @method init
* @param {String} tableId in which we will display the table
* @param {String} searchFormId The Id of the relate.
*/
export const init = (tableId, searchFormId) => {
const pendingPromise = new Pending('mod_bigbluebuttonbn/recordings:init');
fetchRecordingData(tableId)
.then(response => setupDatatable(tableId, searchFormId, response))
.then(() => pendingPromise.resolve())
.catch(displayException);
};
+141
View File
@@ -0,0 +1,141 @@
// 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/>.
/**
* Repository to perform WS calls for mod_bigbluebuttonbn.
*
* @module mod_bigbluebuttonbn/repository
* @copyright 2021 Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import {call as fetchMany} from 'core/ajax';
/**
* Fetch the list of recordings from the server.
*
* @param {Number} bigbluebuttonbnid The instance ID
* @param {String} tools the set of tools to display
* @param {number} groupid
* @returns {Promise}
*/
export const fetchRecordings = (bigbluebuttonbnid, tools, groupid) => {
const args = {
bigbluebuttonbnid,
tools,
};
if (groupid) {
args.groupid = groupid;
}
return fetchMany([{methodname: 'mod_bigbluebuttonbn_get_recordings', args}])[0];
};
/**
* Fetch the list of recordings from the server that can be imported.
*
* @param {Number} destinationinstanceid The destination instance ID
* @param {Number} sourcebigbluebuttonbnid The original instance ID
* @param {Number} sourcecourseid The destination instance ID
* @param {String} tools the set of tools to display
* @param {number} groupid
* @returns {Promise}
*/
export const fetchRecordingsToImport = (
destinationinstanceid,
sourcebigbluebuttonbnid,
sourcecourseid,
tools,
groupid
) => {
const args = {
destinationinstanceid,
sourcebigbluebuttonbnid,
sourcecourseid,
tools,
};
if (groupid) {
args.groupid = groupid;
}
return fetchMany([{methodname: 'mod_bigbluebuttonbn_get_recordings_to_import', args}])[0];
};
/**
* Perform an update on a single recording.
*
* @param {object} args The instance ID
* @returns {Promise}
*/
export const updateRecording = args => fetchMany([
{
methodname: 'mod_bigbluebuttonbn_update_recording',
args,
}
])[0];
/**
* End the Meeting
*
* @param {number} bigbluebuttonbnid
* @param {number} groupid
* @returns {Promise}
*/
export const endMeeting = (bigbluebuttonbnid, groupid) => fetchMany([
{
methodname: 'mod_bigbluebuttonbn_end_meeting',
args: {
bigbluebuttonbnid,
groupid
},
}
])[0];
/**
* Validate completion.
*
* @param {number} bigbluebuttonbnid
* @returns {Promise}
*/
export const completionValidate = (bigbluebuttonbnid) => fetchMany([
{
methodname: 'mod_bigbluebuttonbn_completion_validate',
args: {
bigbluebuttonbnid
},
}
])[0];
/**
* Fetch meeting info for the specified meeting.
*
* @param {number} bigbluebuttonbnid
* @param {number} groupid
* @param {boolean} [updatecache=false]
* @returns {Promise}
*/
export const getMeetingInfo = (bigbluebuttonbnid, groupid, updatecache = false) => fetchMany([
{
methodname: 'mod_bigbluebuttonbn_meeting_info',
args: {
bigbluebuttonbnid,
groupid,
updatecache,
},
}
])[0];
+100
View File
@@ -0,0 +1,100 @@
// 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/>.
/**
* JS actions for the rooms page for mod_bigbluebuttonbn.
*
* @module mod_bigbluebuttonbn/rooms
* @copyright 2021 Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import './actions';
import * as repository from './repository';
import * as roomUpdater from './roomupdater';
import {
exception as displayException,
fetchNotifications,
} from 'core/notification';
import Pending from 'core/pending';
import {getString} from 'core/str';
import {add as addToast} from 'core/toast';
import {eventTypes, notifyCurrentSessionEnded} from './events';
/**
* Init the room
*
* @param {Number} bigbluebuttonbnid bigblubeutton identifier
* @param {Number} pollInterval poll interval in miliseconds
*/
export const init = (bigbluebuttonbnid, pollInterval) => {
const completionElement = document.querySelector('a[href*=completion_validate]');
if (completionElement) {
completionElement.addEventListener("click", event => {
event.preventDefault();
const pendingPromise = new Pending('mod_bigbluebuttonbn/completion:validate');
repository.completionValidate(bigbluebuttonbnid)
.then(() => getString('completionvalidatestatetriggered', 'mod_bigbluebuttonbn'))
.then(str => addToast(str))
.then(() => pendingPromise.resolve())
.catch(displayException);
});
}
document.addEventListener('click', e => {
const joinButton = e.target.closest('[data-action="join"]');
if (joinButton) {
window.open(joinButton.href, 'bigbluebutton_conference');
e.preventDefault();
// Gives the user a bit of time to go into the meeting before polling the room.
setTimeout(() => {
roomUpdater.updateRoom(true);
}, pollInterval);
}
});
document.addEventListener(eventTypes.sessionEnded, () => {
roomUpdater.stop();
roomUpdater.updateRoom();
fetchNotifications();
});
window.addEventListener(eventTypes.currentSessionEnded, () => {
roomUpdater.stop();
roomUpdater.updateRoom();
fetchNotifications();
});
// Room update.
roomUpdater.start(pollInterval);
};
/**
* Auto close child windows when clicking the End meeting button.
* @param {Number} closeDelay time to wait in miliseconds before closing the window
*/
export const setupWindowAutoClose = (closeDelay = 2000) => {
notifyCurrentSessionEnded(window.opener);
window.addEventListener('onbeforeunload', () => {
window.opener.setTimeout(() => {
roomUpdater.updateRoom(true);
}, closeDelay);
},
{
once: true
});
window.close(); // This does not work as scripts can only close windows that are opened by themselves.
};
+110
View File
@@ -0,0 +1,110 @@
// 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/>.
/**
* JS room updater.
*
* @module mod_bigbluebuttonbn/roomupdater
* @copyright 2021 Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import Pending from 'core/pending';
import Templates from "core/templates";
import {exception as displayException} from 'core/notification';
import {getMeetingInfo} from './repository';
let timerReference = null;
let timerRunning = false;
let pollInterval = 0;
let pollIntervalFactor = 1;
const MAX_POLL_INTERVAL_FACTOR = 10;
const resetValues = () => {
timerRunning = false;
timerReference = null;
pollInterval = 0;
pollIntervalFactor = 1;
};
/**
* Start the information poller.
* @param {Number} interval interval in miliseconds between each poll action.
*/
export const start = (interval) => {
resetValues();
timerRunning = true;
pollInterval = interval;
poll();
};
/**
* Stop the room updater.
*/
export const stop = () => {
if (timerReference) {
clearTimeout(timerReference);
}
resetValues();
};
/**
* Start the information poller.
*/
const poll = () => {
if (!timerRunning || !pollInterval) {
// The poller has been stopped.
return;
}
updateRoom()
.then((updateOk) => {
if (!updateOk) {
pollIntervalFactor = (pollIntervalFactor < MAX_POLL_INTERVAL_FACTOR) ?
pollIntervalFactor + 1 : MAX_POLL_INTERVAL_FACTOR;
// We make sure if there is an error that we do not try too often.
}
timerReference = setTimeout(() => poll(), pollInterval * pollIntervalFactor);
return true;
})
.catch();
};
/**
* Update the room information.
*
* @param {boolean} [updatecache=false] should we update cache
* @returns {Promise}
*/
export const updateRoom = (updatecache = false) => {
const bbbRoomViewElement = document.getElementById('bigbluebuttonbn-room-view');
if (bbbRoomViewElement === null) {
return Promise.resolve(false);
}
const bbbId = bbbRoomViewElement.dataset.bbbId;
const groupId = bbbRoomViewElement.dataset.groupId;
const pendingPromise = new Pending('mod_bigbluebuttonbn/roomupdater:updateRoom');
return getMeetingInfo(bbbId, groupId, updatecache)
.then(data => {
// Just make sure we have the right information for the template.
data.haspresentations = !!(data.presentations && data.presentations.length);
return Templates.renderForPromise('mod_bigbluebuttonbn/room_view', data);
})
.then(({html, js}) => Templates.replaceNode(bbbRoomViewElement, html, js))
.then(() => pendingPromise.resolve())
.catch(displayException);
};
@@ -0,0 +1,80 @@
<?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/>.
/**
* Class for backup BigBlueButtonBN.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Fred Dixon (ffdixon [at] blindsidenetworks [dt] com)
* @author Jesus Federico (jesus [at] blindsidenetworks [dt] com)
*/
defined('MOODLE_INTERNAL') || die;
global $CFG;
require_once($CFG->dirroot.'/mod/bigbluebuttonbn/backup/moodle2/backup_bigbluebuttonbn_stepslib.php');
/**
* Backup task that provides all the settings and steps to perform one complete backup of the activity.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class backup_bigbluebuttonbn_activity_task extends backup_activity_task {
/**
* Define (add) particular settings this activity can have.
*
* @return void
*/
protected function define_my_settings() {
// No particular settings for this activity.
}
/**
* Define (add) particular steps this activity can have.
*
* @return void
*/
protected function define_my_steps() {
// Choice only has one structure step.
$this->add_step(new backup_bigbluebuttonbn_activity_structure_step('bigbluebuttonbn_structure', 'bigbluebuttonbn.xml'));
}
/**
* Code the transformations to perform in the activity in order to get transportable (encoded) links.
*
* @param string $content
*
* @return string
*/
public static function encode_content_links($content) {
global $CFG;
$base = preg_quote($CFG->wwwroot.'/mod/bigbluebuttonbn', '#');
// Link to the list of bigbluebuttonbns.
$pattern = '#('.$base."\/index.php\?id\=)([0-9]+)#";
$content = preg_replace($pattern, '$@BIGBLUEBUTTONBNINDEX*$2@$', $content);
// Link to bigbluebuttonbn view by moduleid.
$pattern = '#('.$base."\/view.php\?id\=)([0-9]+)#";
$content = preg_replace($pattern, '$@BIGBLUEBUTTONBNVIEWBYID*$2@$', $content);
return $content;
}
}
@@ -0,0 +1,93 @@
<?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/>.
/**
* Class for the structure used for backup BigBlueButtonBN.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Fred Dixon (ffdixon [at] blindsidenetworks [dt] com)
* @author Jesus Federico (jesus [at] blindsidenetworks [dt] com)
*/
/**
* Define all the backup steps that will be used by the backup_bigbluebuttonbn_activity_task.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class backup_bigbluebuttonbn_activity_structure_step extends backup_activity_structure_step {
/**
* Define the complete bigbluebuttonbn structure for backup, with file and id annotations.
*
* @return object
*/
protected function define_structure() {
// To know if we are including userinfo.
$userinfo = $this->get_setting_value('userinfo');
// Define each element separated.
$bigbluebuttonbn = new backup_nested_element('bigbluebuttonbn', ['id'], [
'type', 'course', 'name', 'intro', 'introformat', 'meetingid',
'moderatorpass', 'viewerpass', 'wait', 'record', 'recordallfromstart',
'recordhidebutton', 'welcome', 'voicebridge', 'openingtime', 'closingtime', 'timecreated',
'timemodified', 'presentation', 'participants', 'userlimit',
'recordings_html', 'recordings_deleted', 'recordings_imported', 'recordings_preview',
'clienttype', 'muteonstart', 'completionattendance',
'completionengagementchats', 'completionengagementtalks', 'completionengagementraisehand',
'completionengagementpollvotes', 'completionengagementemojis',
'guestallowed', 'mustapproveuser']);
$logs = new backup_nested_element('logs');
$log = new backup_nested_element('log', ['id'], [
'courseid', 'bigbluebuttonbnid', 'userid', 'timecreated', 'meetingid', 'log', 'meta']);
$recordings = new backup_nested_element('recordings');
$recording = new backup_nested_element('recording', ['id'], [
'courseid', 'bigbluebuttonbnid', 'groupid', 'recordingid', 'headlesss', 'imported', 'status', 'importeddata',
'timecreated']);
// Build the tree.
$bigbluebuttonbn->add_child($logs);
$logs->add_child($log);
$bigbluebuttonbn->add_child($recordings);
$recordings->add_child($recording);
// Define sources.
$bigbluebuttonbn->set_source_table('bigbluebuttonbn', ['id' => backup::VAR_ACTIVITYID]);
// This source definition only happen if we are including user info.
if ($userinfo) {
$log->set_source_table('bigbluebuttonbn_logs', ['bigbluebuttonbnid' => backup::VAR_PARENTID]);
$recording->set_source_table('bigbluebuttonbn_recordings', ['bigbluebuttonbnid' => backup::VAR_PARENTID]);
}
// Define id annotations.
$log->annotate_ids('user', 'userid');
// Define file annotations.
$bigbluebuttonbn->annotate_files('mod_bigbluebuttonbn', 'intro', null);
$this->add_subplugin_structure('bbbext', $bigbluebuttonbn, true);
// Return the root element (bigbluebuttonbn), wrapped into standard activity structure.
return $this->prepare_activity_structure($bigbluebuttonbn);
}
}
@@ -0,0 +1,107 @@
<?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/>.
/**
* Class for restore BigBlueButtonBN.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Fred Dixon (ffdixon [at] blindsidenetworks [dt] com)
* @author Jesus Federico (jesus [at] blindsidenetworks [dt] com)
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->dirroot.'/mod/bigbluebuttonbn/backup/moodle2/restore_bigbluebuttonbn_stepslib.php');
/**
* Restore task that provides all the settings and steps to perform one complete restore of the activity.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class restore_bigbluebuttonbn_activity_task extends restore_activity_task {
/**
* Define (add) particular settings this activity can have.
*
* @return void
*/
protected function define_my_settings() {
// No particular settings for this activity.
}
/**
* Define (add) particular steps this activity can have.
*
* @return void
*/
protected function define_my_steps() {
// BigBlueButtonBN only has one structure step.
$this->add_step(new restore_bigbluebuttonbn_activity_structure_step('bigbluebuttonbn_structure', 'bigbluebuttonbn.xml'));
}
/**
* Define the contents in the activity that must be processed by the link decoder.
*
* @return array
*/
public static function define_decode_contents() {
$contents = [];
$contents[] = new restore_decode_content('bigbluebuttonbn', ['intro'], 'bigbluebuttonbn');
$contents[] = new restore_decode_content('bigbluebuttonbn_logs', ['log'], 'bigbluebuttonbn_logs');
$contents[] = new restore_decode_content('bigbluebuttonbn_recordings', ['importeddata'], 'bigbluebuttonbn_recordings');
return $contents;
}
/**
* Define the decoding rules for links belonging to the activity to be executed by the link decoder.
*
* @return array
*/
public static function define_decode_rules() {
$rules = [];
$rules[] = new restore_decode_rule('BIGBLUEBUTTONBNVIEWBYID', '/mod/bigbluebuttonbn/view.php?id=$1', 'course_module');
$rules[] = new restore_decode_rule('BIGBLUEBUTTONBNINDEX', '/mod/bigbluebuttonbn/index.php?id=$1', 'course');
return $rules;
}
/**
* Define the restoring rules for logs belonging to the activity to be executed by the link decoder.
*
* @return array
*/
public static function define_restore_log_rules() {
$rules = [];
$rules[] = new restore_log_rule('bigbluebuttonbn', 'add', 'view.php?id={course_module}', '{bigbluebuttonbn}');
$rules[] = new restore_log_rule('bigbluebuttonbn', 'update', 'view.php?id={course_module}', '{bigbluebuttonbn}');
$rules[] = new restore_log_rule('bigbluebuttonbn', 'view', 'view.php?id={course_module}', '{bigbluebuttonbn}');
$rules[] = new restore_log_rule('bigbluebuttonbn', 'report', 'report.php?id={course_module}', '{bigbluebuttonbn}');
return $rules;
}
/**
* Define the restoring rules for course associated to the activity to be executed by the link decoder.
*
* @return array
*/
public static function define_restore_log_rules_for_course() {
$rules = [];
$rules[] = new restore_log_rule('bigbluebuttonbn', 'view all', 'index.php?id={course}', null);
return $rules;
}
}
@@ -0,0 +1,121 @@
<?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/>.
/**
* Class for the structure used for restore BigBlueButtonBN.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Fred Dixon (ffdixon [at] blindsidenetworks [dt] com)
* @author Jesus Federico (jesus [at] blindsidenetworks [dt] com)
*/
/**
* Define all the restore steps that will be used by the restore_url_activity_task.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class restore_bigbluebuttonbn_activity_structure_step extends restore_activity_structure_step {
/**
* Structure step to restore one bigbluebuttonbn activity.
*
* @return array
*/
protected function define_structure() {
$paths = [];
$bbb = new restore_path_element('bigbluebuttonbn', '/activity/bigbluebuttonbn');
$paths[] = $bbb;
$paths[] = new restore_path_element('bigbluebuttonbn_logs', '/activity/bigbluebuttonbn/logs/log');
$paths[] = new restore_path_element('bigbluebuttonbn_recordings', '/activity/bigbluebuttonbn/recordings/recording');
$this->add_subplugin_structure('bbbext', $bbb);
// Return the paths wrapped into standard activity structure.
return $this->prepare_activity_structure($paths);
}
/**
* Process a bigbluebuttonbn restore.
*
* @param array $data The data in object form
* @return void
*/
protected function process_bigbluebuttonbn(array $data) {
global $DB;
$data = (object) $data;
$data->course = $this->get_courseid();
$data->timemodified = $this->apply_date_offset($data->timemodified);
// Check if we are in backup::MODE_IMPORT (we set a new meetingid) or backup::MODE_GENERAL (we keep the same meetingid).
if ($this->get_task()->get_info()->mode == backup::MODE_IMPORT || empty($data->meetingid)) {
// We are in backup::MODE_IMPORT, we need to renew the meetingid.
$data->meetingid = \mod_bigbluebuttonbn\meeting::get_unique_meetingid_seed();
}
// Insert the bigbluebuttonbn record.
$newitemid = $DB->insert_record('bigbluebuttonbn', $data);
// Immediately after inserting "activity" record, call this.
$this->apply_activity_instance($newitemid);
}
/**
* Process a bigbluebuttonbn_logs restore (additional table).
*
* @param array $data The data in object form
* @return void
*/
protected function process_bigbluebuttonbn_logs(array $data) {
global $DB;
$data = (object) $data;
// Apply modifications.
$data->courseid = $this->get_mappingid('course', $data->courseid);
$data->bigbluebuttonbnid = $this->get_new_parentid('bigbluebuttonbn');
$data->userid = $this->get_mappingid('user', $data->userid);
$data->timecreated = $this->apply_date_offset($data->timecreated);
// Insert the bigbluebuttonbn_logs record.
$newitemid = $DB->insert_record('bigbluebuttonbn_logs', $data);
// Immediately after inserting associated record, call this.
$this->set_mapping('bigbluebuttonbn_logs', $data->id, $newitemid);
}
/**
* Process a bigbluebuttonbn_recordings restore (additional table).
*
* @param array $data The data in object form
* @return void
*/
protected function process_bigbluebuttonbn_recordings(array $data) {
global $DB;
$data = (object) $data;
// Apply modifications.
$data->courseid = $this->get_mappingid('course', $data->courseid);
$data->bigbluebuttonbnid = $this->get_new_parentid('bigbluebuttonbn');
$data->timecreated = $this->apply_date_offset($data->timecreated);
// Insert the bigbluebuttonbn_recordings record.
$newitemid = $DB->insert_record('bigbluebuttonbn_recordings', $data);
// Immediately after inserting associated record, call this.
$this->set_mapping('bigbluebuttonbn_recordings', $data->id, $newitemid);
}
/**
* Actions to be executed after the restore is completed
*
* @return void
*/
protected function after_execute() {
// Add bigbluebuttonbn related files, no need to match by itemname (just internally handled context).
$this->add_related_files('mod_bigbluebuttonbn', 'intro', null);
}
}
+72
View File
@@ -0,0 +1,72 @@
<?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/>.
/**
* Intermediator for handling requests from the BigBlueButton server.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Jesus Federico (jesus [at] blindsidenetworks [dt] com)
* @author Darko Miletic (darko.miletic [at] gmail [dt] com)
*/
// We should not have any require login or MOODLE_INTERNAL Check in this file.
// phpcs:disable moodle.Files.MoodleInternal.MoodleInternalGlobalState,moodle.Files.RequireLogin.Missing
require(__DIR__ . '/../../config.php');
use Firebase\JWT\Key;
use mod_bigbluebuttonbn\broker;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\local\config;
use mod_bigbluebuttonbn\meeting;
global $PAGE, $USER, $CFG, $SESSION, $DB;
$params = $_REQUEST;
$broker = new broker();
$error = $broker->validate_parameters($params);
if (!empty($error)) {
header('HTTP/1.0 400 Bad Request. ' . $error);
return;
}
$action = $params['action'];
$instance = instance::get_from_instanceid($params['bigbluebuttonbn']);
if (empty($instance)) {
header('HTTP/1.0 410 Gone. The activity may have been deleted');
return;
}
$PAGE->set_context($instance->get_context());
try {
switch (strtolower($action)) {
case 'recording_ready':
broker::process_recording_ready($instance, $params);
return;
case 'meeting_events':
// When meeting_events callback is implemented by BigBlueButton, Moodle receives a POST request
// which is processed in the function using super globals.
broker::process_meeting_events($instance);
return;
}
header("HTTP/1.0 400 Bad request. The action '{$action}' does not exist");
} catch (Exception $e) {
header('HTTP/1.0 500 Internal Server Error. ' . $e->getMessage());
}
+165
View File
@@ -0,0 +1,165 @@
<?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/>.
/**
* View for BigBlueButton interaction.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Jesus Federico (jesus [at] blindsidenetworks [dt] com)
*/
use core\notification;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\local\exceptions\server_not_available_exception;
use mod_bigbluebuttonbn\local\proxy\bigbluebutton_proxy;
use mod_bigbluebuttonbn\logger;
use mod_bigbluebuttonbn\meeting;
use mod_bigbluebuttonbn\plugin;
use mod_bigbluebuttonbn\recording;
require_once(dirname(dirname(dirname(__FILE__))) . '/config.php');
global $SESSION, $PAGE, $CFG, $DB, $USER, $OUTPUT;
$action = required_param('action', PARAM_TEXT);
$id = optional_param('id', 0, PARAM_INT);
$bn = optional_param('bn', 0, PARAM_INT);
$rid = optional_param('rid', '', PARAM_TEXT);
$rtype = optional_param('rtype', 'presentation', PARAM_TEXT);
$errors = optional_param('errors', '', PARAM_TEXT);
$timeline = optional_param('timeline', 0, PARAM_INT);
$index = optional_param('index', 0, PARAM_INT);
$group = optional_param('group', -1, PARAM_INT);
// Get the bbb instance from either the cmid (id), or the instanceid (bn).
if ($id) {
$instance = instance::get_from_cmid($id);
} else {
if ($bn) {
$instance = instance::get_from_instanceid($bn);
}
}
if (!$instance) {
$courseid = optional_param('courseid', 1, PARAM_INT);
\core\notification::error(get_string('general_error_not_found', 'mod_bigbluebuttonbn', $id));
redirect(new moodle_url('/course/view.php', ['id' => $courseid]));
}
$cm = $instance->get_cm();
$course = $instance->get_course();
$bigbluebuttonbn = $instance->get_instance_data();
$context = $instance->get_context();
require_login($course, true, $cm);
// Note : this uses the group optional_param as a value to decide which groupid.
$groupid = groups_get_activity_group($cm, true) ?: null;
if ($groupid) {
$instance->set_group_id($groupid);
}
// Print the page header.
$PAGE->set_context($context);
$PAGE->set_url('/mod/bigbluebuttonbn/bbb_view.php', ['id' => $cm->id, 'bigbluebuttonbn' => $bigbluebuttonbn->id]);
$PAGE->set_title(format_string($bigbluebuttonbn->name));
$PAGE->set_cacheable(false);
$PAGE->set_heading($course->fullname);
$PAGE->blocks->show_only_fake_blocks();
switch (strtolower($action)) {
case 'logout':
if (isset($errors) && $errors != '') {
$errors = (array) json_decode(urldecode($errors));
$msgerrors = '';
foreach ($errors as $error) {
$msgerrors .= html_writer::tag('p', $error->{'message'}, ['class' => 'alert alert-danger']) . "\n";
}
throw new moodle_exception('view_error_bigbluebutton', 'bigbluebuttonbn',
$CFG->wwwroot . '/mod/bigbluebuttonbn/view.php?id=' . $id, $msgerrors, $errors);
}
if (empty($bigbluebuttonbn)) {
echo get_string('view_message_tab_close', 'bigbluebuttonbn');
die();
}
// Moodle event logger: Create an event for meeting left.
logger::log_meeting_left_event($instance);
// Update the cache.
$meeting = new meeting($instance);
$meeting->update_cache();
// Check the origin page.
$select = "userid = ? AND log = ?";
$params = [
'userid' => $USER->id,
'log' => logger::EVENT_JOIN,
];
$accesses = $DB->get_records_select('bigbluebuttonbn_logs', $select, $params, 'id ASC', 'id, meta', 1);
$lastaccess = end($accesses);
if (!empty($lastaccess->meta)) {
$lastaccess = json_decode($lastaccess->meta);
// If the user acceded from Timeline it should be redirected to the Dashboard.
if (isset($lastaccess->origin) && $lastaccess->origin == logger::ORIGIN_TIMELINE) {
redirect($CFG->wwwroot . '/my/');
}
}
break;
case 'join':
if (empty($bigbluebuttonbn)) {
throw new moodle_exception('view_error_unable_join', 'bigbluebuttonbn');
break;
}
// Check the origin page.
$origin = logger::ORIGIN_BASE;
if ($timeline) {
$origin = logger::ORIGIN_TIMELINE;
} else if ($index) {
$origin = logger::ORIGIN_INDEX;
}
try {
$url = meeting::join_meeting($instance, $origin);
redirect($url);
} catch (server_not_available_exception $e) {
bigbluebutton_proxy::handle_server_not_available($instance);
}
// We should never reach this point.
break;
case 'play':
$recording = recording::get_record(['id' => $rid]);
if ($href = $recording->get_remote_playback_url($rtype)) {
logger::log_recording_played_event($instance, $rid);
redirect(urldecode($href));
} else {
notification::add(get_string('recordingurlnotfound', 'mod_bigbluebuttonbn'), notification::ERROR);
redirect($instance->get_view_url());
}
// We should never reach this point.
break;
}
// When we reach this point, we can close the tab or window where BBB was opened.
echo $OUTPUT->header();
// Behat does not like when we close the Windows as it is expecting to locate
// on click part of the pages (bug with selenium raising an exception). So this is a workaround.
if (!defined('BEHAT_SITE_RUNNING')) {
$PAGE->requires->js_call_amd('mod_bigbluebuttonbn/rooms', 'setupWindowAutoClose');
}
echo $OUTPUT->footer();
@@ -0,0 +1,47 @@
<?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/>.
/**
* Activity base class.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Jesus Federico (jesus [at] blindsidenetworks [dt] com)
*/
namespace mod_bigbluebuttonbn\analytics\indicator;
use core_analytics\local\indicator\community_of_inquiry_activity;
/**
* Activity base class.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class activity_base extends community_of_inquiry_activity {
/**
* No need to fetch grades for resources.
*
* @return bool
*/
public function feedback_check_grades() {
// BigBlueButtonBN's feedback is not contained in grades.
return false;
}
}
@@ -0,0 +1,70 @@
<?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/>.
/**
* Cognitive depth indicator - BigBlueButtonBN.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Jesus Federico (jesus [at] blindsidenetworks [dt] com)
*/
namespace mod_bigbluebuttonbn\analytics\indicator;
use cm_info;
use lang_string;
/**
* Cognitive depth indicator - bigbluebuttonbn.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class cognitive_depth extends activity_base {
/**
* Returns the name.
*
* If there is a corresponding '_help' string this will be shown as well.
*
* @return lang_string
*/
public static function get_name(): lang_string {
return new lang_string('indicator:cognitivedepth', 'mod_bigbluebuttonbn');
}
/**
* Returns the indicator type.
*
* @return string
*/
public function get_indicator_type() {
return self::INDICATOR_COGNITIVE;
}
/**
* Returns the cognitive depth level.
*
* @param cm_info $cm
*
* @return int
*/
public function get_cognitive_depth_level(cm_info $cm) {
return self::COGNITIVE_LEVEL_4;
}
}
@@ -0,0 +1,70 @@
<?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/>.
/**
* Social breadth indicator - BigBlueButtonBN.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Jesus Federico (jesus [at] blindsidenetworks [dt] com)
*/
namespace mod_bigbluebuttonbn\analytics\indicator;
use cm_info;
use lang_string;
/**
* Social breadth indicator - BigBlueButtonBN.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class social_breadth extends activity_base {
/**
* Returns the name.
*
* If there is a corresponding '_help' string this will be shown as well.
*
* @return lang_string
*/
public static function get_name(): lang_string {
return new lang_string('indicator:socialbreadth', 'mod_bigbluebuttonbn');
}
/**
* Returns the indicator type.
*
* @return string
*/
public function get_indicator_type() {
return self::INDICATOR_SOCIAL;
}
/**
* Returns the social breadth level.
*
* @param cm_info $cm
*
* @return int
*/
public function get_social_breadth_level(cm_info $cm) {
return self::SOCIAL_LEVEL_1;
}
}
+193
View File
@@ -0,0 +1,193 @@
<?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/>.
namespace mod_bigbluebuttonbn;
use Exception;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use mod_bigbluebuttonbn\local\config;
/**
* The broker routines
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Jesus Federico (jesus [at] blindsidenetworks [dt] com)
*/
class broker {
/** @var array List of required params */
protected $requiredparams = [
'recording_ready' => [
'bigbluebuttonbn' => 'The BigBlueButtonBN instance ID must be specified.',
'signed_parameters' => 'A JWT encoded string must be included as [signed_parameters].'
],
'meeting_events' => [
'bigbluebuttonbn' => 'The BigBlueButtonBN instance ID must be specified.'
],
];
/**
* Validate the supplied list of parameters, providing feedback about any missing or incorrect values.
*
* @param array $params
* @return null|string
*/
public function validate_parameters(array $params): ?string {
if (!isset($params['action']) || empty($params['action']) ) {
return 'Parameter ['.$params['action'].'] was not included';
}
$action = strtolower($params['action']);
if (!array_key_exists($action, $this->requiredparams)) {
return "Action {$params['action']} can not be performed.";
}
return $this->validate_parameters_message($params, $this->requiredparams[$action]);
}
/**
* Check whether the specified parameter is valid.
*
* @param array $params
* @param array $requiredparams
* @return null|string
*/
protected static function validate_parameters_message(array $params, array $requiredparams): ?string {
foreach ($requiredparams as $param => $message) {
if (!array_key_exists($param, $params) || $params[$param] == '') {
return $message;
}
}
// Everything is valid.
return null;
}
/**
* Helper for responding when recording ready is performed.
*
* @param instance $instance
* @param array $params
*/
public static function process_recording_ready(instance $instance, array $params): void {
// Decodes the received JWT string.
try {
$decodedparameters = JWT::decode(
$params['signed_parameters'],
new Key(config::get('shared_secret'), 'HS256')
);
} catch (Exception $e) {
$error = 'Caught exception: ' . $e->getMessage();
header('HTTP/1.0 400 Bad Request. ' . $error);
return;
}
// Validations.
if (!isset($decodedparameters->record_id)) {
header('HTTP/1.0 400 Bad request. Missing record_id parameter');
return;
}
$recording = recording::get_record(['recordingid' => $decodedparameters->record_id]);
if (!isset($recording)) {
header('HTTP/1.0 400 Bad request. Invalid record_id');
return;
}
// Sends the messages.
try {
// We make sure messages are sent only once.
if ($recording->get('status') != recording::RECORDING_STATUS_NOTIFIED) {
$task = new \mod_bigbluebuttonbn\task\send_recording_ready_notification();
$task->set_instance_id($instance->get_instance_id());
\core\task\manager::queue_adhoc_task($task);
$recording->set('status', recording::RECORDING_STATUS_NOTIFIED);
$recording->update();
}
header('HTTP/1.0 202 Accepted');
} catch (Exception $e) {
$error = 'Caught exception: ' . $e->getMessage();
header('HTTP/1.0 503 Service Unavailable. ' . $error);
}
}
/**
* Process meeting events for instance with provided HTTP headers.
*
* @param instance $instance
* @return void
*/
public static function process_meeting_events(instance $instance) {
try {
// Get the HTTP headers.
$authorization = self::get_authorization_token();
// Pull the Bearer from the headers.
if (empty($authorization)) {
$msg = 'Authorization failed';
header('HTTP/1.0 400 Bad Request. ' . $msg);
return;
}
// Verify the authenticity of the request.
$token = \Firebase\JWT\JWT::decode(
$authorization[1],
new Key(config::get('shared_secret'), 'HS512')
);
// Get JSON string from the body.
$jsonstr = file_get_contents('php://input');
// Convert JSON string to a JSON object.
$jsonobj = json_decode($jsonstr);
$headermsg = meeting::meeting_events($instance, $jsonobj);
header($headermsg);
} catch (Exception $e) {
$msg = 'Caught exception: ' . $e->getMessage();
header('HTTP/1.0 400 Bad Request. ' . $msg);
}
}
/**
* Get authorisation token
*
* We could use getallheaders but this is only compatible with apache types of servers
* some explanations and examples here: https://www.php.net/manual/en/function.getallheaders.php#127190
*
* @return array|null an array composed of the Authorization token provided in the header.
*/
private static function get_authorization_token(): ?array {
$autorization = null;
if (isset($_SERVER['Authorization'])) {
$autorization = trim($_SERVER["Authorization"]);
} else if (isset($_SERVER['HTTP_AUTHORIZATION'])) {
$autorization = trim($_SERVER["HTTP_AUTHORIZATION"]);
} else if (function_exists('apache_request_headers')) {
$requestheaders = apache_request_headers();
$requestheaders = array_combine(array_map('ucwords',
array_keys($requestheaders)), array_values($requestheaders));
if (isset($requestheaders['Authorization'])) {
$autorization = trim($requestheaders['Authorization']);
}
}
return empty($autorization) ? null : explode(" ", $autorization);
}
}
@@ -0,0 +1,328 @@
<?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/>.
namespace mod_bigbluebuttonbn\completion;
use cm_info;
use core_completion\activity_custom_completion;
use mod_bigbluebuttonbn\extension;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\logger;
use moodle_exception;
use stdClass;
/**
* Class custom_completion
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent@call-learning.fr)
*/
class custom_completion extends activity_custom_completion {
/**
* Filters for logs
*/
const FILTERS = [
'completionattendance' => [logger::EVENT_SUMMARY],
'completionengagementchats' => [logger::EVENT_SUMMARY],
'completionengagementtalks' => [logger::EVENT_SUMMARY],
'completionengagementraisehand' => [logger::EVENT_SUMMARY],
'completionengagementpollvotes' => [logger::EVENT_SUMMARY],
'completionengagementemojis' => [logger::EVENT_SUMMARY],
];
/**
* @var array $completionaddons array of extension class for the completion
*/
private $completionaddons;
/**
* activity_custom_completion constructor.
*
* @param cm_info $cm
* @param int $userid
* @param array|null $completionstate The current state of the core completion criteria
*/
public function __construct(cm_info $cm, int $userid, ?array $completionstate = null) {
parent::__construct($cm, $userid, $completionstate);
$completionaddonsclasses = extension::custom_completion_addons_instances($cm, $userid, $completionstate);
$this->completionaddons = array_map(function($targetclassname) use ($cm, $userid, $completionstate) {
return new $targetclassname($cm, $userid, $completionstate);
}, $completionaddonsclasses);
}
/**
* Get current state
*
* @param string $rule
* @return int
*/
public function get_state(string $rule): int {
// Get instance details.
$instance = instance::get_from_cmid($this->cm->id);
if (empty($instance)) {
throw new moodle_exception("Can't find bigbluebuttonbn instance {$this->cm->instance}");
}
// Default return value.
$returnedvalue = COMPLETION_INCOMPLETE;
$filters = self::FILTERS[$rule] ?? [logger::EVENT_SUMMARY];
$logs = logger::get_user_completion_logs($instance, $this->userid, $filters);
if (method_exists($this, "get_{$rule}_value")) {
$completionvalue = $this->aggregate_values($logs, self::class . "::get_{$rule}_value");
if ($completionvalue) {
// So in this case we check the value set in the module setting. If we go over the threshold, then
// this is complete.
$rulevalue = $instance->get_instance_var($rule);
if (!is_null($rulevalue)) {
if ($rulevalue <= $completionvalue) {
$returnedvalue = COMPLETION_COMPLETE;
}
} else {
// If there is at least a hit, we consider it as complete.
$returnedvalue = $completionvalue ? COMPLETION_COMPLETE : COMPLETION_INCOMPLETE;
}
}
}
// Check for any completion for this rule in addons / extensions.
foreach ($this->completionaddons as $customcompletion) {
if (in_array($rule, $customcompletion->get_defined_custom_rules())) {
$returnedvalue = $returnedvalue || $customcompletion->get_state($rule);
}
}
return $returnedvalue;
}
/**
* Compute current state from logs.
*
* @param array $logs
* @param callable $logvaluegetter
* @return int the sum of all values for this particular event (it can be a duration or a number of hits)
*/
protected function aggregate_values(array $logs, callable $logvaluegetter): int {
if (empty($logs)) {
// As completion by engagement with $rulename hand was required, the activity hasn't been completed.
return 0;
}
$value = 0;
foreach ($logs as $log) {
$value += $logvaluegetter($log);
}
return $value;
}
/**
* Fetch the list of custom completion rules that this module defines.
*
* @return array
*/
public static function get_defined_custom_rules(): array {
$rules = [
'completionattendance',
'completionengagementchats',
'completionengagementtalks',
'completionengagementraisehand',
'completionengagementpollvotes',
'completionengagementemojis',
];
$completionaddonsclasses = extension::custom_completion_addons_classes();
foreach ($completionaddonsclasses as $customcompletion) {
$rules = array_merge($rules, $customcompletion::get_defined_custom_rules());
}
return $rules;
}
/**
* Returns an associative array of the descriptions of custom completion rules.
*
* @return array
*/
public function get_custom_rule_descriptions(): array {
$completionengagementchats = $this->cm->customdata['customcompletionrules']['completionengagementchats'] ?? 1;
$completionengagementtalks = $this->cm->customdata['customcompletionrules']['completionengagementtalks'] ?? 1;
$completionengagementraisehand = $this->cm->customdata['customcompletionrules']['completionengagementraisehand'] ?? 1;
$completionengagementpollvotes = $this->cm->customdata['customcompletionrules']['completionengagementpollvotes'] ?? 1;
$completionengagementemojis = $this->cm->customdata['customcompletionrules']['completionengagementemojis'] ?? 1;
$completionattendance = $this->cm->customdata['customcompletionrules']['completionattendance'] ?? 1;
$descriptions = [
'completionengagementchats' => get_string('completionengagementchats_desc', 'mod_bigbluebuttonbn',
$completionengagementchats),
'completionengagementtalks' => get_string('completionengagementtalks_desc', 'mod_bigbluebuttonbn',
$completionengagementtalks),
'completionengagementraisehand' => get_string('completionengagementraisehand_desc', 'mod_bigbluebuttonbn',
$completionengagementraisehand),
'completionengagementpollvotes' => get_string('completionengagementpollvotes_desc', 'mod_bigbluebuttonbn',
$completionengagementpollvotes),
'completionengagementemojis' => get_string('completionengagementemojis_desc', 'mod_bigbluebuttonbn',
$completionengagementemojis),
'completionattendance' => get_string('completionattendance_desc', 'mod_bigbluebuttonbn',
$completionattendance),
];
// Check for any completion for this rule in addons / extensions.
foreach ($this->completionaddons as $customcompletion) {
$descriptions = array_merge($descriptions, $customcompletion->get_custom_rule_descriptions());
}
return $descriptions;
}
/**
* Returns an array of all completion rules, in the order they should be displayed to users.
*
* @return array
*/
public function get_sort_order(): array {
$rules = self::get_defined_custom_rules();
array_unshift($rules, 'completionview');
return $rules;
}
/**
* Get current states of completion in a human-friendly version
*
* @return string[]
*/
public function get_printable_states(): array {
$result = [];
foreach ($this->get_available_custom_rules() as $rule) {
$result[] = $this->get_printable_state($rule);
}
return $result;
}
/**
* Get current states of completion for a rule in a human-friendly version
*
* @param string $rule
* @return string
*/
private function get_printable_state(string $rule): string {
// Get instance details.
$instance = instance::get_from_cmid($this->cm->id);
if (empty($instance)) {
throw new moodle_exception("Can't find bigbluebuttonbn instance {$this->cm->instance}");
}
$summary = "";
$filters = self::FILTERS[$rule] ?? [logger::EVENT_SUMMARY];
$logs = logger::get_user_completion_logs($instance, $this->userid, $filters);
if (method_exists($this, "get_{$rule}_value")) {
$summary = get_string(
$rule . '_event_desc',
'mod_bigbluebuttonbn',
$this->aggregate_values($logs, self::class . "::get_{$rule}_value")
);
}
return $summary;
}
/**
* Get current state in a friendly version
*
* @param string $rule
* @return string
*/
public function get_last_log_timestamp(string $rule): string {
// Get instance details.
$instance = instance::get_from_cmid($this->cm->id);
if (empty($instance)) {
throw new moodle_exception("Can't find bigbluebuttonbn instance {$this->cm->instance}");
}
$filters = self::FILTERS[$rule] ?? [logger::EVENT_SUMMARY];
return logger::get_user_completion_logs_max_timestamp($instance, $this->userid, $filters);
}
/**
* Get attendance summary value
*
* @param stdClass $log
* @return int
*/
protected static function get_completionattendance_value(stdClass $log): int {
$summary = json_decode($log->meta);
return empty($summary->data->duration) ? 0 : (int)($summary->data->duration / 60);
}
/**
* Get general completion engagement value
*
* @param stdClass $log
* @return int
*/
protected static function get_completionengagementchats_value(stdClass $log): int {
return self::get_completionengagement_value($log, 'chats');
}
/**
* Get general completion engagement value
*
* @param stdClass $log
* @return int
*/
protected static function get_completionengagementtalks_value(stdClass $log): int {
return self::get_completionengagement_value($log, 'talks');
}
/**
* Get general completion engagement value
*
* @param stdClass $log
* @return int
*/
protected static function get_completionengagementraisehand_value(stdClass $log): int {
return self::get_completionengagement_value($log, 'raisehand');
}
/**
* Get general completion engagement value
*
* @param stdClass $log
* @return int
*/
protected static function get_completionengagementpollvotes_value(stdClass $log): int {
return self::get_completionengagement_value($log, 'poll_votes');
}
/**
* Get general completion engagement value
*
* @param stdClass $log
* @return int
*/
protected static function get_completionengagementemojis_value(stdClass $log): int {
return self::get_completionengagement_value($log, 'emojis');
}
/**
* Get general completion engagement value
*
* @param stdClass $log
* @param string $type
* @return int
*/
protected static function get_completionengagement_value(stdClass $log, string $type): int {
$summary = json_decode($log->meta);
return intval($summary->data->engagement->$type ?? 0);
}
}
@@ -0,0 +1,56 @@
<?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/>.
namespace mod_bigbluebuttonbn\event;
/**
* The mod_bigbluebuttonbn activity management viewed event.
*
* @package mod_bigbluebuttonbn
* @copyright 2010-2017 Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v2 or later
*/
class activity_management_viewed extends base {
/**
* Init method.
*
* @param string $crud
* @param int $edulevel
*/
protected function init($crud = 'r', $edulevel = self::LEVEL_PARTICIPATING) {
parent::init($crud, $edulevel);
$this->description = "The user with id '##userid' viewed the bigbluebuttonbn activity management page for " .
"the course module id '##contextinstanceid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return 'BigBlueButtonBN activity management viewed';
}
/**
* Return objectid mapping.
*
* @return array
*/
public static function get_objectid_mapping() {
return ['db' => 'bigbluebuttonbn', 'restore' => 'bigbluebuttonbn'];
}
}
+108
View File
@@ -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/>.
namespace mod_bigbluebuttonbn\event;
use coding_exception;
use moodle_url;
/**
* The mod_bigbluebuttonbn abstract base event class. Most mod_bigbluebuttonbn events can extend this class.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class base extends \core\event\base {
/**
* Object Id Mapping.
*
* @var array
*/
protected static $objectidmapping = ['db' => 'bigbluebuttonbn', 'restore' => 'bigbluebuttonbn'];
/** @var $bigbluebuttonbn */
protected $bigbluebuttonbn;
/**
* Description.
*
* @var string
*/
protected $description;
/**
* Legacy log data.
*
* @var array
*/
protected $legacylogdata;
/**
* Returns description of what happened.
*
* @return string
*/
public function get_description() {
$vars = [
'userid' => $this->userid,
'courseid' => $this->courseid,
'objectid' => $this->objectid,
'contextinstanceid' => $this->contextinstanceid,
'other' => $this->other
];
$string = $this->description;
foreach ($vars as $key => $value) {
$string = str_replace("##" . $key, $value, $string);
}
return $string;
}
/**
* Returns relevant URL.
*
* @return moodle_url
*/
public function get_url() {
return new moodle_url('/mod/bigbluebuttonbn/view.php', ['id' => $this->contextinstanceid]);
}
/**
* Init method.
*
* @param string $crud
* @param int $edulevel
*/
protected function init($crud = 'r', $edulevel = self::LEVEL_PARTICIPATING) {
$this->data['crud'] = $crud;
$this->data['edulevel'] = $edulevel;
$this->data['objecttable'] = 'bigbluebuttonbn';
}
/**
* Custom validation.
*
* @throws coding_exception
*/
protected function validate_data() {
parent::validate_data();
if ($this->contextlevel != CONTEXT_MODULE) {
throw new coding_exception('Context level must be CONTEXT_MODULE.');
}
}
}
@@ -0,0 +1,45 @@
<?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/>.
namespace mod_bigbluebuttonbn\event;
/**
* The mod_bigbluebuttonbn activity viewed event.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class course_module_viewed extends \core\event\course_module_viewed {
/**
* Init method.
*
*/
protected function init() {
$this->data['crud'] = 'r';
$this->data['edulevel'] = self::LEVEL_PARTICIPATING;
$this->data['objecttable'] = 'bigbluebuttonbn';
}
/**
* Return objectid mapping.
*
* @return array
*/
public static function get_objectid_mapping() {
return ['db' => 'bigbluebuttonbn', 'restore' => 'bigbluebuttonbn'];
}
}
@@ -0,0 +1,52 @@
<?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/>.
namespace mod_bigbluebuttonbn\event;
/**
* The mod_bigbluebuttonbn class for event name definition.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class events {
/**
* Event name matcher.
*
* @var $events
*/
public static $events = [
'create' => 'activity_created',
'view' => 'course_module_viewed',
'update' => 'activity_updated',
'delete' => 'activity_deleted',
'meeting_create' => 'meeting_created',
'meeting_end' => 'meeting_ended',
'meeting_join' => 'meeting_joined',
'meeting_left' => 'meeting_left',
'recording_delete' => 'recording_deleted',
'recording_import' => 'recording_imported',
'recording_protect' => 'recording_protected',
'recording_publish' => 'recording_published',
'recording_unprotect' => 'recording_unprotected',
'recording_unpublish' => 'recording_unpublished',
'recording_edit' => 'recording_edited',
'recording_play' => 'recording_viewed',
'live_session' => 'live_session'
];
}
@@ -0,0 +1,57 @@
<?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/>.
namespace mod_bigbluebuttonbn\event;
/**
* The mod_bigbluebuttonbn live_session (Experimental: for being triggered when external events are received).
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class live_session_event extends base {
/**
* Init method.
*
* @param string $crud
* @param int $edulevel
*/
protected function init($crud = 'r', $edulevel = self::LEVEL_OTHER) {
parent::init($crud, $edulevel);
$this->description = "The user with id '##userid' triggered action ##other in a " .
"bigbluebutton meeting for the bigbluebuttonbn activity with id " .
"'##objectid' for the course id '##courseid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('event_live_session', 'bigbluebuttonbn');
}
/**
* Return objectid mapping.
*
* @return array
*/
public static function get_objectid_mapping() {
return ['db' => 'bigbluebuttonbn', 'restore' => 'bigbluebuttonbn'];
}
}
@@ -0,0 +1,56 @@
<?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/>.
namespace mod_bigbluebuttonbn\event;
/**
* The mod_bigbluebuttonbn meeting created event, triggered when the meeting is created before join.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class meeting_created extends base {
/**
* Init method.
*
* @param string $crud
* @param int $edulevel
*/
protected function init($crud = 'r', $edulevel = self::LEVEL_OTHER) {
parent::init($crud, $edulevel);
$this->description = "The user with id '##userid' created a bigbluebutton meeting for " .
"the bigbluebuttonbn activity with id '##objectid' for the course id '##courseid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('event_meeting_created', 'bigbluebuttonbn');
}
/**
* Return objectid mapping.
*
* @return array
*/
public static function get_objectid_mapping() {
return ['db' => 'bigbluebuttonbn', 'restore' => 'bigbluebuttonbn'];
}
}
@@ -0,0 +1,58 @@
<?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/>.
namespace mod_bigbluebuttonbn\event;
/**
* The mod_bigbluebuttonbn meeting ended event, triggered when the meeting is ended by the user.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class meeting_ended extends base {
/**
* Init method.
*
* @param string $crud
* @param int $edulevel
*/
protected function init($crud = 'r', $edulevel = self::LEVEL_OTHER) {
parent::init($crud, $edulevel);
$this->description = "A bigbluebutton meeting for the bigbluebuttonbn activity with id " .
"'##objectid' for the course id '##courseid' has been forcibly " .
"ended by the user with id '##userid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('event_meeting_ended', 'bigbluebuttonbn');
}
/**
* Return objectid mapping.
*
* @return array
*/
public static function get_objectid_mapping() {
return ['db' => 'bigbluebuttonbn', 'restore' => 'bigbluebuttonbn'];
}
}
@@ -0,0 +1,57 @@
<?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/>.
namespace mod_bigbluebuttonbn\event;
/**
* The mod_bigbluebuttonbn meeting joined event, triggered when the user joins the session.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class meeting_joined extends base {
/**
* Init method.
*
* @param string $crud
* @param int $edulevel
*/
protected function init($crud = 'r', $edulevel = self::LEVEL_PARTICIPATING) {
parent::init($crud, $edulevel);
$this->description = "The user with id '##userid' has joined a bigbluebutton meeting for " .
"the bigbluebuttonbn activity with id '##objectid' for the course id " .
"'##courseid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('event_meeting_joined', 'bigbluebuttonbn');
}
/**
* Return objectid mapping.
*
* @return array
*/
public static function get_objectid_mapping() {
return ['db' => 'bigbluebuttonbn', 'restore' => 'bigbluebuttonbn'];
}
}
@@ -0,0 +1,57 @@
<?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/>.
namespace mod_bigbluebuttonbn\event;
/**
* The mod_bigbluebuttonbn meeting left event, triggered when the user lefts the meeting using the logout button.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class meeting_left extends base {
/**
* Init method.
*
* @param string $crud
* @param int $edulevel
*/
protected function init($crud = 'r', $edulevel = self::LEVEL_PARTICIPATING) {
parent::init($crud, $edulevel);
$this->description = "The user with id '##userid' has left a bigbluebutton meeting for " .
"the bigbluebuttonbn activity with id '##objectid' for the course id " .
"'##courseid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('event_meeting_left', 'bigbluebuttonbn');
}
/**
* Return objectid mapping.
*
* @return array
*/
public static function get_objectid_mapping() {
return ['db' => 'bigbluebuttonbn', 'restore' => 'bigbluebuttonbn'];
}
}
@@ -0,0 +1,56 @@
<?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/>.
namespace mod_bigbluebuttonbn\event;
/**
* The mod_bigbluebuttonbn recording deleted event.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class recording_deleted extends base {
/**
* Init method.
*
* @param string $crud
* @param int $edulevel
*/
protected function init($crud = 'r', $edulevel = self::LEVEL_OTHER) {
parent::init($crud, $edulevel);
$this->description = "The user with id '##userid' has deleted a recording with id " .
"'##other' from the course id '##courseid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('event_recording_deleted', 'bigbluebuttonbn');
}
/**
* Return objectid mapping.
*
* @return array
*/
public static function get_objectid_mapping() {
return ['db' => 'bigbluebuttonbn', 'restore' => 'bigbluebuttonbn'];
}
}
@@ -0,0 +1,56 @@
<?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/>.
namespace mod_bigbluebuttonbn\event;
/**
* The mod_bigbluebuttonbn recording edited event.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class recording_edited extends base {
/**
* Init method.
*
* @param string $crud
* @param int $edulevel
*/
protected function init($crud = 'r', $edulevel = self::LEVEL_OTHER) {
parent::init($crud, $edulevel);
$this->description = "The user with id '##userid' has edited a recording with id " .
"'##other' in the course id '##courseid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('event_recording_edited', 'bigbluebuttonbn');
}
/**
* Return objectid mapping.
*
* @return array
*/
public static function get_objectid_mapping() {
return ['db' => 'bigbluebuttonbn', 'restore' => 'bigbluebuttonbn'];
}
}
@@ -0,0 +1,56 @@
<?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/>.
namespace mod_bigbluebuttonbn\event;
/**
* The mod_bigbluebuttonbn recording imported event.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class recording_imported extends base {
/**
* Init method.
*
* @param string $crud
* @param int $edulevel
*/
protected function init($crud = 'r', $edulevel = self::LEVEL_OTHER) {
parent::init($crud, $edulevel);
$this->description = "The user with id '##userid' has imported a recording with id " .
"'##other' in the course id '##courseid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('event_recording_imported', 'bigbluebuttonbn');
}
/**
* Return objectid mapping.
*
* @return array
*/
public static function get_objectid_mapping() {
return ['db' => 'bigbluebuttonbn', 'restore' => 'bigbluebuttonbn'];
}
}
@@ -0,0 +1,55 @@
<?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/>.
namespace mod_bigbluebuttonbn\event;
/**
* The mod_bigbluebuttonbn recording protected event.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class recording_protected extends base {
/**
* Init method.
*
* @param string $crud
* @param int $edulevel
*/
protected function init($crud = 'r', $edulevel = self::LEVEL_OTHER) {
parent::init($crud, $edulevel);
$this->description = "The user with id '##userid' has protected a recording with id " .
"'##other' in the course id '##courseid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('event_recording_protected', 'bigbluebuttonbn');
}
/**
* Return objectid mapping.
*
* @return array
*/
public static function get_objectid_mapping() {
return ['db' => 'bigbluebuttonbn', 'restore' => 'bigbluebuttonbn'];
}
}
@@ -0,0 +1,56 @@
<?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/>.
namespace mod_bigbluebuttonbn\event;
/**
* The mod_bigbluebuttonbn recording published event.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class recording_published extends base {
/**
* Init method.
*
* @param string $crud
* @param int $edulevel
*/
protected function init($crud = 'r', $edulevel = self::LEVEL_OTHER) {
parent::init($crud, $edulevel);
$this->description = "The user with id '##userid' has published a recording with id " .
"'##other' in the course id '##courseid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('event_recording_published', 'bigbluebuttonbn');
}
/**
* Return objectid mapping.
*
* @return array
*/
public static function get_objectid_mapping() {
return ['db' => 'bigbluebuttonbn', 'restore' => 'bigbluebuttonbn'];
}
}
@@ -0,0 +1,56 @@
<?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/>.
namespace mod_bigbluebuttonbn\event;
/**
* The mod_bigbluebuttonbn recording unprotected event.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class recording_unprotected extends base {
/**
* Init method.
*
* @param string $crud
* @param int $edulevel
*/
protected function init($crud = 'r', $edulevel = self::LEVEL_OTHER) {
parent::init($crud, $edulevel);
$this->description = "The user with id '##userid' has unprotected a recording with id " .
"'##other' in the course id '##courseid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('event_recording_unprotected', 'bigbluebuttonbn');
}
/**
* Return objectid mapping.
*
* @return array
*/
public static function get_objectid_mapping() {
return ['db' => 'bigbluebuttonbn', 'restore' => 'bigbluebuttonbn'];
}
}
@@ -0,0 +1,56 @@
<?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/>.
namespace mod_bigbluebuttonbn\event;
/**
* The mod_bigbluebuttonbn recording unpublished event.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class recording_unpublished extends base {
/**
* Init method.
*
* @param string $crud
* @param int $edulevel
*/
protected function init($crud = 'r', $edulevel = self::LEVEL_OTHER) {
parent::init($crud, $edulevel);
$this->description = "The user with id '##userid' has unpublished a recording with id " .
"'##other' in the course id '##courseid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('event_recording_unpublished', 'bigbluebuttonbn');
}
/**
* Return objectid mapping.
*
* @return array
*/
public static function get_objectid_mapping() {
return ['db' => 'bigbluebuttonbn', 'restore' => 'bigbluebuttonbn'];
}
}
@@ -0,0 +1,56 @@
<?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/>.
namespace mod_bigbluebuttonbn\event;
/**
* The mod_bigbluebuttonbn recording viewed event.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class recording_viewed extends base {
/**
* Init method.
*
* @param string $crud
* @param int $edulevel
*/
protected function init($crud = 'r', $edulevel = self::LEVEL_OTHER) {
parent::init($crud, $edulevel);
$this->description = "The user with id '##userid' has viewed a recording with id " .
"'##other' from the course id '##courseid'.";
}
/**
* Return localised event name.
*
* @return string
*/
public static function get_name() {
return get_string('event_recording_viewed', 'bigbluebuttonbn');
}
/**
* Return objectid mapping.
*
* @return array
*/
public static function get_objectid_mapping() {
return ['db' => 'bigbluebuttonbn', 'restore' => 'bigbluebuttonbn'];
}
}
+220
View File
@@ -0,0 +1,220 @@
<?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/>.
namespace mod_bigbluebuttonbn;
use cache;
use cm_info;
use mod_bigbluebuttonbn\local\extension\action_url_addons;
use mod_bigbluebuttonbn\local\extension\custom_completion_addons;
use mod_bigbluebuttonbn\local\extension\mod_form_addons;
use mod_bigbluebuttonbn\local\extension\mod_instance_helper;
use stdClass;
use core_plugin_manager;
/**
* Generic subplugin management helper
*
* @package mod_bigbluebuttonbn
* @copyright 2023 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent@call-learning.fr)
*/
class extension {
/**
* Plugin name for extension
*/
const BBB_EXTENSION_PLUGIN_NAME = 'bbbext';
/**
* Invoke a subplugin hook that will return additional parameters
*
* @param string $action
* @param array $data
* @param array $metadata
* @param int|null $instanceid
* @return array associative array with the additional data and metadata (indexed by 'data' and
* 'metadata' keys).
*/
public static function action_url_addons(
string $action = '',
array $data = [],
array $metadata = [],
?int $instanceid = null
): array {
$allmutationclass = self::get_instances_implementing(action_url_addons::class);
$additionaldata = [];
$additionalmetadata = [];
foreach ($allmutationclass as $mutationclass) {
// Here we intentionally just pass data and metadata and not the result as we
// do not want subplugin to assume that another subplugin is doing a modification.
['data' => $newdata, 'metadata' => $newmetadata] = $mutationclass->execute($action, $data, $metadata, $instanceid);
$additionaldata = array_merge($additionaldata, $newdata ?? []);
$additionalmetadata = array_merge($additionalmetadata, $newmetadata ?? []);
}
return [
'data' => $additionaldata,
'metadata' => $additionalmetadata
];
}
/**
* Get new instance of classes that are named on the base of this classname and implementing this class
*
* @param string $classname
* @param array|null $newparameters additional parameters for the constructor.
* @return array
*/
protected static function get_instances_implementing(string $classname, ?array $newparameters = []): array {
$classes = self::get_classes_implementing($classname);
sort($classes); // Make sure all extension classes are returned in the same order. This is arbitrarily in
// alphabetical order and depends on the classname but this one way to ensure consistency across calls.
return array_map(function($targetclassname) use ($newparameters) {
// If $newparameters is null, the constructor will be called without parameters.
return new $targetclassname(...$newparameters);
}, $classes);
}
/**
* Get classes are named on the base of this classname and implementing this class
*
* @param string $classname
* @return array
*/
protected static function get_classes_implementing(string $classname): array {
// Get the class basename without Reflection API.
$classnamecomponents = explode("\\", $classname);
$classbasename = end($classnamecomponents);
$allsubs = core_plugin_manager::instance()->get_plugins_of_type(self::BBB_EXTENSION_PLUGIN_NAME);
$extensionclasses = [];
foreach ($allsubs as $sub) {
if (!$sub->is_enabled()) {
continue;
}
$targetclassname = "\\bbbext_{$sub->name}\\bigbluebuttonbn\\$classbasename";
if (!class_exists($targetclassname)) {
continue;
}
if (!is_subclass_of($targetclassname, $classname)) {
debugging("The class $targetclassname should extend $classname in the subplugin {$sub->name}. Ignoring.");
continue;
}
$extensionclasses[] = $targetclassname;
}
return $extensionclasses;
}
/**
* Get all custom_completion addons classes.
*
* @return array of custom completion addon classes.
*/
public static function custom_completion_addons_classes(): array {
return self::get_classes_implementing(custom_completion_addons::class);
}
/**
* Get all custom_completion addons classes instances.
*
* @param cm_info $cm
* @param int $userid
* @param array|null $completionstate
* @return array of custom completion addon instances.
*/
public static function custom_completion_addons_instances(cm_info $cm, int $userid, ?array $completionstate = null): array {
return self::get_instances_implementing(custom_completion_addons::class, [$cm, $userid, $completionstate]);
}
/**
* Get all mod_form addons classes instances
*
* @param \MoodleQuickForm $mform
* @param stdClass|null $bigbluebuttondata
* @param string|null $suffix
* @return array of custom completion addon classes instances
*/
public static function mod_form_addons_instances(\MoodleQuickForm $mform, ?stdClass $bigbluebuttondata = null,
string $suffix = null): array {
return self::get_instances_implementing(mod_form_addons::class, [$mform, $bigbluebuttondata, $suffix]);
}
/**
* Get additional join tables for instance when extension activated
*
* @return array of additional tables names. They all have a field called bigbluebuttonbnid that identifies the bbb instance.
*/
public static function get_join_tables(): array {
global $DB;
// We use cache here as it will be called very often.
$cache = cache::make('mod_bigbluebuttonbn', 'subplugins');
if ($cache->get('additionaltables')) {
return $cache->get('additionaltables');
}
$additionaltablesclasses = self::get_instances_implementing(mod_instance_helper::class);
$tables = [];
foreach ($additionaltablesclasses as $tableclass) {
$tables = array_merge($tables, $tableclass->get_join_tables() ?? []);
}
// Warning and removal for tables that do not have the bigbluebuttonid field.
foreach ($tables as $index => $table) {
$columns = $DB->get_columns($table);
if (empty($columns['bigbluebuttonbnid'])) {
debugging("get_instance_additional_tables: $table should have a column named bigbluebuttonid");
unset($tables[$index]);
}
}
$cache->set('additionaltables', $tables);
return $tables;
}
/**
* Add instance processing
*
* @param stdClass $data data to persist
* @return void
*/
public static function add_instance(stdClass $data): void {
$formmanagersclasses = self::get_instances_implementing(mod_instance_helper::class);
foreach ($formmanagersclasses as $fmclass) {
$fmclass->add_instance($data);
}
}
/**
* Update instance processing
*
* @param stdClass $data data to persist
* @return void
*/
public static function update_instance(stdClass $data): void {
$formmanagersclasses = self::get_instances_implementing(mod_instance_helper::class);
foreach ($formmanagersclasses as $fmclass) {
$fmclass->update_instance($data);
}
}
/**
* Delete instance processing
*
* @param int $id instance id
* @return void
*/
public static function delete_instance(int $id): void {
$formmanagersclasses = self::get_instances_implementing(mod_instance_helper::class);
foreach ($formmanagersclasses as $fmclass) {
$fmclass->delete_instance($id);
}
}
}
+107
View File
@@ -0,0 +1,107 @@
<?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/>.
namespace mod_bigbluebuttonbn\external;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\restricted_context_exception;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\meeting;
/**
* External service to check whether a user can join a meeting.
*
* This is mainly used by the mobile application.
*
* @package mod_bigbluebuttonbn
* @category external
* @copyright 2018 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class can_join extends external_api {
/**
* Returns description of method parameters
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'cmid' => new external_value(PARAM_INT, 'course module id', VALUE_REQUIRED),
'groupid' => new external_value(PARAM_INT, 'bigbluebuttonbn group id', VALUE_DEFAULT, 0),
]);
}
/**
* Updates a recording
*
* @param int $cmid the bigbluebuttonbn course module id
* @param null|int $groupid
* @return array (empty array for now)
* @throws \restricted_context_exception
*/
public static function execute(
int $cmid,
?int $groupid = 0
): array {
// Validate the cmid ID.
[
'cmid' => $cmid,
'groupid' => $groupid,
] = self::validate_parameters(self::execute_parameters(), [
'cmid' => $cmid,
'groupid' => $groupid,
]);
$result = [
'can_join' => false,
'cmid' => $cmid,
];
$instance = instance::get_from_cmid($cmid);
if (empty($instance)) {
return $result;
}
// Validate the groupid.
if (!groups_group_visible($groupid, $instance->get_course(), $instance->get_cm())) {
throw new restricted_context_exception();
}
$instance->set_group_id($groupid);
self::validate_context($instance->get_context());
$meeting = new meeting($instance);
$result['can_join'] = $meeting->can_join();
return $result;
}
/**
* Describe the return structure of the external service.
*
* @return external_single_structure
* @since Moodle 3.3
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([
'can_join' => new external_value(PARAM_BOOL, 'Can join session'),
'cmid' => new external_value(PARAM_INT, 'course module id', VALUE_REQUIRED),
]);
}
}
@@ -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/>.
namespace mod_bigbluebuttonbn\external;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\local\proxy\bigbluebutton_proxy;
/**
* External service to validate completion.
*
* @package mod_bigbluebuttonbn
* @category external
* @copyright 2018 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class completion_validate extends external_api {
/**
* Returns description of method parameters
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'bigbluebuttonbnid' => new external_value(PARAM_INT, 'bigbluebuttonbn instance id'),
]);
}
/**
* Mark activity as complete
*
* @param int $bigbluebuttonbnid the bigbluebuttonbn instance id
* @return array (empty array for now)
*/
public static function execute(
int $bigbluebuttonbnid
): array {
// Validate the bigbluebuttonbnid ID.
[
'bigbluebuttonbnid' => $bigbluebuttonbnid,
] = self::validate_parameters(self::execute_parameters(), [
'bigbluebuttonbnid' => $bigbluebuttonbnid,
]);
$result = ['warnings' => []];
// Fetch the session, features, and profile.
$instance = instance::get_from_instanceid($bigbluebuttonbnid);
if ($instance) {
$context = $instance->get_context();
// Validate that the user has access to this activity.
self::validate_context($context);
// Get list with all the users enrolled in the course.
[$sort, $sqlparams] = users_order_by_sql('u');
if (has_capability('moodle/course:update', $context)) {
$users = get_enrolled_users($context, 'mod/bigbluebuttonbn:view', 0, 'u.*', $sort);
foreach ($users as $user) {
// Enqueue a task for processing the completion.
bigbluebutton_proxy::enqueue_completion_event($instance->get_instance_data(), $user->id);
}
} else {
$result['warnings'][] = [
'item' => 'mod_bigbluebuttonbn',
'itemid' => $instance->get_instance_id(),
'warningcode' => 'nopermissions',
'message' => get_string('nopermissions', 'error', 'completion_validate')
];
}
} else {
$result['warnings'][] = [
'item' => 'mod_bigbluebuttonbn',
'itemid' => $bigbluebuttonbnid,
'warningcode' => 'indexerrorbbtn',
'message' => get_string('index_error_bbtn', 'mod_bigbluebuttonbn', $bigbluebuttonbnid)
];
}
// We might want to return a status here or some warnings.
return $result;
}
/**
* Describe the return structure of the external service.
*
* @return external_single_structure
* @since Moodle 3.0
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([
'warnings' => new external_warnings(),
]);
}
}
+126
View File
@@ -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/>.
namespace mod_bigbluebuttonbn\external;
use core\notification;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
use core_external\restricted_context_exception;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\local\exceptions\bigbluebutton_exception;
use mod_bigbluebuttonbn\logger;
use mod_bigbluebuttonbn\meeting;
/**
* External service to end a meeting.
*
* @package mod_bigbluebuttonbn
* @category external
* @copyright 2018 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class end_meeting extends external_api {
/**
* Returns description of method parameters
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'bigbluebuttonbnid' => new external_value(PARAM_INT, 'bigbluebuttonbn instance id'),
'groupid' => new external_value(PARAM_INT, 'bigbluebuttonbn group id', VALUE_DEFAULT, 0),
]);
}
/**
* Updates a recording
*
* @param int $bigbluebuttonbnid the bigbluebuttonbn instance id
* @param int $groupid the groupid (either 0 or the groupid)
* @return array (empty array for now)
* @throws \invalid_parameter_exception
* @throws \moodle_exception
* @throws restricted_context_exception
*/
public static function execute(
int $bigbluebuttonbnid,
int $groupid
): array {
// Validate the bigbluebuttonbnid ID.
[
'bigbluebuttonbnid' => $bigbluebuttonbnid,
'groupid' => $groupid,
] = self::validate_parameters(self::execute_parameters(), [
'bigbluebuttonbnid' => $bigbluebuttonbnid,
'groupid' => $groupid,
]);
// Fetch the session, features, and profile.
$instance = instance::get_from_instanceid($bigbluebuttonbnid);
if (empty($instance)) {
throw new \moodle_exception('Unknown Instance');
}
if (!groups_group_visible($groupid, $instance->get_course(), $instance->get_cm())) {
throw new restricted_context_exception();
}
$instance->set_group_id($groupid);
$context = $instance->get_context();
// Validate that the user has access to this activity and to manage recordings.
self::validate_context($context);
if (!$instance->user_can_end_meeting()) {
throw new restricted_context_exception();
}
// Execute the end command.
$meeting = new meeting($instance);
try {
$meeting->end_meeting();
} catch (bigbluebutton_exception $e) {
return [
'warnings' => [
[
'item' => $instance->get_meeting_name(),
'itemid' => $instance->get_instance_id(),
'warningcode' => 'notFound',
'message' => $e->getMessage()
]
]
];
}
logger::log_meeting_ended_event($instance);
// Update the cache.
$meeting->update_cache();
notification::add(get_string('end_session_notification', 'mod_bigbluebuttonbn'), notification::INFO);
return [];
}
/**
* Describe the return structure of the external service.
*
* @return external_single_structure
* @since Moodle 3.0
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([
'warnings' => new external_warnings(),
]);
}
}
@@ -0,0 +1,115 @@
<?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/>.
namespace mod_bigbluebuttonbn\external;
use core_course\external\helper_for_get_mods_by_courses;
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 core_external\external_warnings;
use core_external\util as external_util;
/**
* External service to get activity per course
*
* This is mainly used by the mobile application.
*
* @package mod_bigbluebuttonbn
* @category external
* @copyright 2018 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class get_bigbluebuttonbns_by_courses extends external_api {
/**
* Describes the parameters for get_bigbluebuttonbns_by_courses.
*
* @return external_function_parameters
* @since Moodle 3.11
*/
public static function execute_parameters() {
return new external_function_parameters([
'courseids' => new external_multiple_structure(
new external_value(PARAM_INT, 'Course id'), 'Array of course ids', VALUE_DEFAULT, []
),
]
);
}
/**
* Returns a list of bigbluebuttonbns in a provided list of courses.
* If no list is provided all bigbluebuttonbns that the user can view will be returned.
*
* @param array $courseids course ids
* @return array of warnings and bigbluebuttonbns
* @since Moodle 3.11
*/
public static function execute($courseids = []) {
global $USER;
$warnings = [];
$returnedbigbluebuttonbns = [];
['courseids' => $courseids] = self::validate_parameters(self::execute_parameters(), ['courseids' => $courseids]);
$mycourses = [];
if (empty($courseids)) {
$mycourses = enrol_get_my_courses();
$courseids = array_keys($mycourses);
}
// Ensure there are courseids to loop through.
if (!empty($courseids)) {
[$courses, $warnings] = external_util::validate_courses($courseids, $mycourses);
// Get the bigbluebuttonbns in this course, this function checks users visibility permissions.
// We can avoid then additional validate_context calls.
$bigbluebuttonbns = get_all_instances_in_courses("bigbluebuttonbn", $courses, $USER->id);
foreach ($bigbluebuttonbns as $bigbluebuttonbn) {
helper_for_get_mods_by_courses::format_name_and_intro($bigbluebuttonbn, 'mod_bigbluebuttonbn');
$returnedbigbluebuttonbns[] = $bigbluebuttonbn;
}
}
$result = [
'bigbluebuttonbns' => $returnedbigbluebuttonbns,
'warnings' => $warnings
];
return $result;
}
/**
* Describes the get_bigbluebuttonbns_by_courses return value.
*
* @return external_single_structure
* @since Moodle 3.11
*/
public static function execute_returns() {
return new external_single_structure([
'bigbluebuttonbns' => new external_multiple_structure(
new external_single_structure(array_merge(
helper_for_get_mods_by_courses::standard_coursemodule_elements_returns(),
[
'meetingid' => new external_value(PARAM_RAW, 'Meeting id'),
'timemodified' => new external_value(PARAM_INT, 'Last time the instance was modified'),
]
))
),
'warnings' => new external_warnings(),
]
);
}
}
+117
View File
@@ -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/>.
namespace mod_bigbluebuttonbn\external;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
use core_external\restricted_context_exception;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\local\exceptions\meeting_join_exception;
use mod_bigbluebuttonbn\meeting;
/**
* External service to create the meeting (if needed), check user limit, and return the join URL when we can join.
*
* This is mainly used by the mobile application.
*
* @package mod_bigbluebuttonbn
* @category external
* @copyright 2018 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class get_join_url extends external_api {
/**
* Returns description of method parameters
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'cmid' => new external_value(PARAM_INT, 'course module id', VALUE_REQUIRED),
'groupid' => new external_value(PARAM_INT, 'bigbluebuttonbn group id', VALUE_DEFAULT, 0),
]);
}
/**
* Updates a recording
*
* @param int $cmid the bigbluebuttonbn course module id
* @param null|int $groupid
* @return array (empty array for now)
*
* @throws restricted_context_exception
*/
public static function execute(
int $cmid,
?int $groupid = 0
): array {
// Validate the cmid ID.
[
'cmid' => $cmid,
'groupid' => $groupid,
] = self::validate_parameters(self::execute_parameters(), [
'cmid' => $cmid,
'groupid' => $groupid,
]);
$result = ['warnings' => []];
$instance = instance::get_from_cmid($cmid);
if (empty($instance)) {
throw new \moodle_exception('nosuchinstance', 'mod_bigbluebuttonbn', null,
['entity' => get_string('module', 'course'), 'id' => $cmid]);
}
// Validate the groupid.
if (!groups_group_visible($groupid, $instance->get_course(), $instance->get_cm())) {
throw new restricted_context_exception();
}
$instance->set_group_id($groupid);
// Validate that the user has access to this activity and to join the meeting.
self::validate_context($instance->get_context());
if (!$instance->can_join()) {
throw new restricted_context_exception();
}
try {
$result['join_url'] = meeting::join_meeting($instance);
} catch (meeting_join_exception $e) {
$result['warnings'][] = [
'item' => 'mod_bigbluebuttonbn',
'itemid' => $instance->get_instance_id(),
'warningcode' => $e->errorcode,
'message' => $e->getMessage()
];
}
return $result;
}
/**
* Describe the return structure of the external service.
*
* @return external_single_structure
* @since Moodle 3.3
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([
'join_url' => new external_value(PARAM_RAW, 'Can join session', VALUE_OPTIONAL),
'warnings' => new external_warnings(),
]);
}
}
+156
View File
@@ -0,0 +1,156 @@
<?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/>.
namespace mod_bigbluebuttonbn\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 core_external\external_warnings;
use core_external\restricted_context_exception;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\local\bigbluebutton\recordings\recording_data;
use mod_bigbluebuttonbn\local\proxy\bigbluebutton_proxy;
/**
* External service to fetch a list of recordings from the BBB service.
*
* @package mod_bigbluebuttonbn
* @category external
* @copyright 2018 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class get_recordings extends external_api {
/**
* Returns description of method parameters
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'bigbluebuttonbnid' => new external_value(PARAM_INT, 'bigbluebuttonbn instance id'),
'tools' => new external_value(PARAM_RAW, 'a set of enabled tools', VALUE_DEFAULT,
'protect,unprotect,publish,unpublish,delete'),
'groupid' => new external_value(PARAM_INT, 'Group ID', VALUE_DEFAULT, null),
]);
}
/**
* Get a list of recordings
*
* @param int $bigbluebuttonbnid the bigbluebuttonbn instance id to which the recordings are referred.
* @param string|null $tools
* @param int|null $groupid
* @return array of warnings and status result
* @throws \invalid_parameter_exception
* @throws restricted_context_exception
*/
public static function execute(
int $bigbluebuttonbnid = 0,
?string $tools = 'protect,unprotect,publish,unpublish,delete',
?int $groupid = null
): array {
global $USER;
$returnval = [
'status' => false,
'warnings' => [],
];
// Validate the bigbluebuttonbnid ID.
[
'bigbluebuttonbnid' => $bigbluebuttonbnid,
'tools' => $tools,
'groupid' => $groupid,
] = self::validate_parameters(self::execute_parameters(), [
'bigbluebuttonbnid' => $bigbluebuttonbnid,
'tools' => $tools,
'groupid' => $groupid,
]);
$tools = explode(',', $tools ?? 'protect,unprotect,publish,unpublish,delete');
// Fetch the session, features, and profile.
$instance = instance::get_from_instanceid($bigbluebuttonbnid);
if (!$instance) {
$returnval['warnings'][] = [
'item' => $bigbluebuttonbnid,
'warningcode' => 'nosuchinstance',
'message' => get_string('nosuchinstance', 'mod_bigbluebuttonbn',
(object) ['id' => $bigbluebuttonbnid, 'entity' => 'bigbluebuttonbn'])
];
} else {
$typeprofiles = bigbluebutton_proxy::get_instance_type_profiles();
$profilefeature = $typeprofiles[$instance->get_type()]['features'];
$showrecordings = in_array('all', $profilefeature) || in_array('showrecordings', $profilefeature);
if ($showrecordings) {
$context = $instance->get_context();
// Validate that the user has access to this activity.
self::validate_context($context);
if (!$instance->user_has_group_access($USER, $groupid)) {
new restricted_context_exception();
}
if ($groupid) {
$instance->set_group_id($groupid);
}
$recordings = $instance->get_recordings([], $instance->get_instance_var('recordings_deleted') ?? false);
$tabledata = recording_data::get_recording_table($recordings, $tools, $instance);
$returnval['tabledata'] = $tabledata;
$returnval['status'] = true;
} else {
$returnval['warnings'][] = [
'item' => $bigbluebuttonbnid,
'warningcode' => 'instanceprofilewithoutrecordings',
'message' => get_string('instanceprofilewithoutrecordings', 'mod_bigbluebuttonbn')
];
}
}
return $returnval;
}
/**
* Describe the return structure of the external service.
*
* @return external_single_structure
* @since Moodle 3.0
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([
'status' => new external_value(PARAM_BOOL, 'Whether the fetch was successful'),
'tabledata' => new external_single_structure([
'activity' => new external_value(PARAM_ALPHANUMEXT),
'ping_interval' => new external_value(PARAM_INT),
'locale' => new external_value(PARAM_TEXT),
'profile_features' => new external_multiple_structure(new external_value(PARAM_TEXT)),
'columns' => new external_multiple_structure(new external_single_structure([
'key' => new external_value(PARAM_ALPHA),
'label' => new external_value(PARAM_TEXT),
'width' => new external_value(PARAM_ALPHANUMEXT),
// See https://datatables.net/reference/option/columns.type .
'type' => new external_value(PARAM_ALPHANUMEXT, 'Column type', VALUE_OPTIONAL),
'sortable' => new external_value(PARAM_BOOL, 'Whether this column is sortable', VALUE_OPTIONAL, false),
'allowHTML' => new external_value(PARAM_BOOL, 'Whether this column contains HTML', VALUE_OPTIONAL, false),
'formatter' => new external_value(PARAM_ALPHANUMEXT, 'Formatter name', VALUE_OPTIONAL),
])),
'data' => new external_value(PARAM_RAW), // For now it will be json encoded.
], '', VALUE_OPTIONAL),
'warnings' => new external_warnings()
]);
}
}
@@ -0,0 +1,203 @@
<?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/>.
namespace mod_bigbluebuttonbn\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 core_external\external_warnings;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\local\bigbluebutton\recordings\recording_data;
use mod_bigbluebuttonbn\recording;
/**
* External service to fetch a list of recordings from the BBB service.
*
* @package mod_bigbluebuttonbn
* @category external
* @copyright 2018 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class get_recordings_to_import extends external_api {
/**
* Returns description of method parameters
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'destinationinstanceid' => new external_value(
PARAM_INT,
'Id of the other BBB we target for importing recordings into.
The idea here is to remove already imported recordings.',
VALUE_REQUIRED
),
'sourcebigbluebuttonbnid' => new external_value(PARAM_INT,
'bigbluebuttonbn instance id',
VALUE_DEFAULT,
0),
'sourcecourseid' => new external_value(PARAM_INT,
'source courseid to filter by',
VALUE_DEFAULT,
0),
'tools' => new external_value(PARAM_RAW, 'a set of enabled tools', VALUE_DEFAULT,
'protect,unprotect,publish,unpublish,delete'),
'groupid' => new external_value(PARAM_INT, 'Group ID', VALUE_DEFAULT, null),
]);
}
/**
* Get a list of recordings
*
* @param int $destinationinstanceid the bigbluebuttonbn instance id where recordings have been already imported.
* @param int|null $sourcebigbluebuttonbnid the bigbluebuttonbn instance id to which the recordings are referred.
* @param int|null $sourcecourseid the source courseid to filter by
* @param string|null $tools
* @param int|null $groupid
* @return array of warnings and status result
* @throws \invalid_parameter_exception
* @throws \restricted_context_exception
*/
public static function execute(
int $destinationinstanceid,
?int $sourcebigbluebuttonbnid = 0,
?int $sourcecourseid = 0,
?string $tools = 'protect,unprotect,publish,unpublish,delete',
?int $groupid = null
): array {
global $USER, $DB;
$returnval = [
'status' => false,
'warnings' => [],
];
// Validate the sourcebigbluebuttonbnid ID.
[
'destinationinstanceid' => $destinationinstanceid,
'sourcebigbluebuttonbnid' => $sourcebigbluebuttonbnid,
'sourcecourseid' => $sourcecourseid,
'tools' => $tools,
'groupid' => $groupid
] = self::validate_parameters(self::execute_parameters(), [
'destinationinstanceid' => $destinationinstanceid,
'sourcebigbluebuttonbnid' => $sourcebigbluebuttonbnid,
'sourcecourseid' => $sourcecourseid,
'tools' => $tools,
'groupid' => $groupid
]);
$tools = explode(',', $tools ?? 'protect,unprotect,publish,unpublish,delete');
// Fetch the session, features, and profile.
$sourceinstance = null;
$sourcecourse = null;
if ($sourcecourseid) {
$sourcecourse = $DB->get_record('course', ['id' => $sourcecourseid], '*', MUST_EXIST);
}
if (!empty($sourcebigbluebuttonbnid)) {
$sourceinstance = instance::get_from_instanceid($sourcebigbluebuttonbnid);
if (!$sourceinstance) {
throw new \invalid_parameter_exception('Source Bigbluebutton Id is invalid');
}
$sourcecourse = $sourceinstance->get_course();
// Validate that the user has access to this activity.
self::validate_context($sourceinstance->get_context());
}
$destinstance = instance::get_from_instanceid($destinationinstanceid);
// Validate that the user has access to this activity.
self::validate_context($destinstance->get_context());
if (!$destinstance->user_has_group_access($USER, $groupid)) {
throw new \invalid_parameter_exception('Invalid group for this user ' . $groupid);
}
if ($groupid) {
$destinstance->set_group_id($groupid);
}
// Exclude itself from the list if in import mode.
$excludedids = [$destinstance->get_instance_id()];
if ($sourceinstance) {
$recordings = $sourceinstance->get_recordings($excludedids);
} else {
// There is a course id or a 0, so we fetch all recording including deleted recordings from this course.
$recordings = recording::get_recordings_for_course(
$sourcecourseid,
$excludedids,
true,
false,
($sourcecourseid == 0 || $sourcebigbluebuttonbnid == 0),
($sourcecourseid == 0 || $sourcebigbluebuttonbnid == 0)
);
}
if ($destinationinstanceid) {
// Remove recording already imported in this specific activity.
$destinationinstance = instance::get_from_instanceid($destinationinstanceid);
$importedrecordings = recording::get_recordings_for_instance(
$destinationinstance,
true,
true
);
// Unset from $recordings if recording is already imported.
// Recording $recordings are indexed by $id (moodle table column id).
foreach ($recordings as $index => $recording) {
foreach ($importedrecordings as $irecord) {
if ($irecord->get('recordingid') == $recording->get('recordingid')) {
unset($recordings[$index]);
}
}
}
}
$tabledata = recording_data::get_recording_table($recordings, $tools, $sourceinstance, $sourcecourseid);
$returnval['tabledata'] = $tabledata;
$returnval['status'] = true;
return $returnval;
}
/**
* Describe the return structure of the external service.
*
* @return external_single_structure
* @since Moodle 3.0
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([
'status' => new external_value(PARAM_BOOL, 'Whether the fetch was successful'),
'tabledata' => new external_single_structure([
'activity' => new external_value(PARAM_ALPHANUMEXT),
'ping_interval' => new external_value(PARAM_INT),
'locale' => new external_value(PARAM_TEXT),
'profile_features' => new external_multiple_structure(new external_value(PARAM_TEXT)),
'columns' => new external_multiple_structure(new external_single_structure([
'key' => new external_value(PARAM_ALPHA),
'label' => new external_value(PARAM_TEXT),
'width' => new external_value(PARAM_ALPHANUMEXT),
// See https://datatables.net/reference/option/columns.type .
'type' => new external_value(PARAM_ALPHANUMEXT, 'Column type', VALUE_OPTIONAL),
'sortable' => new external_value(PARAM_BOOL, 'Whether this column is sortable', VALUE_OPTIONAL, false),
'allowHTML' => new external_value(PARAM_BOOL, 'Whether this column contains HTML', VALUE_OPTIONAL, false),
'formatter' => new external_value(PARAM_ALPHANUMEXT, 'Formatter name', VALUE_OPTIONAL),
])),
'data' => new external_value(PARAM_RAW), // For now it will be json encoded.
], '', VALUE_OPTIONAL),
'warnings' => new external_warnings()
]);
}
}
+152
View File
@@ -0,0 +1,152 @@
<?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/>.
namespace mod_bigbluebuttonbn\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 core_external\restricted_context_exception;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\local\proxy\bigbluebutton_proxy;
use mod_bigbluebuttonbn\meeting;
/**
* External service to fetch meeting information.
*
* @package mod_bigbluebuttonbn
* @category external
* @copyright 2018 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class meeting_info extends external_api {
/**
* Returns description of method parameters.
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'bigbluebuttonbnid' => new external_value(PARAM_INT, 'bigbluebuttonbn instance id'),
'groupid' => new external_value(PARAM_INT, 'bigbluebuttonbn group id', VALUE_DEFAULT, 0),
'updatecache' => new external_value(PARAM_BOOL, 'update cache ?', VALUE_DEFAULT, false),
]);
}
/**
* Fetch meeting information.
*
* @param int $bigbluebuttonbnid the bigbluebuttonbn instance id
* @param int $groupid
* @param bool $updatecache
* @return array
* @throws \moodle_exception
* @throws restricted_context_exception
*/
public static function execute(
int $bigbluebuttonbnid,
int $groupid,
bool $updatecache = false
): array {
// Validate the bigbluebuttonbnid ID.
[
'bigbluebuttonbnid' => $bigbluebuttonbnid,
'groupid' => $groupid,
'updatecache' => $updatecache,
] = self::validate_parameters(self::execute_parameters(), [
'bigbluebuttonbnid' => $bigbluebuttonbnid,
'groupid' => $groupid,
'updatecache' => $updatecache,
]);
// Fetch the session, features, and profile.
$instance = instance::get_from_instanceid($bigbluebuttonbnid);
$instance->set_group_id($groupid);
if (!groups_group_visible($groupid, $instance->get_course(), $instance->get_cm())) {
throw new restricted_context_exception();
}
$context = $instance->get_context();
// Validate that the user has access to this activity and to manage recordings.
self::validate_context($context);
// Check if the BBB server is working.
$serverversion = bigbluebutton_proxy::get_server_version();
if ($serverversion === null) {
throw new \moodle_exception('general_error_no_answer', 'mod_bigbluebuttonbn',
bigbluebutton_proxy::get_server_not_available_url($instance),
bigbluebutton_proxy::get_server_not_available_message($instance));
}
$meetinginfo = (array) meeting::get_meeting_info_for_instance($instance, $updatecache);
// Make the structure WS friendly.
array_walk($meetinginfo['features'], function(&$value, $key){
$value = ['name' => $key, 'isenabled' => (bool) $value];
});
return $meetinginfo;
}
/**
* Describe the return structure of the external service.
*
* @return external_single_structure
* @since Moodle 3.0
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([
'cmid' => new external_value(PARAM_INT, 'CM id'),
'userlimit' => new external_value(PARAM_INT, 'User limit'),
'bigbluebuttonbnid' => new external_value(PARAM_RAW, 'bigbluebuttonbn instance id'),
'groupid' => new external_value(PARAM_INT, 'bigbluebuttonbn group id', VALUE_DEFAULT, 0),
'meetingid' => new external_value(PARAM_RAW, 'Meeting id'),
'openingtime' => new external_value(PARAM_INT, 'Opening time', VALUE_OPTIONAL),
'closingtime' => new external_value(PARAM_INT, 'Closing time', VALUE_OPTIONAL),
'statusrunning' => new external_value(PARAM_BOOL, 'Status running', VALUE_OPTIONAL),
'statusclosed' => new external_value(PARAM_BOOL, 'Status closed', VALUE_OPTIONAL),
'statusopen' => new external_value(PARAM_BOOL, 'Status open', VALUE_OPTIONAL),
'statusmessage' => new external_value(PARAM_TEXT, 'Status message', VALUE_OPTIONAL),
'startedat' => new external_value(PARAM_INT, 'Started at', VALUE_OPTIONAL),
'moderatorcount' => new external_value(PARAM_INT, 'Moderator count', VALUE_OPTIONAL),
'participantcount' => new external_value(PARAM_INT, 'Participant count', VALUE_OPTIONAL),
'moderatorplural' => new external_value(PARAM_BOOL, 'Several moderators ?', VALUE_OPTIONAL),
'participantplural' => new external_value(PARAM_BOOL, 'Several participants ?', VALUE_OPTIONAL),
'canjoin' => new external_value(PARAM_BOOL, 'Can join'),
'ismoderator' => new external_value(PARAM_BOOL, 'Is moderator'),
'presentations' => new external_multiple_structure(
new external_single_structure([
'url' => new external_value(PARAM_URL, 'presentation URL'),
'iconname' => new external_value(PARAM_RAW, 'icon name'),
'icondesc' => new external_value(PARAM_TEXT, 'icon text'),
'name' => new external_value(PARAM_TEXT, 'presentation name'),
])
),
'joinurl' => new external_value(PARAM_URL, 'Join URL'),
'guestaccessenabled' => new external_value(PARAM_BOOL, 'Guest access enabled', VALUE_OPTIONAL),
'guestjoinurl' => new external_value(PARAM_URL, 'Guest URL', VALUE_OPTIONAL),
'guestpassword' => new external_value(PARAM_RAW, 'Guest join password', VALUE_OPTIONAL),
'features' => new external_multiple_structure(
new external_single_structure([
'name' => new external_value(PARAM_ALPHA, 'Feature name.'),
'isenabled' => new external_value(PARAM_BOOL, 'Whether the feature is enabled.'),
]), 'List of features for the instance', VALUE_OPTIONAL
),
]
);
}
}
@@ -0,0 +1,134 @@
<?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/>.
namespace mod_bigbluebuttonbn\external;
use coding_exception;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_single_structure;
use core_external\external_value;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\local\bigbluebutton\recordings\recording_action;
use mod_bigbluebuttonbn\recording;
/**
* External service to update the details of one recording.
*
* @package mod_bigbluebuttonbn
* @category external
* @copyright 2018 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class update_recording extends external_api {
/**
* Updates a recording
*
* @param int $bigbluebuttonbnid the bigbluebuttonbn instance id, either the same as the one set in the recording or a new
* instance to import the recording into.
* @param int $recordingid
* @param string $action
* @param string|null $additionaloptions
* @return array (empty array for now)
*/
public static function execute(
int $bigbluebuttonbnid,
int $recordingid,
string $action,
string $additionaloptions = null
): array {
// Validate the bigbluebuttonbnid ID.
[
'bigbluebuttonbnid' => $bigbluebuttonbnid,
'recordingid' => $recordingid,
'action' => $action,
'additionaloptions' => $additionaloptions,
] = self::validate_parameters(self::execute_parameters(), [
'bigbluebuttonbnid' => $bigbluebuttonbnid,
'recordingid' => $recordingid,
'action' => $action,
'additionaloptions' => $additionaloptions,
]);
switch ($action) {
case 'delete':
case 'edit':
case 'protect':
case 'publish':
case 'unprotect':
case 'unpublish':
case 'import':
break;
default:
throw new coding_exception("Unknown action '{$action}'");
}
$instance = instance::get_from_instanceid($bigbluebuttonbnid);
$recordingcontext = $instance->get_context();
self::validate_context($recordingcontext);
require_capability('mod/bigbluebuttonbn:managerecordings', $recordingcontext);
require_capability("mod/bigbluebuttonbn:{$action}recordings", $recordingcontext);
// Fetch the session, features, and profile.
$recording = new recording($recordingid);
// Check both the recording instance context and the bbb context.
$relatedinstance = instance::get_from_instanceid($recording->get('bigbluebuttonbnid'));
if ($relatedinstance) {
$recordingcontext = $relatedinstance->get_context();
// Validate that the user has access to this activity and to manage recordings.
self::validate_context($recordingcontext);
require_capability('mod/bigbluebuttonbn:managerecordings', $recordingcontext);
require_capability("mod/bigbluebuttonbn:{$action}recordings", $recordingcontext);
}
$additionaloptionsobject = $additionaloptions ? json_decode($additionaloptions) : null;
// Specific action such as import, delete, publish, unpublish, edit,....
if (method_exists(recording_action::class, "$action")) {
forward_static_call(
['\mod_bigbluebuttonbn\local\bigbluebutton\recordings\recording_action', "$action"],
$recording,
$instance,
$additionaloptionsobject
);
}
return [];
}
/**
* Returns description of method parameters
*
* @return external_function_parameters
*/
public static function execute_parameters(): external_function_parameters {
return new external_function_parameters([
'bigbluebuttonbnid' => new external_value(
PARAM_INT,
'bigbluebuttonbn instance id, this might be a different one from the one set in recordingid in case of importing'
),
'recordingid' => new external_value(PARAM_INT, 'The moodle internal recording ID'),
'action' => new external_value(PARAM_ALPHANUMEXT, 'The action to perform'),
'additionaloptions' => new external_value(PARAM_RAW, 'Additional options', VALUE_REQUIRED, null),
]);
}
/**
* Describe the return structure of the external service.
*
* @return external_single_structure
* @since Moodle 3.0
*/
public static function execute_returns(): external_single_structure {
return new external_single_structure([]);
}
}
@@ -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/>.
namespace mod_bigbluebuttonbn\external;
use context_module;
use core_external\external_api;
use core_external\external_function_parameters;
use core_external\external_single_structure;
use core_external\external_value;
use core_external\external_warnings;
use mod_bigbluebuttonbn\instance;
/**
* External service to trigger the course module viewed event and update the module completion status
*
* This is mainly used by the mobile application.
*
* @package mod_bigbluebuttonbn
* @category external
* @copyright 2018 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class view_bigbluebuttonbn extends external_api {
/**
* Returns description of method parameters
*
* @return external_function_parameters
* @since Moodle 3.0
*/
public static function execute_parameters() {
return new external_function_parameters([
'bigbluebuttonbnid' => new external_value(PARAM_INT, 'bigbluebuttonbn instance id'),
]
);
}
/**
* Trigger the course module viewed event and update the module completion status.
*
* @param int $instanceid the bigbluebuttonbn instance id
* @return array of warnings and status result
* @since Moodle 3.0
*/
public static function execute($instanceid) {
global $CFG;
require_once($CFG->dirroot . "/mod/bigbluebuttonbn/lib.php");
['bigbluebuttonbnid' => $instanceid] = self::validate_parameters(
self::execute_parameters(),
['bigbluebuttonbnid' => $instanceid]
);
$instance = instance::get_from_instanceid($instanceid);
if (empty($instance)) {
return [
'status' => false,
'warnings' => [
[
'item' => 'mod_bigbluebuttonbn',
'itemid' => 0,
'warningcode' => 'nosuchinstance',
'message' => get_string('nosuchinstance', 'mod_bigbluebuttonbn',
(object) ['id' => $instanceid, 'entity' => 'bigbluebuttonbn'])
]
]
];
}
$context = context_module::instance($instance->get_cm_id());
self::validate_context($context);
require_capability('mod/bigbluebuttonbn:view', $context);
// Call the bigbluebuttonbn/lib API.
bigbluebuttonbn_view($instance->get_instance_data(), $instance->get_course(), $instance->get_cm(), $context);
return [
'status' => true,
'warnings' => []
];
}
/**
* Returns description of method result value
*
* @return \core_external\external_description
* @since Moodle 3.0
*/
public static function execute_returns() {
return new external_single_structure([
'status' => new external_value(PARAM_BOOL, 'status: true if success'),
'warnings' => new external_warnings()
]
);
}
}
@@ -0,0 +1,220 @@
<?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/>.
namespace mod_bigbluebuttonbn\form;
use context;
use core_form\dynamic_form;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\local\exceptions\bigbluebutton_exception;
use mod_bigbluebuttonbn\task\send_guest_emails;
use moodle_exception;
use moodle_url;
use MoodleQuickForm;
/**
* Popup form to add new guests to a meeting and show/copy credential to access the guest login page.
*
* @package mod_bigbluebuttonbn
* @copyright 2022 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent [at] call-learning [dt] fr)
*/
class guest_add extends dynamic_form {
/**
* Max length for credential and url fields.
*/
const MAX_INPUT_LENGTH = 35;
/**
* Process the form submission, used if form was submitted via AJAX.
*
* @return array
*/
public function process_dynamic_submission(): array {
global $USER;
$data = $this->get_data();
$allmails = [];
if (!empty($data->emails)) {
$emails = explode(',', $data->emails);
foreach ($emails as $email) {
$email = trim($email);
if (validate_email($email)) {
$allmails[] = $email;
}
}
$adhoctask = new send_guest_emails();
$adhoctask->set_custom_data(
[
'emails' => $allmails,
'useridfrom' => $USER->id
]
);
$adhoctask->set_instance_id($data->id);
\core\task\manager::queue_adhoc_task($adhoctask);
}
return [
'result' => true,
'emails' => join(', ', $allmails),
'emailcount' => count($allmails),
'errors' => ''
];
}
/**
* Perform some validation.
*
* @param array $formdata
* @param array $files
* @return array
*/
public function validation($formdata, $files): array {
$errors = [];
$emailserrors = [];
if (!empty($formdata['emails'])) {
$emails = explode(',', $formdata['emails']);
foreach ($emails as $email) {
$email = trim($email);
if (!validate_email($email)) {
$emailserrors[] .= get_string('guestaccess_emails_invalidemail', 'mod_bigbluebuttonbn', $email);
}
}
}
if (!empty($emailserrors)) {
$errors['emails'] = \html_writer::alist($emailserrors);
}
return $errors;
}
/**
* Load in existing data as form defaults (not applicable).
*
* @return void
*/
public function set_data_for_dynamic_submission(): void {
$instance = $this->get_instance_from_params();
$data = [
'id' => $instance->get_instance_id(),
'groupid' => $instance->get_group_id(),
'guestjoinurl' => $instance->get_guest_access_url(),
'guestpassword' => $instance->get_guest_access_password(),
];
$this->set_data($data);
}
/**
* Get BigblueButton instance from context params
*
* @return instance
* @throws moodle_exception
*/
protected function get_instance_from_params(): instance {
$bbid = $this->optional_param('id', null, PARAM_INT);
$groupid = $this->optional_param('groupid', null, PARAM_INT);
if (empty($bbid)) {
throw new moodle_exception('guestaccess_add_no_id', 'mod_bigbluebuttonbn');
}
$instance = instance::get_from_instanceid($bbid);
if ($groupid) {
$instance->set_group_id($groupid);
}
return $instance;
}
/**
* Form definition
*/
protected function definition() {
self::add_meeting_links_elements($this->_form);
$mform = $this->_form;
$mform->addElement('text', 'emails',
get_string('guestaccess_emails', 'mod_bigbluebuttonbn'),
);
$mform->addHelpButton('emails', 'guestaccess_emails', 'mod_bigbluebuttonbn');
$mform->setDefault('emails', '');
$mform->setType('emails', PARAM_RAW);
$mform->addElement('hidden', 'id');
$mform->setType('id', PARAM_INT);
$mform->addElement('hidden', 'groupid');
$mform->setType('groupid', PARAM_INT);
}
/**
* Add meeting links element. Helper for this form and the mod_form (module form)
*
* @param MoodleQuickForm $mform
* @return void
*/
public static function add_meeting_links_elements(MoodleQuickForm &$mform): void {
global $CFG;
MoodleQuickForm::registerElementType('text_with_copy',
"$CFG->dirroot/mod/bigbluebuttonbn/classes/form/text_with_copy_element.php",
text_with_copy_element::class);
$mform->addElement('text_with_copy', 'guestjoinurl',
get_string('guestaccess_meeting_link', 'mod_bigbluebuttonbn'),
[
'copylabel' => get_string('guestaccess_copy_link', 'mod_bigbluebuttonbn'),
'size' => self::MAX_INPUT_LENGTH,
'readonly' => 'readonly'
]
);
$mform->setType('guestjoinurl', PARAM_URL);
$mform->addElement('text_with_copy', 'guestpassword',
get_string('guestaccess_meeting_password', 'mod_bigbluebuttonbn'),
[
'copylabel' => get_string('guestaccess_copy_password', 'mod_bigbluebuttonbn'),
'readonly' => 'readonly',
'size' => self::MAX_INPUT_LENGTH,
]
);
$mform->setType('guestpassword', PARAM_RAW);
}
/**
* Check if current user has access to this form, otherwise throw exception.
*
* @return void
* @throws moodle_exception
*/
protected function check_access_for_dynamic_submission(): void {
$context = $this->get_context_for_dynamic_submission();
$instance = instance::get_from_cmid($context->instanceid);
if (!$instance->is_moderator()) {
throw new \restricted_context_exception();
}
}
/**
* Return form context
*
* @return context
*/
protected function get_context_for_dynamic_submission(): context {
$instance = $this->get_instance_from_params();
return $instance->get_context();
}
/**
* Returns url to set in $PAGE->set_url() when form is being rendered or submitted via AJAX.
*
* @return moodle_url
*/
protected function get_page_url_for_dynamic_submission(): moodle_url {
$context = $this->get_context_for_dynamic_submission();
return new moodle_url('/mod/bigbluebuttonbn/view.php', ['id' => $context->instanceid]);
}
}
@@ -0,0 +1,78 @@
<?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/>.
namespace mod_bigbluebuttonbn\form;
defined('MOODLE_INTERNAL') || die;
global $CFG;
require_once($CFG->libdir . '/formslib.php');
/**
* Guest login form.
*
* @package mod_bigbluebuttonbn
* @copyright 2022 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent [at] call-learning [dt] fr)
*/
class guest_login extends \moodleform {
/**
* Form definition
*/
protected function definition() {
global $USER;
$mform = $this->_form;
$mform->addElement('text', 'username',
get_string('guestaccess_username', 'mod_bigbluebuttonbn'));
$mform->setType('username', PARAM_NOTAGS);
$mform->addRule('username',
get_string('required'), 'required', null, 'client');
if (isloggedin() && !isguestuser()) {
$mform->setConstant('username', fullname($USER));
$mform->freeze('username');
}
$mform->addElement('password', 'password',
get_string('guestaccess_password', 'mod_bigbluebuttonbn'));
$mform->setType('password', PARAM_RAW);
$mform->addRule('password',
get_string('required'), 'required', null, 'client');
$mform->addElement('hidden', 'uid', $this->_customdata['uid']);
$mform->setType('uid', PARAM_ALPHANUMEXT);
$this->add_action_buttons(false, get_string('guestaccess_join_meeting', 'mod_bigbluebuttonbn'));
}
/**
* Validate form
*
* @param array $data
* @param array $files
* @return array
* @throws \coding_exception
*/
public function validation($data, $files): array {
$errors = parent::validation($data, $files);
$instance = $this->_customdata['instance'];
if ($data['password'] != $instance->get_guest_access_password()) {
$errors['password'] = get_string('guestaccess_meeting_invalid_password', 'mod_bigbluebuttonbn');
}
return $errors;
}
}
@@ -0,0 +1,98 @@
<?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/>.
namespace mod_bigbluebuttonbn\form;
use MoodleQuickForm_text;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once("$CFG->libdir/form/text.php");
/**
* Text type form element with a copy widget
*
* Contains HTML class for a text type element and a link that will copy its content in the copy/paste buffer
*
* @package mod_bigbluebuttonbn
* @copyright 2022 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent [at] call-learning [dt] fr)
*/
class text_with_copy_element extends MoodleQuickForm_text {
/** @var string an element template. */
public $_groupElementTemplate;
/**
* Accepts a renderer
*
* @param object $renderer An HTML_QuickForm_Renderer object
* @param bool $required Whether an element is required
* @param string $error An error message associated with an element
* @return void
*/
public function accept(&$renderer, $required = false, $error = null) {
global $OUTPUT;
$elementname = $this->getName();
// Make sure the element has an id.
$this->_generateId();
$advanced = isset($renderer->_advancedElements[$elementname]);
$elementcontext = $this->export_for_template($OUTPUT);
$helpbutton = '';
if (method_exists($this, 'getHelpButton')) {
$helpbutton = $this->getHelpButton();
}
$label = $this->getLabel();
$text = '';
if (method_exists($this, 'getText')) {
// There currently exists code that adds a form element with an empty label.
// If this is the case then set the label to the description.
if (empty($label)) {
$label = $this->getText();
} else {
$text = $this->getText();
}
}
$context = array(
'element' => $elementcontext,
'label' => $label,
'text' => $text,
'required' => $required,
'advanced' => $advanced,
'helpbutton' => $helpbutton,
'error' => $error,
'copylabel' => $this->_attributes['copylabel'] ?? get_string('copy', 'core_editor')
);
$html = $OUTPUT->render_from_template('mod_bigbluebuttonbn/element_text_with_copy', $context);
if ($renderer->_inGroup) {
$this->_groupElementTemplate = $html;
}
if (($renderer->_inGroup) && !empty($renderer->_groupElementTemplate)) {
$renderer->_groupElementTemplate = $html;
} else if (!isset($renderer->_templates[$elementname])) {
$renderer->_templates[$elementname] = $html;
}
if (in_array($elementname, $renderer->_stopFieldsetElements) && $renderer->_fieldsetsOpen > 0) {
$renderer->_html .= $renderer->_closeFieldsetTemplate;
$renderer->_fieldsetsOpen--;
}
$renderer->_html .= $html;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,135 @@
<?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/>.
namespace mod_bigbluebuttonbn\local\bigbluebutton\recordings;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\recording;
use mod_bigbluebuttonbn\local\config;
/**
* Collection of helper methods for handling recordings actions in Moodle.
*
* Utility class for meeting actions
*
* @package mod_bigbluebuttonbn
* @copyright 2021 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class recording_action {
/**
* Import recording
*
* @param recording $recording
* @param instance $targetinstance
*/
public static function import(recording $recording, instance $targetinstance): void {
$recording->create_imported_recording($targetinstance);
}
/**
* Helper for performing delete on recordings.
*
* @param recording $recording
*/
public static function delete(recording $recording): void {
// As the recordingid was not identified as imported recording link, execute delete on a real recording.
// Step 1, delete imported links associated to the recording.
$recordingstodelete = recording::get_records(['recordingid' => $recording->get('recordingid'),
'imported' => true]);
foreach ($recordingstodelete as $rec) {
$rec->delete();
}
$recording->delete();
}
/**
* Helper for performing edit on recordings.
*
* @param recording $recording
*/
public static function edit(recording $recording): void {
$recording->update();
}
/**
* Helper for performing unprotect on recordings.
*
* @param recording $recording
*/
public static function unprotect(recording $recording): void {
if (!(boolean) config::get('recording_protect_editable')) {
// Recording protect action through UI is disabled, there is no need to do anything else.
throw new \moodle_exception('cannotperformaction', 'mod_bigblubuebuttobn', '', 'unprotect');
}
if ($recording->get('imported')) {
// Imported recordings can not be unprotected. There is no need to do anything else.
throw new \moodle_exception('cannotperformaction', 'mod_bigblubuebuttobn', '', 'unprotect');
}
$recording->set('protected', false);
$recording->update();
}
/**
* Helper for performing protect on recordings.
*
* @param recording $recording
*/
public static function protect(recording $recording): void {
if (!(boolean) config::get('recording_protect_editable')) {
// Recording protect action through UI is disabled, there is no need to do anything else.
throw new \moodle_exception('cannotperformaction', 'mod_bigblubuebuttobn', '', 'protect');
}
if ($recording->get('imported')) {
// Imported recordings can not be unprotected. There is no need to do anything else.
throw new \moodle_exception('cannotperformaction', 'mod_bigblubuebuttobn', '', 'protect');
}
$recording->set('protected', true);
$recording->update();
}
/**
* Helper for performing unpublish on recordings.
*
* @param recording $recording
*/
public static function unpublish(recording $recording): void {
if ($recording->get('imported')) {
/* Since the recording link is the one fetched from the BBB server, imported recordings can not be
* unpublished. There is no need to do anything else.
*/
throw new \moodle_exception('cannotperformaction', 'mod_bigblubuebuttobn', '', 'unpublish');
}
$recording->set('published', false);
$recording->update();
}
/**
* Helper for performing publish on recordings.
*
* @param recording $recording
*/
public static function publish(recording $recording): void {
if ($recording->get('imported')) {
/* Since the recording link is the one fetched from the BBB server, imported recordings can not be
* unpublished. There is no need to do anything else.
*/
throw new \moodle_exception('cannotperformaction', 'mod_bigblubuebuttobn', '', 'publish');
}
$recording->set('published', true);
$recording->update();
}
}
@@ -0,0 +1,319 @@
<?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/>.
namespace mod_bigbluebuttonbn\local\bigbluebutton\recordings;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\local\config;
use mod_bigbluebuttonbn\local\helpers\roles;
use mod_bigbluebuttonbn\local\proxy\bigbluebutton_proxy;
use mod_bigbluebuttonbn\output\recording_description_editable;
use mod_bigbluebuttonbn\output\recording_name_editable;
use mod_bigbluebuttonbn\output\recording_row_actionbar;
use mod_bigbluebuttonbn\output\recording_row_playback;
use mod_bigbluebuttonbn\output\recording_row_preview;
use mod_bigbluebuttonbn\recording;
use stdClass;
/**
* The recordings_data.
*
* @package mod_bigbluebuttonbn
* @copyright 2021 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent.david [at] call-learning [dt] fr)
* @author Jesus Federico (jesus [at] blindsidenetworks [dt] com)
*/
class recording_data {
/**
* Get the full recording table
*
* @param array $recordings
* @param array $tools
* @param instance|null $instance
* @param int $courseid
* @return array
*/
public static function get_recording_table(array $recordings, array $tools, instance $instance = null,
int $courseid = 0): array {
$typeprofiles = bigbluebutton_proxy::get_instance_type_profiles();
$typeprofile = empty($instance) ? $typeprofiles[0] : $typeprofiles[$instance->get_type()];
$lang = get_string('locale', 'core_langconfig');
$locale = substr($lang, 0, strpos($lang, '.'));
$tabledata = [
'activity' => empty($instance) ? '' : bigbluebutton_proxy::view_get_activity_status($instance),
'ping_interval' => (int) config::get('waitformoderator_ping_interval') * 1000,
'locale' => substr($locale, 0, strpos($locale, '_')),
'profile_features' => $typeprofile['features'],
'columns' => [],
'data' => '',
];
$hascapabilityincourse = empty($instance) && roles::has_capability_in_course($courseid,
'mod/bigbluebuttonbn:managerecordings');
$data = [];
// Build table content.
foreach ($recordings as $recording) {
$rowtools = $tools;
// Protected recordings may be enabled or disabled from UI through configuration.
if (!(boolean) config::get('recording_protect_editable')) {
$rowtools = array_diff($rowtools, ['protect', 'unprotect']);
}
// Protected recordings is not a standard feature, remove actions when protected flag is not present.
if (in_array('protect', $rowtools) && $recording->get('protected') === null) {
$rowtools = array_diff($rowtools, ['protect', 'unprotect']);
}
$rowdata = self::row($instance, $recording, $rowtools);
if (!empty($rowdata)) {
$data[] = $rowdata;
}
}
$columns = [
[
'key' => 'playback',
'label' => get_string('view_recording_playback', 'bigbluebuttonbn'),
'width' => '125px',
'type' => 'html',
'allowHTML' => true,
],
[
'key' => 'recording',
'label' => get_string('view_recording_name', 'bigbluebuttonbn'),
'width' => '125px',
'type' => 'html',
'allowHTML' => true,
],
[
'key' => 'description',
'label' => get_string('view_recording_description', 'bigbluebuttonbn'),
'sortable' => true,
'width' => '250px',
'type' => 'html',
'allowHTML' => true,
],
];
// Initialize table headers.
$ispreviewenabled = !empty($instance) && self::preview_enabled($instance);
$ispreviewenabled = $ispreviewenabled || $hascapabilityincourse;
if ($ispreviewenabled) {
$columns[] = [
'key' => 'preview',
'label' => get_string('view_recording_preview', 'bigbluebuttonbn'),
'width' => '250px',
'type' => 'html',
'allowHTML' => true,
];
}
$columns[] = [
'key' => 'date',
'label' => get_string('view_recording_date', 'bigbluebuttonbn'),
'sortable' => true,
'width' => '225px',
'type' => 'html',
'formatter' => 'customDate',
];
$columns[] = [
'key' => 'duration',
'label' => get_string('view_recording_duration', 'bigbluebuttonbn'),
'width' => '50px',
'allowHTML' => false,
'sortable' => true,
];
// Either instance is empty and we must show the toolbar (with restricted content) or we check
// specific rights related to the instance.
$canmanagerecordings = !empty($instance) && $instance->can_manage_recordings();
$canmanagerecordings = $canmanagerecordings || $hascapabilityincourse;
if ($canmanagerecordings) {
$columns[] = [
'key' => 'actionbar',
'label' => get_string('view_recording_actionbar', 'bigbluebuttonbn'),
'width' => '120px',
'type' => 'html',
'allowHTML' => true,
];
}
$tabledata['columns'] = $columns;
$tabledata['data'] = json_encode($data);
return $tabledata;
}
/**
* Helper function builds a row for the data used by the recording table.
*
* TODO: replace this with templates whenever possible so we just
* return the data via the API.
*
* @param instance|null $instance $instance
* @param recording $rec a recording row
* @param array|null $tools
* @param int|null $courseid
* @return stdClass|null
*/
public static function row(?instance $instance, recording $rec, ?array $tools = null, ?int $courseid = 0): ?stdClass {
global $PAGE;
$hascapabilityincourse = empty($instance) && roles::has_capability_in_course($courseid,
'mod/bigbluebuttonbn:managerecordings');
$renderer = $PAGE->get_renderer('mod_bigbluebuttonbn');
foreach ($tools as $key => $tool) {
if ((!empty($instance) && !$instance->can_perform_on_recordings($tool))
|| (empty($instance) && !$hascapabilityincourse)) {
unset($tools[$key]);
}
}
if (!self::include_recording_table_row($instance, $rec)) {
return null;
}
$rowdata = new stdClass();
// Set recording_playback.
$recordingplayback = new recording_row_playback($rec, $instance);
$rowdata->playback = $renderer->render($recordingplayback);
if (empty($instance)) {
// Set activity name.
$rowdata->recording = $rec->get('name');
// Set activity description.
$rowdata->description = $rec->get('description');
} else {
// Set activity name.
$recordingname = new recording_name_editable($rec, $instance);
$rowdata->recording = $renderer->render_inplace_editable($recordingname);
// Set activity description.
$recordingdescription = new recording_description_editable($rec, $instance);
$rowdata->description = $renderer->render_inplace_editable($recordingdescription);
}
if ((!empty($instance) && self::preview_enabled($instance)) || $hascapabilityincourse) {
// Set recording_preview.
$rowdata->preview = '';
if ($rec->get('playbacks')) {
$rowpreview = new recording_row_preview($rec);
$rowdata->preview = $renderer->render($rowpreview);
}
}
// Set date.
$starttime = $rec->get('starttime');
$rowdata->date = !is_null($starttime) ? floatval($starttime) : 0;
// Set duration.
$rowdata->duration = self::row_duration($rec);
// Set actionbar, if user is allowed to manage recordings.
if ((!empty($instance) && $instance->can_manage_recordings()) || $hascapabilityincourse) {
$actionbar = new recording_row_actionbar($rec, $tools);
$rowdata->actionbar = $renderer->render($actionbar);
}
return $rowdata;
}
/**
* Helper function evaluates if recording preview should be included.
*
* @param instance $instance
* @return bool
*/
public static function preview_enabled(instance $instance): bool {
return $instance->get_instance_var('recordings_preview') == '1';
}
/**
* Helper function converts recording duration used in row for the data used by the recording table.
*
* @param recording $recording
* @return int
*/
protected static function row_duration(recording $recording): int {
$playbacks = $recording->get('playbacks');
if (empty($playbacks)) {
return 0;
}
foreach ($playbacks as $playback) {
// Ignore restricted playbacks.
if (array_key_exists('restricted', $playback) && strtolower($playback['restricted']) == 'true') {
continue;
}
// Take the length form the fist playback with an actual value.
if (!empty($playback['length'])) {
return intval($playback['length']);
}
}
return 0;
}
/**
* Helper function to handle yet unknown recording types
*
* @param string $playbacktype : for now presentation, video, statistics, capture, notes, podcast
* @return string the matching language string or a capitalised version of the provided string
*/
public static function type_text(string $playbacktype): string {
// Check first if string exists, and if it does not, just default to the capitalised version of the string.
$text = ucwords($playbacktype);
$typestringid = 'view_recording_format_' . $playbacktype;
if (get_string_manager()->string_exists($typestringid, 'bigbluebuttonbn')) {
$text = get_string($typestringid, 'bigbluebuttonbn');
}
return $text;
}
/**
* Helper function evaluates if recording row should be included in the table.
*
* @param instance|null $instance
* @param recording $rec a bigbluebuttonbn_recordings row
* @return bool
*/
protected static function include_recording_table_row(?instance $instance, recording $rec): bool {
if (empty($instance)) {
return roles::has_capability_in_course($rec->get('courseid'), 'mod/bigbluebuttonbn:managerecordings');
}
// Exclude unpublished recordings, only if user has no rights to manage them.
if (!$rec->get('published') && !$instance->can_manage_recordings()) {
return false;
}
// Imported recordings are always shown as long as they are published.
if ($rec->get('imported')) {
return true;
}
// When show imported recordings only is enabled, exclude all other recordings.
if ($instance->get_recordings_imported() && !$rec->get('imported')) {
return false;
}
// Administrators and moderators are always allowed.
if ($instance->is_admin() || $instance->is_moderator()) {
return true;
}
// When groups are enabled, exclude those to which the user doesn't have access to.
if ($instance->uses_groups() && !$instance->can_manage_recordings()) {
if (groups_get_activity_groupmode($instance->get_cm()) == VISIBLEGROUPS) {
// In case we are in visible group mode, we show all recordings.
return true;
}
// Else we check if the Recording group is the same as the instance. Instance group
// being the group chosen for this instance.
return intval($rec->get('groupid')) === $instance->get_group_id();
}
return true;
}
}
@@ -0,0 +1,283 @@
<?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/>.
namespace mod_bigbluebuttonbn\local;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\local\proxy\bigbluebutton_proxy;
use mod_bigbluebuttonbn\recording;
/**
* Handles the global configuration based on config.php.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Jesus Federico (jesus [at] blindsidenetworks [dt] com)
*/
class config {
/** @var string Default bigbluebutton server url */
public const DEFAULT_SERVER_URL = 'https://test-moodle.blindsidenetworks.com/bigbluebutton/';
/** @var string Default bigbluebutton server shared secret */
public const DEFAULT_SHARED_SECRET = '0b21fcaf34673a8c3ec8ed877d76ae34';
/** @var string the default bigbluebutton checksum algorithm */
public const DEFAULT_CHECKSUM_ALGORITHM = 'SHA256';
/** @var array list of supported bigbluebutton checksum algorithm */
const CHECKSUM_ALGORITHMS = [
self::DEFAULT_CHECKSUM_ALGORITHM,
'SHA1',
'SHA512'
];
/**
* Returns moodle version.
*
* @return string
*/
protected static function get_moodle_version_major(): string {
global $CFG;
$versionarray = explode('.', $CFG->version);
return $versionarray[0];
}
/**
* Returns configuration default values.
*
* @return array
*/
protected static function defaultvalues(): array {
return [
'server_url' => '',
'shared_secret' => '',
'voicebridge_editable' => false,
'importrecordings_enabled' => false,
'importrecordings_from_deleted_enabled' => false,
'waitformoderator_default' => false,
'waitformoderator_editable' => true,
'waitformoderator_ping_interval' => '10',
'waitformoderator_cache_ttl' => '60',
'userlimit_default' => '0',
'userlimit_editable' => false,
'preuploadpresentation_editable' => false,
'recordingready_enabled' => false,
'recordingstatus_enabled' => false,
'meetingevents_enabled' => false,
'participant_moderator_default' => '0',
'profile_picture_enabled' => false,
'scheduled_pre_opening' => '10',
'recordings_enabled' => true,
'recordings_deleted_default' => false,
'recordings_deleted_editable' => false,
'recordings_imported_default' => false,
'recordings_imported_editable' => false,
'recordings_preview_default' => true,
'recordings_preview_editable' => false,
'recording_default' => true,
'recording_editable' => true,
'recording_refresh_period' => recording::RECORDING_REFRESH_DEFAULT_PERIOD,
'recording_all_from_start_default' => false,
'recording_all_from_start_editable' => false,
'recording_hide_button_default' => false,
'recording_hide_button_editable' => false,
'recording_protect_editable' => true,
'general_warning_message' => '',
'general_warning_roles' => 'editingteacher,teacher',
'general_warning_box_type' => 'info',
'general_warning_button_text' => '',
'general_warning_button_href' => '',
'general_warning_button_class' => '',
'muteonstart_default' => false,
'muteonstart_editable' => false,
'disablecam_default' => false,
'disablecam_editable' => true,
'disablemic_default' => false,
'disablemic_editable' => true,
'disableprivatechat_default' => false,
'disableprivatechat_editable' => true,
'disablepublicchat_default' => false,
'disablepublicchat_editable' => true,
'disablenote_default' => false,
'disablenote_editable' => true,
'hideuserlist_default' => false,
'hideuserlist_editable' => true,
'welcome_default' => '',
'welcome_editable' => true,
'default_dpa_accepted' => false,
'poll_interval' => bigbluebutton_proxy::DEFAULT_POLL_INTERVAL,
'checksum_algorithm' => self::DEFAULT_CHECKSUM_ALGORITHM,
];
}
/**
* Returns default value for an specific setting.
*
* @param string $setting
* @return string|null
*/
public static function defaultvalue(string $setting): ?string {
$defaultvalues = self::defaultvalues();
if (!array_key_exists($setting, $defaultvalues)) {
return null;
}
return $defaultvalues[$setting];
}
/**
* Returns value for an specific setting.
*
* @param string $setting
* @return string
*/
public static function get(string $setting): string {
global $CFG;
if (isset($CFG->bigbluebuttonbn[$setting])) {
return (string) $CFG->bigbluebuttonbn[$setting];
}
if (isset($CFG->{'bigbluebuttonbn_' . $setting})) {
return (string) $CFG->{'bigbluebuttonbn_' . $setting};
}
return (string) self::defaultvalue($setting);
}
/**
* Validates if recording settings are enabled.
*
* @return bool
*/
public static function recordings_enabled(): bool {
return (boolean) self::get('recordings_enabled');
}
/**
* Validates if imported recording settings are enabled.
*
* @return bool
*/
public static function importrecordings_enabled(): bool {
return (boolean) self::get('importrecordings_enabled');
}
/**
* Check if bbb server credentials are invalid.
*
* @return bool
*/
public static function server_credentials_invalid(): bool {
// Test server credentials across all versions of the plugin are flagged.
$parsedurl = parse_url(self::get('server_url'));
$defaultserverurl = parse_url(self::DEFAULT_SERVER_URL);
if (!isset($parsedurl['host'])) {
return false;
}
if (strpos($parsedurl['host'], $defaultserverurl['host']) === 0) {
return true;
}
if (strpos($parsedurl['host'], 'test-install.blindsidenetworks.com') === 0) {
return true;
}
return false;
}
/**
* Wraps current settings in an array.
*
* @return array
*/
public static function get_options(): array {
return [
'version_major' => self::get_moodle_version_major(),
'voicebridge_editable' => self::get('voicebridge_editable'),
'importrecordings_enabled' => self::get('importrecordings_enabled'),
'importrecordings_from_deleted_enabled' => self::get('importrecordings_from_deleted_enabled'),
'waitformoderator_default' => self::get('waitformoderator_default'),
'waitformoderator_editable' => self::get('waitformoderator_editable'),
'userlimit_default' => self::get('userlimit_default'),
'userlimit_editable' => self::get('userlimit_editable'),
'preuploadpresentation_editable' => self::get('preuploadpresentation_editable'),
'recordings_enabled' => self::get('recordings_enabled'),
'meetingevents_enabled' => self::get('meetingevents_enabled'),
'recordings_deleted_default' => self::get('recordings_deleted_default'),
'recordings_deleted_editable' => self::get('recordings_deleted_editable'),
'recordings_imported_default' => self::get('recordings_imported_default'),
'recordings_imported_editable' => self::get('recordings_imported_editable'),
'recordings_preview_default' => self::get('recordings_preview_default'),
'recordings_preview_editable' => self::get('recordings_preview_editable'),
'recording_default' => self::get('recording_default'),
'recording_editable' => self::get('recording_editable'),
'recording_refresh_period' => self::get('recording_refresh_period'),
'recording_all_from_start_default' => self::get('recording_all_from_start_default'),
'recording_all_from_start_editable' => self::get('recording_all_from_start_editable'),
'recording_hide_button_default' => self::get('recording_hide_button_default'),
'recording_hide_button_editable' => self::get('recording_hide_button_editable'),
'recording_protect_editable' => self::get('recording_protect_editable'),
'general_warning_message' => self::get('general_warning_message'),
'general_warning_box_type' => self::get('general_warning_box_type'),
'general_warning_button_text' => self::get('general_warning_button_text'),
'general_warning_button_href' => self::get('general_warning_button_href'),
'general_warning_button_class' => self::get('general_warning_button_class'),
'muteonstart_editable' => self::get('muteonstart_editable'),
'muteonstart_default' => self::get('muteonstart_default'),
'disablecam_editable' => self::get('disablecam_editable'),
'disablecam_default' => self::get('disablecam_default'),
'disablemic_editable' => self::get('disablemic_editable'),
'disablemic_default' => self::get('disablemic_default'),
'disableprivatechat_editable' => self::get('disableprivatechat_editable'),
'disableprivatechat_default' => self::get('disableprivatechat_default'),
'disablepublicchat_editable' => self::get('disablepublicchat_editable'),
'disablepublicchat_default' => self::get('disablepublicchat_default'),
'disablenote_editable' => self::get('disablenote_editable'),
'disablenote_default' => self::get('disablenote_default'),
'hideuserlist_editable' => self::get('hideuserlist_editable'),
'hideuserlist_default' => self::get('hideuserlist_default'),
'welcome_default' => self::get('welcome_default'),
'welcome_editable' => self::get('welcome_editable'),
'poll_interval' => self::get('poll_interval'),
'guestaccess_enabled' => self::get('guestaccess_enabled'),
];
}
/**
* Helper function returns an array with enabled features for an specific profile type.
*
* @param array $typeprofiles
* @param string|null $type
*
* @return array
*/
public static function get_enabled_features(array $typeprofiles, ?string $type = null): array {
$enabledfeatures = [];
$features = $typeprofiles[instance::TYPE_ALL]['features'];
if (!is_null($type) && key_exists($type, $typeprofiles)) {
$features = $typeprofiles[$type]['features'];
}
$enabledfeatures['showroom'] = (in_array('all', $features) || in_array('showroom', $features));
// Evaluates if recordings are enabled for the Moodle site.
$enabledfeatures['showrecordings'] = false;
if (self::recordings_enabled()) {
$enabledfeatures['showrecordings'] = (in_array('all', $features) || in_array('showrecordings', $features));
}
$enabledfeatures['importrecordings'] = false;
if (self::importrecordings_enabled()) {
$enabledfeatures['importrecordings'] = (in_array('all', $features) || in_array('importrecordings', $features));
}
return $enabledfeatures;
}
}
@@ -0,0 +1,40 @@
<?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/>.
namespace mod_bigbluebuttonbn\local\exceptions;
use mod_bigbluebuttonbn\plugin;
/**
* Class bigbluebutton_exception generic exception. This is supposed to be recoverable.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent [at] call-learning [dt] fr)
*/
class bigbluebutton_exception extends \moodle_exception {
/**
* Constructor
*
* @param string $errorcode The name of the string from error.php to print
* @param mixed $additionalinfo Extra words and phrases that might be required in the error string
*/
public function __construct($errorcode, $additionalinfo = null) {
parent::__construct($errorcode, plugin::COMPONENT, '', $additionalinfo);
}
}
@@ -0,0 +1,38 @@
<?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/>.
namespace mod_bigbluebuttonbn\local\exceptions;
use mod_bigbluebuttonbn\plugin;
/**
* The mod_bigbluebuttonbn cannot join meeting exception.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent [at] call-learning [dt] fr)
*/
class meeting_join_exception extends \moodle_exception {
/**
* Constructor
*
* @param string $errorcode The name of the string from error.php to print
*/
public function __construct($errorcode) {
parent::__construct($errorcode, plugin::COMPONENT);
}
}
@@ -0,0 +1,55 @@
<?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/>.
namespace mod_bigbluebuttonbn\local\exceptions;
/**
* Class server_not_available_exception
*
* This kind of error cannot be recovered and should be displayed to the user
* signaling that there is an error in the configuration.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent [at] call-learning [dt] fr)
*/
class server_not_available_exception extends \moodle_exception {
/**
* Constructor
*
* @param string $errorcode The name of the string from error.php to print
* @param string $module name of module
* @param string $link The url where the user will be prompted to continue. If no url is provided the user will be directed to
* the site index page.
* @param mixed $a Extra words and phrases that might be required in the error string
* @param string $debuginfo optional debugging information
*/
public function __construct($errorcode, $module = '', $link = '', $a = null, $debuginfo = null) {
global $CFG;
$hasdebugdeveloper = (
isset($CFG->debugdisplay) &&
isset($CFG->debug) &&
$CFG->debugdisplay &&
$CFG->debug === DEBUG_DEVELOPER
);
if ($hasdebugdeveloper && is_null($debuginfo)) {
$debuginfo = $this->getTraceAsString();
}
parent::__construct($errorcode, $module, $link, $a, $debuginfo);
}
}
@@ -0,0 +1,43 @@
<?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/>.
namespace mod_bigbluebuttonbn\local\extension;
/**
* A single action class to mutate the action URL.
*
* @package mod_bigbluebuttonbn
* @copyright 2023 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent@call-learning.fr)
*/
class action_url_addons {
/**
* Mutate the action URL.
*
* By design:
* 1. we should only add parameters
* 2. we cannot count on the order the subplugins are called
*
* @param string $action
* @param array $data
* @param array $metadata
* @return array associative array with the additional data and metadata (indexed by 'data' and
* 'metadata' keys).
*/
public function execute(string $action = '', array $data = [], array $metadata = []): array {
return ['data' => [], 'metadata' => []];
}
}
@@ -0,0 +1,79 @@
<?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/>.
namespace mod_bigbluebuttonbn\local\extension;
use cm_info;
/**
* A class to deal with completion rules addons in a subplugin
*
* @package mod_bigbluebuttonbn
* @copyright 2023 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent@call-learning.fr)
*/
abstract class custom_completion_addons {
/** @var cm_info The course module information object. */
protected $cm;
/** @var int The user's ID. */
protected $userid;
/** @var array The current state of core completion */
protected $completionstate;
/**
* activity_custom_completion constructor.
*
* @param cm_info $cm
* @param int $userid
* @param array|null $completionstate The current state of the core completion criteria
*/
public function __construct(cm_info $cm, int $userid, ?array $completionstate = null) {
$this->cm = $cm;
$this->userid = $userid;
$this->completionstate = $completionstate;
}
/**
* Fetches the completion state for a given completion rule.
*
* @param string $rule The completion rule.
* @return int The completion state.
*/
abstract public function get_state(string $rule): int;
/**
* Fetch the list of custom completion rules that this module defines.
*
* @return array
*/
abstract public static function get_defined_custom_rules(): array;
/**
* Returns an associative array of the descriptions of custom completion rules.
*
* @return array
*/
abstract public function get_custom_rule_descriptions(): array;
/**
* Returns an array of all completion rules, in the order they should be displayed to users.
*
* @return array
*/
abstract public function get_sort_order(): array;
}
@@ -0,0 +1,113 @@
<?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/>.
namespace mod_bigbluebuttonbn\local\extension;
use stdClass;
/**
* A class for the main mod form extension
*
* @package mod_bigbluebuttonbn
* @copyright 2023 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent@call-learning.fr)
*/
abstract class mod_form_addons {
/**
* @var \MoodleQuickForm|null moodle form
*/
protected $mform = null;
/**
* @var stdClass|null $bigbluebuttonbndata BigBlueButton data if any
*/
protected $bigbluebuttonbndata = null;
/**
* @var string|null $suffix suffix for form elements
*/
protected $suffix = null;
/**
* Constructor
*
* @param \MoodleQuickForm $mform
* @param stdClass|null $bigbluebuttonbndata
* @param string|null $suffix
*/
public function __construct(\MoodleQuickForm &$mform, ?stdClass $bigbluebuttonbndata = null, string $suffix = null) {
$this->mform = $mform;
$this->bigbluebuttonbndata = $bigbluebuttonbndata;
$this->suffix = $suffix;
}
/**
* Add new form field definition
*/
abstract public function add_fields(): void;
/**
* Validate form and returns an array of errors indexed by field name
*
* @param array $data
* @param array $files
* @return array
*/
abstract public function validation(array $data, array $files): array;
/**
* Allows modules to modify the data returned by form get_data().
* This method is also called in the bulk activity completion form.
*
* Only available on moodleform_mod.
*
* @param stdClass $data passed by reference
*/
abstract public function data_postprocessing(\stdClass &$data): void;
/**
* Can be overridden to add custom completion rules if the module wishes
* them. If overriding this, you should also override completion_rule_enabled.
* <p>
* Just add elements to the form as needed and return the list of IDs. The
* system will call disabledIf and handle other behaviour for each returned
* ID.
*
* @return string[] Array of string IDs of added items, empty array if none
*/
abstract public function add_completion_rules(): array;
/**
* Called during validation. Override to indicate, based on the data, whether
* a custom completion rule is enabled (selected).
*
* @param array $data Input data (not yet validated)
* @return bool True if one or more rules is enabled, false if none are;
* default returns false
*/
public function completion_rule_enabled(array $data): bool {
return false;
}
/**
* Form adjustments after setting data
*
* @return void
*/
public function definition_after_data() {
// Nothing for now.
}
}
@@ -0,0 +1,60 @@
<?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/>.
namespace mod_bigbluebuttonbn\local\extension;
use stdClass;
/**
* Class defining a way to deal with instance save/update/delete in extension
*
* @package mod_bigbluebuttonbn
* @copyright 2023 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent@call-learning.fr)
*/
class mod_instance_helper {
/**
* Runs any processes that must run before a bigbluebuttonbn insert/update.
*
* @param stdClass $bigbluebuttonbn BigBlueButtonBN form data
*/
public function add_instance(stdClass $bigbluebuttonbn) {
// Nothing for now.
}
/**
* Runs any processes that must be run after a bigbluebuttonbn insert/update.
*
* @param stdClass $bigbluebuttonbn BigBlueButtonBN form data
*/
public function update_instance(stdClass $bigbluebuttonbn): void {
// Nothing for now.
}
/**
* Runs any processes that must be run after a bigbluebuttonbn delete.
*
* @param int $cmid
*/
public function delete_instance(int $cmid): void {
}
/**
* Get any join table name that is used to store additional data for the instance.
* @return string[]
*/
public function get_join_tables(): array {
return [];
}
}
@@ -0,0 +1,324 @@
<?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/>.
/**
* The mod_bigbluebuttonbn files helper
*
* @package mod_bigbluebuttonbn
* @copyright 2021 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent [at] call-learning [dt] fr)
*/
namespace mod_bigbluebuttonbn\local\helpers;
use cache;
use cache_store;
use context;
use context_module;
use context_system;
use mod_bigbluebuttonbn\instance;
use moodle_url;
use stdClass;
/**
* Utility class for all files routines helper
*
* @package mod_bigbluebuttonbn
* @copyright 2021 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class files {
/**
* Helper for validating pluginfile.
*
* @param stdClass $context context object
* @param string $filearea file area
*
* @return bool|null false if file not valid
*/
public static function pluginfile_valid(stdClass $context, string $filearea): ?bool {
// Can be in context module or in context_system (if is the presentation by default).
if (!in_array($context->contextlevel, [CONTEXT_MODULE, CONTEXT_SYSTEM])) {
return false;
}
if (!array_key_exists($filearea, self::get_file_areas())) {
return false;
}
return true;
}
/**
* Helper for getting pluginfile.
*
* @param stdClass|null $course course object
* @param stdClass|null $cm course module object
* @param context $context context object
* @param string $filearea file area
* @param array $args extra arguments
*
* @return \stored_file|bool
*/
public static function pluginfile_file(?stdClass $course, ?stdClass $cm, context $context, string $filearea, array $args) {
$filename = self::get_plugin_filename($course, $cm, $context, $args);
if (!$filename) {
return false;
}
$fullpath = "/$context->id/mod_bigbluebuttonbn/$filearea/0/" . $filename;
$fs = get_file_storage();
$file = $fs->get_file_by_hash(sha1($fullpath));
if (!$file || $file->is_directory()) {
return false;
}
return $file;
}
/**
* Get a full path to the file attached as a preuploaded presentation
* or if there is none, set the presentation field will be set to blank.
*
* @param stdClass $bigbluebuttonformdata BigBlueButtonBN form data
* Note that $bigbluebuttonformdata->presentation is the id of the filearea whereas the bbb instance table
* stores the file name/path
* @return string
*/
public static function save_media_file(stdClass &$bigbluebuttonformdata): string {
if (!isset($bigbluebuttonformdata->presentation) || $bigbluebuttonformdata->presentation == '') {
return '';
}
$context = context_module::instance($bigbluebuttonformdata->coursemodule);
// Set the filestorage object.
$fs = get_file_storage();
// Save the file if it exists that is currently in the draft area.
file_save_draft_area_files($bigbluebuttonformdata->presentation, $context->id, 'mod_bigbluebuttonbn', 'presentation', 0);
// Get the file if it exists.
$files = $fs->get_area_files(
$context->id,
'mod_bigbluebuttonbn',
'presentation',
0,
'itemid, filepath, filename',
false
);
// Check that there is a file to process.
$filesrc = '';
if (count($files) == 1) {
// Get the first (and only) file.
$file = reset($files);
$filesrc = '/' . $file->get_filename();
}
return $filesrc;
}
/**
* Helper return array containing the file descriptor for a preuploaded presentation.
*
* @param context $context
* @param string $presentation matching presentation file name
* @param int $id bigbluebutton instance id
* @param bool $withnonce add nonce to the url
* @return array|null the representation of the presentation as an associative array
*/
public static function get_presentation(context $context, string $presentation, $id = null, $withnonce = false): ?array {
global $CFG;
$fs = get_file_storage();
$files = [];
$defaultpresentation = $fs->get_area_files(
context_system::instance()->id,
'mod_bigbluebuttonbn',
'presentationdefault',
0,
"filename",
false
);
$activitypresentation = $files = $fs->get_area_files(
$context->id,
'mod_bigbluebuttonbn',
'presentation',
false,
'itemid, filepath, filename',
false
);
// Presentation upload logic based on config settings.
if (empty($defaultpresentation)) {
if (empty($activitypresentation) || !\mod_bigbluebuttonbn\local\config::get('preuploadpresentation_editable')) {
return null;
}
$files = $activitypresentation;
} else {
if (empty($activitypresentation) || !\mod_bigbluebuttonbn\local\config::get('preuploadpresentation_editable')) {
$files = $defaultpresentation;
$id = null;
} else {
$files = $activitypresentation;
}
}
$pnoncevalue = 0;
if ($withnonce) {
$nonceid = 0;
if (!is_null($id)) {
$instance = instance::get_from_instanceid($id);
$nonceid = $instance->get_instance_id();
}
$pnoncevalue = self::generate_nonce($nonceid);
}
$file = null;
foreach ($files as $f) {
if (basename($f->get_filename()) == basename($presentation)) {
$file = $f;
}
}
if (!$file && !empty($files)) {
$file = reset($files);
}
if (empty($file)) {
return null; // File was not found.
}
// Note: $pnoncevalue is an int.
$url = moodle_url::make_pluginfile_url(
$file->get_contextid(),
$file->get_component(),
$file->get_filearea(),
$withnonce ? $pnoncevalue : null, // Hack: item id as a nonce.
$file->get_filepath(),
$file->get_filename()
);
return [
'icondesc' => get_mimetype_description($file),
'iconname' => file_file_icon($file),
'name' => $file->get_filename(),
'url' => $url->out(false),
];
}
/**
* Helper for getting pluginfile name.
*
* @param stdClass|null $course course object
* @param stdClass|null $cm course module object
* @param context $context context object
* @param array $args extra arguments
*
* @return string|null
*/
public static function get_plugin_filename(?stdClass $course, ?stdClass $cm, context $context, array $args): ?string {
global $DB;
if ($context->contextlevel != CONTEXT_SYSTEM) {
// Plugin has a file to use as default in general setting.
// The difference with the standard bigbluebuttonbn_pluginfile_filename() are.
// - Context is system, so we don't need to check the cmid in this case.
// - The area is "presentationdefault_cache".
if (!$DB->get_record('bigbluebuttonbn', ['id' => $cm->instance])) {
return null;
}
}
// Plugin has a file to use as default in general setting.
// The difference with the standard bigbluebuttonbn_pluginfile_filename() are.
// - Context is system, so we don't need to check the cmid in this case.
// - The area is "presentationdefault_cache".
if (count($args) > 1) {
$id = 0;
if ($cm) {
$instance = instance::get_from_cmid($cm->id);
$id = $instance->get_instance_id();
}
$actualnonce = self::get_nonce($id);
return ($args['0'] == $actualnonce) ? $args['1'] : null;
}
if (!empty($course)) {
require_course_login($course, true, $cm, true, true);
} else {
require_login(null, true, $cm, true, true);
}
if (!has_capability('mod/bigbluebuttonbn:join', $context)) {
return null;
}
return implode('/', $args);
}
/**
* Helper generates a salt used for the preuploaded presentation callback url.
*
* @param int $id
* @return int
*/
protected static function get_nonce(int $id): int {
$cache = static::get_nonce_cache();
$pnoncekey = sha1($id);
$existingnoncedata = $cache->get($pnoncekey);
if ($existingnoncedata) {
if ($existingnoncedata->counter > 0) {
$existingnoncedata->counter--;
$cache->set($pnoncekey, $existingnoncedata);
return $existingnoncedata->nonce;
}
}
// The item id was adapted for granting public access to the presentation once in order to allow BigBlueButton to gather
// the file once.
return static::generate_nonce($id);
}
/**
* Generate a nonce and store it in the cache
*
* @param int $id
* @return int
*/
protected static function generate_nonce($id): int {
$cache = static::get_nonce_cache();
$pnoncekey = sha1($id);
// The item id was adapted for granting public access to the presentation once in order to allow BigBlueButton to gather
// the file once.
$pnoncevalue = ((int) microtime()) + mt_rand();
$cache->set($pnoncekey, (object) ['nonce' => $pnoncevalue, 'counter' => 2]);
return $pnoncevalue;
}
/**
* Get cache for nonce
*
* @return \cache_application|\cache_session|cache_store
*/
private static function get_nonce_cache() {
return cache::make_from_params(
cache_store::MODE_APPLICATION,
'mod_bigbluebuttonbn',
'presentation_cache'
);
}
/**
* Returns an array of file areas.
*
* @return array a list of available file areas
*
*/
protected static function get_file_areas(): array {
$areas = [];
$areas['presentation'] = get_string('mod_form_block_presentation', 'bigbluebuttonbn');
$areas['presentationdefault'] = get_string('mod_form_block_presentation_default', 'bigbluebuttonbn');
return $areas;
}
}
@@ -0,0 +1,209 @@
<?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/>.
namespace mod_bigbluebuttonbn\local\helpers;
use calendar_event;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\logger;
use mod_bigbluebuttonbn\plugin;
use stdClass;
/**
* Utility class for all instance (module) routines helper.
*
* @package mod_bigbluebuttonbn
* @copyright 2021 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent [at] call-learning [dt] fr)
*/
class mod_helper {
/**
* Runs any processes that must run before a bigbluebuttonbn insert/update.
*
* @param stdClass $bigbluebuttonbn BigBlueButtonBN form data
**/
public static function process_pre_save(stdClass $bigbluebuttonbn) {
self::process_pre_save_instance($bigbluebuttonbn);
self::process_pre_save_checkboxes($bigbluebuttonbn);
self::process_pre_save_common($bigbluebuttonbn);
$bigbluebuttonbn->participants = htmlspecialchars_decode($bigbluebuttonbn->participants, ENT_COMPAT);
}
/**
* Runs process for defining the instance (insert/update).
*
* @param stdClass $bigbluebuttonbn BigBlueButtonBN form data
**/
protected static function process_pre_save_instance(stdClass $bigbluebuttonbn): void {
$bigbluebuttonbn->timemodified = time();
if ((integer) $bigbluebuttonbn->instance == 0) {
$bigbluebuttonbn->meetingid = 0;
$bigbluebuttonbn->timecreated = time();
$bigbluebuttonbn->timemodified = 0;
// As it is a new activity, assign passwords.
$bigbluebuttonbn->moderatorpass = plugin::random_password(12);
$bigbluebuttonbn->viewerpass = plugin::random_password(12, $bigbluebuttonbn->moderatorpass);
}
}
/**
* Runs process for assigning default value to checkboxes.
*
* @param stdClass $bigbluebuttonbn BigBlueButtonBN form data
**/
protected static function process_pre_save_checkboxes($bigbluebuttonbn) {
if (!isset($bigbluebuttonbn->wait)) {
$bigbluebuttonbn->wait = 0;
}
if (!isset($bigbluebuttonbn->record)) {
$bigbluebuttonbn->record = 0;
}
if (!isset($bigbluebuttonbn->recordallfromstart)) {
$bigbluebuttonbn->recordallfromstart = 0;
}
if (!isset($bigbluebuttonbn->recordhidebutton)) {
$bigbluebuttonbn->recordhidebutton = 0;
}
if (!isset($bigbluebuttonbn->recordings_html)) {
$bigbluebuttonbn->recordings_html = 0;
}
if (!isset($bigbluebuttonbn->recordings_deleted)) {
$bigbluebuttonbn->recordings_deleted = 0;
}
if (!isset($bigbluebuttonbn->recordings_imported)) {
$bigbluebuttonbn->recordings_imported = 0;
}
if (!isset($bigbluebuttonbn->recordings_preview)) {
$bigbluebuttonbn->recordings_preview = 0;
}
if (!isset($bigbluebuttonbn->muteonstart)) {
$bigbluebuttonbn->muteonstart = 0;
}
if (!isset($bigbluebuttonbn->disablecam)) {
$bigbluebuttonbn->disablecam = 0;
}
if (!isset($bigbluebuttonbn->disablemic)) {
$bigbluebuttonbn->disablemic = 0;
}
if (!isset($bigbluebuttonbn->disableprivatechat)) {
$bigbluebuttonbn->disableprivatechat = 0;
}
if (!isset($bigbluebuttonbn->disablepublicchat)) {
$bigbluebuttonbn->disablepublicchat = 0;
}
if (!isset($bigbluebuttonbn->disablenote)) {
$bigbluebuttonbn->disablenote = 0;
}
if (!isset($bigbluebuttonbn->hideuserlist)) {
$bigbluebuttonbn->hideuserlist = 0;
}
}
/**
* Runs process for wipping common settings when 'recordings only'.
*
* @param stdClass $bigbluebuttonbn BigBlueButtonBN form data
**/
protected static function process_pre_save_common(stdClass $bigbluebuttonbn): void {
// Make sure common settings are removed when 'recordings only'.
if ($bigbluebuttonbn->type == instance::TYPE_RECORDING_ONLY) {
$bigbluebuttonbn->groupmode = 0;
$bigbluebuttonbn->groupingid = 0;
}
}
/**
* Runs any processes that must be run after a bigbluebuttonbn insert/update.
*
* @param stdClass $bigbluebuttonbn BigBlueButtonBN form data
**/
public static function process_post_save(stdClass $bigbluebuttonbn): void {
self::process_post_save_event($bigbluebuttonbn);
self::process_post_save_completion($bigbluebuttonbn);
}
/**
* Generates an event after a bigbluebuttonbn insert/update.
*
* @param stdClass $bigbluebuttonbn BigBlueButtonBN form data
**/
protected static function process_post_save_event(stdClass $bigbluebuttonbn): void {
global $CFG, $DB;
require_once($CFG->dirroot . '/calendar/lib.php');
$eventid = $DB->get_field('event', 'id', [
'modulename' => 'bigbluebuttonbn',
'instance' => $bigbluebuttonbn->id,
'eventtype' => logger::EVENT_MEETING_START
]);
// Delete the event from calendar when/if openingtime is NOT set.
if (!isset($bigbluebuttonbn->openingtime) || !$bigbluebuttonbn->openingtime) {
if ($eventid) {
$calendarevent = calendar_event::load($eventid);
$calendarevent->delete();
}
return;
}
// Add event to the calendar as openingtime is set.
$event = (object) [
'eventtype' => logger::EVENT_MEETING_START,
'type' => CALENDAR_EVENT_TYPE_ACTION,
'name' => get_string('calendarstarts', 'bigbluebuttonbn', $bigbluebuttonbn->name),
'description' => format_module_intro('bigbluebuttonbn', $bigbluebuttonbn, $bigbluebuttonbn->coursemodule, false),
'format' => FORMAT_HTML,
'courseid' => $bigbluebuttonbn->course,
'groupid' => 0,
'userid' => 0,
'modulename' => 'bigbluebuttonbn',
'instance' => $bigbluebuttonbn->id,
'timestart' => $bigbluebuttonbn->openingtime,
'timeduration' => 0,
'timesort' => $bigbluebuttonbn->openingtime,
'visible' => instance_is_visible('bigbluebuttonbn', $bigbluebuttonbn),
'priority' => null,
];
// Update the event in calendar when/if eventid was found.
if ($eventid) {
$event->id = $eventid;
$calendarevent = calendar_event::load($eventid);
$calendarevent->update($event);
return;
}
calendar_event::create($event);
}
/**
* Generates an event after a bigbluebuttonbn activity is completed.
*
* @param stdClass $bigbluebuttonbn BigBlueButtonBN form data
**/
protected static function process_post_save_completion(stdClass $bigbluebuttonbn): void {
if (empty($bigbluebuttonbn->completionexpected)) {
return;
}
\core_completion\api::update_completion_date_event(
$bigbluebuttonbn->coursemodule,
'bigbluebuttonbn',
$bigbluebuttonbn->id,
$bigbluebuttonbn->completionexpected
);
}
}
@@ -0,0 +1,131 @@
<?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/>.
/**
* The mod_bigbluebuttonbn resetting instance helper
*
* @package mod_bigbluebuttonbn
* @copyright 2021 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent [at] call-learning [dt] fr)
*/
namespace mod_bigbluebuttonbn\local\helpers;
use context_module;
use core_tag_tag;
use mod_bigbluebuttonbn\local\config;
use mod_bigbluebuttonbn\recording;
/**
* Utility class for resetting instance routines helper
*
* @package mod_bigbluebuttonbn
* @copyright 2021 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class reset {
/**
* Used by the reset_course_userdata for deleting recordings
*
* This will delete recordings in the database and not in the remote BBB server.
*
* @param int $courseid
*/
public static function reset_recordings(int $courseid): void {
// Criteria for search : courseid or bigbluebuttonbn=null or subset=false or includedeleted=true.
$recordings = recording::get_recordings_for_course(
$courseid,
[], // Exclude itself.
false,
true
);
if ($recordings) {
// Remove all the recordings.
foreach ($recordings as $recording) {
$recording->delete();
}
}
}
/**
* Used by the reset_course_userdata for deleting tags linked to bigbluebuttonbn instances in the course.
*
* @param int $courseid
*/
public static function reset_tags(int $courseid): void {
global $DB;
// Remove all the tags linked to the room/activities in this course.
if ($bigbluebuttonbns = $DB->get_records('bigbluebuttonbn', ['course' => $courseid])) {
foreach ($bigbluebuttonbns as $bigbluebuttonbn) {
if (!$cm = get_coursemodule_from_instance('bigbluebuttonbn', $bigbluebuttonbn->id, $courseid)) {
continue;
}
$context = context_module::instance($cm->id);
core_tag_tag::delete_instances('mod_bigbluebuttonbn', null, $context->id);
}
}
}
/**
* Used by the reset_course_userdata for deleting events linked to bigbluebuttonbn instances in the course.
*
* @param string $courseid
* @return bool status
*/
public static function reset_events($courseid) {
global $DB;
// Remove all the events.
return $DB->delete_records('event', ['modulename' => 'bigbluebuttonbn', 'courseid' => $courseid]);
}
/**
* Returns status used on every defined reset action.
*
* @param string $item
* @return array status array
*/
public static function reset_getstatus(string $item): array {
return ['component' => get_string('modulenameplural', 'bigbluebuttonbn'),
'item' => get_string("removed{$item}", 'bigbluebuttonbn'),
'error' => false];
}
/**
* Define items to be reset by course/reset.php
*
* @return array
*/
public static function reset_course_items(): array {
$items = ["events" => 0, "tags" => 0, "logs" => 0];
// Include recordings only if enabled.
if ((boolean) config::recordings_enabled()) {
$items["recordings"] = 0;
}
return $items;
}
/**
* Reset logs for each BBB instance of this course
*
* @param int $courseid
* @return bool status
*/
public static function reset_logs(int $courseid) {
global $DB;
return $DB->delete_records('bigbluebuttonbn_logs', ['courseid' => $courseid]);
}
}
@@ -0,0 +1,452 @@
<?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/>.
/**
* The mod_bigbluebuttonbn roles helper
*
* @package mod_bigbluebuttonbn
* @copyright 2021 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent [at] call-learning [dt] fr)
*/
namespace mod_bigbluebuttonbn\local\helpers;
use cache;
use cache_store;
use context;
use context_course;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\local\proxy\bigbluebutton_proxy;
use stdClass;
/**
* Utility class for all roles routines helper
*
* @package mod_bigbluebuttonbn
* @copyright 2021 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class roles {
/** @var int The bigbluebutton viewer role */
public const ROLE_VIEWER = 'viewer';
/** @var string The bigbluebutton moderator role */
public const ROLE_MODERATOR = 'moderator';
/**
* Returns user roles in a context.
*
* @param context $context
* @param int $userid
*
* @return array $userroles
*/
public static function get_user_roles(context $context, int $userid) {
global $DB;
$userroles = get_user_roles($context, $userid);
if ($userroles) {
$where = '';
foreach ($userroles as $userrole) {
$where .= (empty($where) ? ' WHERE' : ' OR') . ' id=' . $userrole->roleid;
}
$userroles = $DB->get_records_sql('SELECT * FROM {role}' . $where);
}
return $userroles;
}
/**
* Returns guest role wrapped in an array.
*
* @return array
*/
protected static function get_guest_role() {
$guestrole = get_guest_role();
return [$guestrole->id => $guestrole];
}
/**
* Returns an array containing all the users in a context wrapped for html select element.
*
* @param context_course $context
* @param null $bbactivity
* @return array $users
*/
public static function get_users_array(context_course $context, $bbactivity = null) {
// CONTRIB-7972, check the group of current user and course group mode.
$groups = null;
$users = (array) get_enrolled_users($context, '', 0, 'u.*', null, 0, 0, true);
$course = get_course($context->instanceid);
$groupmode = groups_get_course_groupmode($course);
if ($bbactivity) {
list($bbcourse, $cm) = get_course_and_cm_from_instance($bbactivity->id, 'bigbluebuttonbn');
$groupmode = groups_get_activity_groupmode($cm);
}
if ($groupmode == SEPARATEGROUPS && !has_capability('moodle/site:accessallgroups', $context)) {
global $USER;
$groups = groups_get_all_groups($course->id, $USER->id);
$users = [];
foreach ($groups as $g) {
$users += (array) get_enrolled_users($context, '', $g->id, 'u.*', null, 0, 0, true);
}
}
return array_map(
function($u) {
return ['id' => $u->id, 'name' => fullname($u)];
},
$users);
}
/**
* Can do some administration in this course, likely manage recordings
*
* @param int $courseid
* @param string $capability
*/
public static function has_capability_in_course(int $courseid, string $capability) {
global $DB;
if (empty($courseid) || !$DB->record_exists('course', ['id' => $courseid])) {
return has_capability('moodle/site:config', \context_system::instance());
}
$coursecontext = context_course::instance($courseid);
return has_capability($capability, $coursecontext);
}
/**
* Returns an array containing all the roles in a context.
*
* @param context|null $context $context
* @param bool|null $onlyviewableroles
*
* @return array $roles
*/
public static function get_roles(?context $context = null, ?bool $onlyviewableroles = true) {
global $CFG;
if ($onlyviewableroles == true && $CFG->branch >= 35) {
$roles = (array) get_viewable_roles($context);
foreach ($roles as $key => $value) {
$roles[$key] = $value;
}
} else {
$roles = (array) role_get_names($context);
foreach ($roles as $key => $value) {
$roles[$key] = $value->localname;
}
}
return $roles;
}
/**
* Returns an array containing all the roles in a context wrapped for html select element.
*
* @param context|null $context $context
* @param bool $onlyviewableroles
*
* @return array $users
*/
protected static function get_roles_select(context $context = null, bool $onlyviewableroles = true) {
global $CFG;
if ($onlyviewableroles == true && $CFG->branch >= 35) {
$roles = (array) get_viewable_roles($context);
foreach ($roles as $key => $value) {
$roles[$key] = ['id' => $key, 'name' => $value];
}
} else {
$roles = (array) role_get_names($context);
foreach ($roles as $key => $value) {
$roles[$key] = ['id' => $value->id, 'name' => $value->localname];
}
}
return $roles;
}
/**
* Returns role that corresponds to an id.
*
* @param string|integer $id
*
* @return stdClass|null $role
*/
protected static function get_role($id): ?stdClass {
$roles = (array) role_get_names();
if (is_numeric($id) && isset($roles[$id])) {
return (object) $roles[$id];
}
foreach ($roles as $role) {
if ($role->shortname == $id) {
return $role;
}
}
return null;
}
/**
* Returns an array to populate a list of participants used in mod_form.js.
*
* @param context $context
* @param null|stdClass $bbactivity
* @return array $data
*/
public static function get_participant_data(context $context, ?stdClass $bbactivity = null) {
$data = [
'all' => [
'name' => get_string('mod_form_field_participant_list_type_all', 'bigbluebuttonbn'),
'children' => []
],
];
$data['role'] = [
'name' => get_string('mod_form_field_participant_list_type_role', 'bigbluebuttonbn'),
'children' => self::get_roles_select($context, true)
];
$data['user'] = [
'name' => get_string('mod_form_field_participant_list_type_user', 'bigbluebuttonbn'),
'children' => self::get_users_array($context, $bbactivity),
];
return $data;
}
/**
* Returns an array to populate a list of participants used in mod_form.php.
*
* @param stdClass|null $bigbluebuttonbn
* @param context $context
*
* @return array
*/
public static function get_participant_list(?stdClass $bigbluebuttonbn, context $context): array {
global $USER;
if ($bigbluebuttonbn == null) {
return self::get_participant_rules_encoded(
self::get_participant_list_default($context, $USER->id)
);
}
if (empty($bigbluebuttonbn->participants)) {
$bigbluebuttonbn->participants = "[]";
}
$rules = json_decode($bigbluebuttonbn->participants, true);
if (empty($rules)) {
$rules = self::get_participant_list_default($context,
bigbluebutton_proxy::get_instance_ownerid($bigbluebuttonbn));
}
return self::get_participant_rules_encoded($rules);
}
/**
* Returns an array to populate a list of participants used in mod_form.php with default values.
*
* @param context $context
* @param int|null $ownerid
*
* @return array
*/
protected static function get_participant_list_default(context $context, ?int $ownerid = null) {
$participantlist = [];
$participantlist[] = [
'selectiontype' => 'all',
'selectionid' => 'all',
'role' => self::ROLE_VIEWER,
];
$defaultrules = explode(',', \mod_bigbluebuttonbn\local\config::get('participant_moderator_default'));
foreach ($defaultrules as $defaultrule) {
if ($defaultrule == '0') {
if (!empty($ownerid) && is_enrolled($context, $ownerid)) {
$participantlist[] = [
'selectiontype' => 'user',
'selectionid' => (string) $ownerid,
'role' => self::ROLE_MODERATOR];
}
continue;
}
$participantlist[] = [
'selectiontype' => 'role',
'selectionid' => $defaultrule,
'role' => self::ROLE_MODERATOR];
}
return $participantlist;
}
/**
* Returns an array to populate a list of participants used in mod_form.php with bigbluebuttonbn values.
*
* @param array $rules
*
* @return array
*/
protected static function get_participant_rules_encoded(array $rules): array {
foreach ($rules as $key => $rule) {
if ($rule['selectiontype'] !== 'role' || is_numeric($rule['selectionid'])) {
continue;
}
$role = self::get_role($rule['selectionid']);
if ($role == null) {
unset($rules[$key]);
continue;
}
$rule['selectionid'] = $role->id;
$rules[$key] = $rule;
}
return $rules;
}
/**
* Returns an array to populate a list of participant_selection used in mod_form.php.
*
* @return array
*/
public static function get_participant_selection_data(): array {
return [
'type_options' => [
'all' => get_string('mod_form_field_participant_list_type_all', 'bigbluebuttonbn'),
'role' => get_string('mod_form_field_participant_list_type_role', 'bigbluebuttonbn'),
'user' => get_string('mod_form_field_participant_list_type_user', 'bigbluebuttonbn'),
],
'type_selected' => 'all',
'options' => ['all' => '---------------'],
'selected' => 'all',
];
}
/**
* Evaluate if a user in a context is moderator based on roles and participation rules.
*
* @param context $context
* @param array $participantlist
* @param int $userid
*
* @return bool
*/
public static function is_moderator(context $context, array $participantlist, ?int $userid = null): bool {
global $USER;
// If an admin, then also a moderator.
if (has_capability('moodle/site:config', $context)) {
return true;
}
if (!is_array($participantlist)) {
return false;
}
if (empty($userid)) {
$userid = $USER->id;
}
$userroles = self::get_guest_role();
if (!isguestuser()) {
$userroles = self::get_user_roles($context, $userid);
}
return self::is_moderator_validator($participantlist, $userid, $userroles);
}
/**
* Iterates participant list rules to evaluate if a user is moderator.
*
* @param array $participantlist
* @param int $userid
* @param array $userroles
*
* @return bool
*/
protected static function is_moderator_validator(array $participantlist, int $userid, array $userroles): bool {
// Iterate participant rules.
foreach ($participantlist as $participant) {
if (self::is_moderator_validate_rule($participant, $userid, $userroles)) {
return true;
}
}
return false;
}
/**
* Evaluate if a user is moderator based on roles and a particular participation rule.
*
* @param array $participant
* @param int $userid
* @param array $userroles
*
* @return bool
*/
protected static function is_moderator_validate_rule(array $participant, int $userid, array $userroles): bool {
if ($participant['role'] == self::ROLE_VIEWER) {
return false;
}
// Validation for the 'all' rule.
if ($participant['selectiontype'] == 'all') {
return true;
}
// Validation for a 'user' rule.
if ($participant['selectiontype'] == 'user') {
if ($participant['selectionid'] == $userid) {
return true;
}
return false;
}
// Validation for a 'role' rule.
$role = self::get_role($participant['selectionid']);
if ($role != null && array_key_exists($role->id, $userroles)) {
return true;
}
return false;
}
/**
* Updates the meeting info cached object when a participant has joined.
*
* @param string $meetingid
* @param bool $ismoderator
*
* @return void
*/
public static function participant_joined(string $meetingid, bool $ismoderator): void {
$cache = cache::make_from_params(cache_store::MODE_APPLICATION, 'mod_bigbluebuttonbn', 'meetings_cache');
$result = $cache->get($meetingid);
$meetinginfo = json_decode($result['meeting_info']);
$meetinginfo->participantCount += 1;
if ($ismoderator) {
$meetinginfo->moderatorCount += 1;
}
$cache->set($meetingid, ['creation_time' => $result['creation_time'],
'meeting_info' => json_encode($meetinginfo)]);
}
/**
* Helper function returns a list of courses a user has access to, wrapped in an array that can be used
* by a html select.
*
* @param instance $instance
* @return array
*/
public static function import_get_courses_for_select(instance $instance): array {
if ($instance->is_admin()) {
$courses = get_courses('all', 'c.fullname ASC');
// It includes the name of the site as a course (category 0), so remove the first one.
unset($courses['1']);
} else {
$courses = enrol_get_users_courses($instance->get_user_id(), false, 'id,shortname,fullname');
}
$courses = array_filter($courses, function($course) {
$modules = get_fast_modinfo($course->id);
return !empty($modules->instances['bigbluebuttonbn']);
});
$coursesforselect = [];
foreach ($courses as $course) {
$coursesforselect[$course->id] = $course->fullname . " (" . $course->shortname . ")";
}
return $coursesforselect;
}
}
@@ -0,0 +1,73 @@
<?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/>.
namespace mod_bigbluebuttonbn\local\helpers;
use cm_info;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\logger;
use stdClass;
/**
* Utility class for all user information
*
* Used mainly in user_outline and user_complete
*
* @package mod_bigbluebuttonbn
* @copyright 2022 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent [at] call-learning [dt] fr)
*/
class user_info {
/**
* Event to watch for.
*/
const EVENT_TO_WATCH = [
'join' => logger::EVENT_JOIN,
'play_recording' => logger::EVENT_PLAYED
];
/**
* Get user outline and complete info
*
* @param stdClass $course
* @param stdClass $user
* @param cm_info $mod
* @return array[] an array of infos and timestamps (latest timestamp)
*/
public static function get_user_info_outline(stdClass $course, stdClass $user, cm_info $mod): array {
$completion = new \completion_info($course);
$cdata = $completion->get_data($mod, false, $user->id);
$logtimestamps = [];
$infos = [];
if (!empty($cdata->viewed) && $cdata->viewed) {
$infos[] = get_string('report_room_view', 'mod_bigbluebuttonbn');
$logtimestamps[] = $cdata->timemodified;
}
$instance = instance::get_from_cmid($mod->id);
foreach (self::EVENT_TO_WATCH as $eventtype => $logtype) {
$logs = logger::get_user_completion_logs($instance, $user->id, [$logtype]);
if ($logs) {
$infos[] = get_string("report_{$eventtype}_info", 'mod_bigbluebuttonbn', count($logs));
$latesttime = array_reduce($logs,
function($acc, $log) {
return ($acc > $log->timecreated) ? $acc : $log->timecreated;
}, 0);
$logtimestamps[] = $latesttime;
}
}
return [$infos, $logtimestamps];
}
}
@@ -0,0 +1,81 @@
<?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/>.
namespace mod_bigbluebuttonbn\local\plugins;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->libdir . '/adminlib.php');
use admin_externalpage;
use core_component;
use core_text;
use mod_bigbluebuttonbn\extension;
use moodle_url;
/**
* Admin external page that displays a list of the installed extension plugins.
*
* @package mod_bigbluebuttonbn
* @copyright 2023 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent@call-learning.fr)
*/
class admin_page_manage_extensions extends admin_externalpage {
/**
* Global URL for page.
*/
const ADMIN_PAGE_URL = '/mod/bigbluebuttonbn/adminmanageplugins.php';
/**
* The constructor - calls parent constructor
*
*/
public function __construct() {
$url = new moodle_url(self::ADMIN_PAGE_URL);
$managepagename = 'manage' . extension::BBB_EXTENSION_PLUGIN_NAME . 'plugins';
parent::__construct(
$managepagename,
get_string($managepagename, 'mod_bigbluebuttonbn'),
$url
);
}
/**
* Search plugins for the specified string
*
* @param string $query The string to search for
* @return array
*/
public function search($query): array {
if ($result = parent::search($query)) {
return $result;
}
foreach (core_component::get_plugin_list(extension::BBB_EXTENSION_PLUGIN_NAME ) as $name => $notused) {
$pluginname = core_text::strtolower(
get_string('pluginname', extension::BBB_EXTENSION_PLUGIN_NAME . '_' . $name)
);
if (str_contains($pluginname, $query) !== false) {
$result = (object)[
'page' => $this,
'settings' => [],
];
return [$this->name => $result];
}
}
return [];
}
}
@@ -0,0 +1,329 @@
<?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/>.
namespace mod_bigbluebuttonbn\local\plugins;
use cache_helper;
use context_system;
use core_component;
use core_plugin_manager;
use flexible_table;
use html_writer;
use mod_bigbluebuttonbn\extension;
use moodle_url;
use pix_icon;
/**
* Class that handles the display and configuration of the list of extension plugins.
*
* This is directly taken from the mod_assign code. We might need to have a global API there for this.
*
* @package mod_bigbluebuttonbn
* @copyright 2023 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent@call-learning.fr)
*/
class admin_plugin_manager {
/** @var object the url of the manage submission plugin page */
private $pageurl;
/**
* Constructor for this assignment plugin manager
*
*/
public function __construct() {
$this->pageurl = new moodle_url(admin_page_manage_extensions::ADMIN_PAGE_URL);
}
/**
* This is the entry point for this controller class.
*
* @param string|null $action - The action to perform
* @param string|null $plugin - Optional name of a plugin type to perform the action on
* @return void
*/
public function execute(?string $action = null, ?string $plugin = null): void {
if (empty($action) || empty($plugin)) {
$action = 'view';
}
$this->check_permissions();
$actionname = "plugins_$action";
if (method_exists($this, $actionname)) {
$nextaction = $this->$actionname($plugin);
if ($nextaction) {
$this->execute($nextaction, $plugin);
}
}
}
/**
* Check this user has permission to edit the list of installed plugins
*
* @return void
*/
private function check_permissions(): void {
require_login();
$systemcontext = context_system::instance();
require_capability('moodle/site:config', $systemcontext);
}
/**
* Write the HTML for the submission plugins table.
*
* @return void
*/
private function plugins_view(): void {
global $OUTPUT, $CFG;
require_once($CFG->libdir . '/tablelib.php');
$this->print_header();
$table = new flexible_table(extension::BBB_EXTENSION_PLUGIN_NAME . 'pluginsadminttable');
$table->define_baseurl($this->pageurl);
$table->define_columns([
'pluginname',
'version',
'hideshow',
'order',
'settings',
'uninstall'
]);
$table->define_headers([
get_string('subplugintype_bbbext', 'mod_bigbluebuttonbn'),
get_string('version'), get_string('hide') . '/' . get_string('show'),
get_string('order'),
get_string('settings'),
get_string('uninstallplugin', 'core_admin')
]);
$table->set_attribute('id', extension::BBB_EXTENSION_PLUGIN_NAME . 'plugins');
$table->set_attribute('class', 'admintable generaltable');
$table->setup();
$plugins = $this->get_sorted_plugins_list();
$instances = core_plugin_manager::instance()->get_plugins_of_type(extension::BBB_EXTENSION_PLUGIN_NAME);
foreach ($plugins as $idx => $plugin) {
$componentname = extension::BBB_EXTENSION_PLUGIN_NAME . '_' . $plugin;
$typebasedir = "";
if (in_array($plugin, array_keys($instances))) {
$typebasedir = ($instances[$plugin])->typerootdir;
}
$row = [];
$class = '';
$pluginversion = get_config($componentname, 'version');
$row[] = get_string('pluginname', $componentname);
$row[] = $pluginversion;
$visible = !get_config($componentname, 'disabled');
if ($visible) {
$row[] = $this->format_icon_link('hide', $plugin, 't/hide', get_string('disable'));
} else {
$row[] = $this->format_icon_link('show', $plugin, 't/show', get_string('enable'));
$class = 'dimmed_text';
}
$movelinks = '';
if (!$idx == 0) {
$movelinks .= $this->format_icon_link('moveup', $plugin, 't/up', get_string('up')) . ' ';
} else {
$movelinks .= $OUTPUT->spacer(['width' => 16]);
}
if ($idx != count($plugins) - 1) {
$movelinks .= $this->format_icon_link('movedown', $plugin, 't/down', get_string('down')) . ' ';
}
$row[] = $movelinks;
$exists = file_exists($typebasedir . '/' . $plugin . '/settings.php');
// We do not display settings for plugin who have not yet been installed (so have no version yet).
if (!empty($pluginversion) && $exists) {
$row[] = html_writer::link(
new moodle_url('/admin/settings.php', ['section' => $componentname]),
get_string('settings')
);
} else {
$row[] = '&nbsp;';
}
$url = core_plugin_manager::instance()->get_uninstall_url(
$componentname,
'manage'
);
if ($url) {
$row[] = html_writer::link($url, get_string('uninstallplugin', 'core_admin'));
} else {
$row[] = '&nbsp;';
}
$table->add_data($row, $class);
}
$table->finish_output();
$this->print_footer();
}
/**
* Write the page header
*
* @return void
*/
private function print_header(): void {
global $OUTPUT;
$pageidentifier = 'manage' . extension::BBB_EXTENSION_PLUGIN_NAME . 'plugins';
admin_externalpage_setup($pageidentifier);
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string($pageidentifier, 'mod_bigbluebuttonbn'));
}
/**
* Return a list of plugins sorted by the order defined in the admin interface
*
* @return array The list of plugins
*/
public function get_sorted_plugins_list(): array {
$names = core_component::get_plugin_list(extension::BBB_EXTENSION_PLUGIN_NAME);
$result = [];
foreach ($names as $name => $path) {
$idx = get_config(extension::BBB_EXTENSION_PLUGIN_NAME . '_' . $name, 'sortorder');
if (!$idx) {
$idx = 0;
}
while (array_key_exists($idx, $result)) {
$idx += 1;
}
$result[$idx] = $name;
}
ksort($result);
return $result;
}
/**
* Util function for writing an action icon link
*
* @param string $action URL parameter to include in the link
* @param string $plugin URL parameter to include in the link
* @param string $icon The key to the icon to use (e.g. 't/up')
* @param string $alt The string description of the link used as the title and alt text
* @return string The icon/link
*/
private function format_icon_link(string $action, string $plugin, string $icon, string $alt): string {
global $OUTPUT;
return $OUTPUT->action_icon(
new moodle_url(
$this->pageurl,
['action' => $action, 'plugin' => $plugin, 'sesskey' => sesskey()]
),
new pix_icon($icon, $alt, 'moodle', ['title' => $alt]),
null,
['title' => $alt]
);
}
/**
* Write the page footer
*
* @return void
*/
private function print_footer(): void {
global $OUTPUT;
echo $OUTPUT->footer();
}
/**
* Hide this plugin.
*
* @param string $plugin - The plugin to hide
* @return string The next page to display
*/
private function plugins_hide(string $plugin): string {
$class = \core_plugin_manager::resolve_plugininfo_class(extension::BBB_EXTENSION_PLUGIN_NAME);
$class::enable_plugin($plugin, false);
cache_helper::purge_by_event('mod_bigbluebuttonbn/pluginenabledisabled');
// Also clear the cache for all BigBlueButtonModules.
rebuild_course_cache(0, true);
return 'view';
}
/**
* Show this plugin.
*
* @param string $plugin - The plugin to show
* @return string The next page to display
*/
private function plugins_show(string $plugin): string {
$class = \core_plugin_manager::resolve_plugininfo_class(extension::BBB_EXTENSION_PLUGIN_NAME);
$class::enable_plugin($plugin, true);
cache_helper::purge_by_event('mod_bigbluebuttonbn/pluginenabledisabled');
return 'view';
}
/**
* Move this plugin up
*
* We need this function so we can call directly (without the dir parameter)
* @param string $plugintomove - The plugin to move
* @return string The next page to display
*/
private function plugins_moveup(string $plugintomove): string {
return $this->move_plugin($plugintomove, 'up');
}
/**
* Move this plugin down
*
* We need this function so we can call directly (without the dir parameter)
* @param string $plugintomove - The plugin to move
* @return string The next page to display
*/
private function plugins_movedown(string $plugintomove): string {
return $this->move_plugin($plugintomove, 'down');
}
/**
* Change the order of this plugin.
*
* @param string $plugintomove - The plugin to move
* @param string $dir - up or down
* @return string The next page to display
*/
private function move_plugin(string $plugintomove, string $dir): string {
$plugins = $this->get_sorted_plugins_list();
$plugins = array_values($plugins);
$currentindex = array_search($plugintomove, $plugins);
if ($currentindex === false) {
return 'view';
}
// Make the switch.
if ($dir === 'up') {
if ($currentindex > 0) {
$tempplugin = $plugins[$currentindex - 1];
$plugins[$currentindex - 1] = $plugins[$currentindex];
$plugins[$currentindex] = $tempplugin;
}
} else if ($dir === 'down') {
if ($currentindex < (count($plugins) - 1)) {
$tempplugin = $plugins[$currentindex + 1];
$plugins[$currentindex + 1] = $plugins[$currentindex];
$plugins[$currentindex] = $tempplugin;
}
}
// Save the new normal order.
foreach ($plugins as $key => $plugin) {
set_config('sortorder', $key, extension::BBB_EXTENSION_PLUGIN_NAME . '_' . $plugin);
}
return 'view';
}
}
@@ -0,0 +1,567 @@
<?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/>.
namespace mod_bigbluebuttonbn\local\proxy;
use cache;
use completion_info;
use Exception;
use mod_bigbluebuttonbn\completion\custom_completion;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\local\config;
use mod_bigbluebuttonbn\local\exceptions\bigbluebutton_exception;
use mod_bigbluebuttonbn\local\exceptions\server_not_available_exception;
use moodle_url;
use stdClass;
use user_picture;
/**
* The bigbluebutton proxy class.
*
* This class acts as a proxy between Moodle and the BigBlueButton API server,
* and handles all requests relating to the server and meetings.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Jesus Federico (jesus [at] blindsidenetworks [dt] com)
*/
class bigbluebutton_proxy extends proxy_base {
/**
* Minimum poll interval for remote bigbluebutton server in seconds.
*/
const MIN_POLL_INTERVAL = 2;
/**
* Default poll interval for remote bigbluebutton server in seconds.
*/
const DEFAULT_POLL_INTERVAL = 5;
/**
* Builds and returns a url for joining a BigBlueButton meeting.
*
* @param instance $instance
* @param string|null $createtime
*
* @return string
*/
public static function get_join_url(
instance $instance,
?string $createtime
): string {
return self::internal_get_join_url($instance, $createtime);
}
/**
* Builds and returns a url for joining a BigBlueButton meeting.
*
* @param instance $instance
* @param string|null $createtime
* @param string $username
* @return string
*/
public static function get_guest_join_url(
instance $instance,
?string $createtime,
string $username
): string {
return self::internal_get_join_url($instance, $createtime, $username, true);
}
/**
* Internal helper method to builds and returns a url for joining a BigBlueButton meeting.
*
* @param instance $instance
* @param string|null $jointime = null
* @param string|null $userfullname
* @param bool $isguestjoin
* @return string
*/
private static function internal_get_join_url(
instance $instance,
?string $jointime,
string $userfullname = null,
bool $isguestjoin = false
): string {
$data = [
'meetingID' => $instance->get_meeting_id(),
'fullName' => $userfullname ?? $instance->get_user_fullname(),
'password' => $instance->get_current_user_password(),
'logoutURL' => $isguestjoin ? $instance->get_guest_access_url()->out(false) : $instance->get_logout_url()->out(false),
'role' => $instance->get_current_user_role()
];
if (!$isguestjoin) {
$data['userID'] = $instance->get_user_id();
$data['guest'] = "false";
} else {
$data['guest'] = "true";
}
if (!is_null($jointime)) {
$data['createTime'] = $jointime;
}
$currentlang = current_language();
if (!empty(trim($currentlang))) {
$data['userdata-bbb_override_default_locale'] = $currentlang;
}
if ($instance->is_profile_picture_enabled()) {
$user = $instance->get_user();
if (!empty($user->picture)) {
$data['avatarURL'] = self::get_avatar_url($user)->out(false);
}
}
return self::action_url('join', $data, [], $instance->get_instance_id());
}
/**
* Get user avatar URL
*
* @param stdClass $user
* @return moodle_url
*/
private static function get_avatar_url(stdClass $user): moodle_url {
global $PAGE;
$userpicture = new user_picture($user);
$userpicture->includetoken = true;
$userpicture->size = 3; // Size f3.
return $userpicture->get_url($PAGE);
}
/**
* Perform api request on BBB.
*
* @return null|string
*/
public static function get_server_version(): ?string {
$cache = cache::make('mod_bigbluebuttonbn', 'serverinfo');
$serverversion = $cache->get('serverversion');
if (!$serverversion) {
$xml = self::fetch_endpoint_xml('');
if (!$xml || $xml->returncode != 'SUCCESS') {
return null;
}
if (!isset($xml->version)) {
return null;
}
$serverversion = (string) $xml->version;
$cache->set('serverversion', $serverversion);
}
return (double) $serverversion;
}
/**
* Helper for getting the owner userid of a bigbluebuttonbn instance.
*
* @param stdClass $bigbluebuttonbn BigBlueButtonBN instance
* @return int ownerid (a valid user id or null if not registered/found)
*/
public static function get_instance_ownerid(stdClass $bigbluebuttonbn): int {
global $DB;
$filters = [
'bigbluebuttonbnid' => $bigbluebuttonbn->id,
'log' => 'Add',
];
return (int) $DB->get_field('bigbluebuttonbn_logs', 'userid', $filters);
}
/**
* Helper evaluates if a voicebridge number is unique.
*
* @param int $instance
* @param int $voicebridge
* @return bool
*/
public static function is_voicebridge_number_unique(int $instance, int $voicebridge): bool {
global $DB;
if ($voicebridge == 0) {
return true;
}
$select = 'voicebridge = ' . $voicebridge;
if ($instance != 0) {
$select .= ' AND id <>' . $instance;
}
if (!$DB->get_records_select('bigbluebuttonbn', $select)) {
return true;
}
return false;
}
/**
* Helper function validates a remote resource.
*
* @param string $url
* @return bool
*/
public static function is_remote_resource_valid(string $url): bool {
$urlhost = parse_url($url, PHP_URL_HOST);
$serverurlhost = parse_url(\mod_bigbluebuttonbn\local\config::get('server_url'), PHP_URL_HOST);
if ($urlhost == $serverurlhost) {
// Skip validation when the recording URL host is the same as the configured BBB server.
return true;
}
$cache = cache::make('mod_bigbluebuttonbn', 'validatedurls');
if ($cachevalue = $cache->get($urlhost)) {
// Skip validation when the recording URL was already validated.
return $cachevalue == 1;
}
$curl = new curl();
$curl->head($url);
$isvalid = false;
if ($info = $curl->get_info()) {
if ($info['http_code'] == 200) {
$isvalid = true;
} else {
debugging(
"Resources hosted by {$urlhost} are unreachable. Server responded with {$info['http_code']}",
DEBUG_DEVELOPER
);
$isvalid = false;
}
// Note: When a cache key is not found, it returns false.
// We need to distinguish between a result not found, and an invalid result.
$cache->set($urlhost, $isvalid ? 1 : 0);
}
return $isvalid;
}
/**
* Helper function enqueues one user for being validated as for completion.
*
* @param stdClass $bigbluebuttonbn
* @param int $userid
* @return void
*/
public static function enqueue_completion_event(stdClass $bigbluebuttonbn, int $userid): void {
try {
// Create the instance of completion_update_state task.
$task = new \mod_bigbluebuttonbn\task\completion_update_state();
// Add custom data.
$data = [
'bigbluebuttonbn' => $bigbluebuttonbn,
'userid' => $userid,
];
$task->set_custom_data($data);
// CONTRIB-7457: Task should be executed by a user, maybe Teacher as Student won't have rights for overriding.
// $ task -> set_userid ( $ user -> id );.
// Enqueue it.
\core\task\manager::queue_adhoc_task($task);
} catch (Exception $e) {
mtrace("Error while enqueuing completion_update_state task. " . (string) $e);
}
}
/**
* Helper function enqueues completion trigger.
*
* @param stdClass $bigbluebuttonbn
* @param int $userid
* @return void
*/
public static function update_completion_state(stdClass $bigbluebuttonbn, int $userid) {
global $CFG;
require_once($CFG->libdir . '/completionlib.php');
list($course, $cm) = get_course_and_cm_from_instance($bigbluebuttonbn, 'bigbluebuttonbn');
$completion = new completion_info($course);
if (!$completion->is_enabled($cm)) {
mtrace("Completion not enabled");
return;
}
$bbbcompletion = new custom_completion($cm, $userid);
if ($bbbcompletion->get_overall_completion_state()) {
mtrace("Completion for userid $userid and bigbluebuttonid {$bigbluebuttonbn->id} updated.");
$completion->update_state($cm, COMPLETION_COMPLETE, $userid, true);
} else {
// Still update state to current value (prevent unwanted caching).
$completion->update_state($cm, COMPLETION_UNKNOWN, $userid);
mtrace("Activity not completed for userid $userid and bigbluebuttonid {$bigbluebuttonbn->id}.");
}
}
/**
* Helper function returns an array with the profiles (with features per profile) for the different types
* of bigbluebuttonbn instances.
*
* @return array
*/
public static function get_instance_type_profiles(): array {
$instanceprofiles = [
instance::TYPE_ALL => [
'id' => instance::TYPE_ALL,
'name' => get_string('instance_type_default', 'bigbluebuttonbn'),
'features' => ['all']
],
instance::TYPE_ROOM_ONLY => [
'id' => instance::TYPE_ROOM_ONLY,
'name' => get_string('instance_type_room_only', 'bigbluebuttonbn'),
'features' => ['showroom', 'welcomemessage', 'voicebridge', 'waitformoderator', 'userlimit',
'recording', 'sendnotifications', 'lock', 'preuploadpresentation', 'permissions', 'schedule', 'groups',
'modstandardelshdr', 'availabilityconditionsheader', 'tagshdr', 'competenciessection',
'completionattendance', 'completionengagement', 'availabilityconditionsheader']
],
instance::TYPE_RECORDING_ONLY => [
'id' => instance::TYPE_RECORDING_ONLY,
'name' => get_string('instance_type_recording_only', 'bigbluebuttonbn'),
'features' => ['showrecordings', 'importrecordings', 'availabilityconditionsheader']
],
];
return $instanceprofiles;
}
/**
* Helper function returns an array with the profiles (with features per profile) for the different types
* of bigbluebuttonbn instances that the user is allowed to create.
*
* @param bool $room
* @param bool $recording
*
* @return array
*/
public static function get_instance_type_profiles_create_allowed(bool $room, bool $recording): array {
$profiles = self::get_instance_type_profiles();
if (!$room) {
unset($profiles[instance::TYPE_ROOM_ONLY]);
unset($profiles[instance::TYPE_ALL]);
}
if (!$recording) {
unset($profiles[instance::TYPE_RECORDING_ONLY]);
unset($profiles[instance::TYPE_ALL]);
}
return $profiles;
}
/**
* Helper function returns an array with the profiles (with features per profile) for the different types
* of bigbluebuttonbn instances.
*
* @param array $profiles
*
* @return array
*/
public static function get_instance_profiles_array(array $profiles = []): array {
$profilesarray = [];
foreach ($profiles as $key => $profile) {
$profilesarray[$profile['id']] = $profile['name'];
}
return $profilesarray;
}
/**
* Return the status of an activity [open|not_started|ended].
*
* @param instance $instance
* @return string
*/
public static function view_get_activity_status(instance $instance): string {
$now = time();
if (!empty($instance->get_instance_var('openingtime')) && $now < $instance->get_instance_var('openingtime')) {
// The activity has not been opened.
return 'not_started';
}
if (!empty($instance->get_instance_var('closingtime')) && $now > $instance->get_instance_var('closingtime')) {
// The activity has been closed.
return 'ended';
}
// The activity is open.
return 'open';
}
/**
* Ensure that the remote server was contactable.
*
* @param instance $instance
*/
public static function require_working_server(instance $instance): void {
$version = null;
try {
$version = self::get_server_version();
} catch (server_not_available_exception $e) {
self::handle_server_not_available($instance);
}
if (empty($version)) {
self::handle_server_not_available($instance);
}
}
/**
* Handle the server not being available.
*
* @param instance $instance
*/
public static function handle_server_not_available(instance $instance): void {
\core\notification::add(
self::get_server_not_available_message($instance),
\core\notification::ERROR
);
redirect(self::get_server_not_available_url($instance));
}
/**
* Get message when server not available
*
* @param instance $instance
* @return string
*/
public static function get_server_not_available_message(instance $instance): string {
if ($instance->is_admin()) {
return get_string('view_error_unable_join', 'mod_bigbluebuttonbn');
} else if ($instance->is_moderator()) {
return get_string('view_error_unable_join_teacher', 'mod_bigbluebuttonbn');
} else {
return get_string('view_error_unable_join_student', 'mod_bigbluebuttonbn');
}
}
/**
* Get URL to the page displaying that the server is not available
*
* @param instance $instance
* @return string
*/
public static function get_server_not_available_url(instance $instance): string {
if ($instance->is_admin()) {
return new moodle_url('/admin/settings.php', ['section' => 'modsettingbigbluebuttonbn']);
} else if ($instance->is_moderator()) {
return new moodle_url('/course/view.php', ['id' => $instance->get_course_id()]);
} else {
return new moodle_url('/course/view.php', ['id' => $instance->get_course_id()]);
}
}
/**
* Create a Meeting
*
* @param array $data
* @param array $metadata
* @param string|null $presentationname
* @param string|null $presentationurl
* @param int|null $instanceid
* @return array
* @throws bigbluebutton_exception
*/
public static function create_meeting(
array $data,
array $metadata,
?string $presentationname = null,
?string $presentationurl = null,
?int $instanceid = null
): array {
$createmeetingurl = self::action_url('create', $data, $metadata, $instanceid);
$curl = new curl();
if (!is_null($presentationname) && !is_null($presentationurl)) {
$payload = "<?xml version='1.0' encoding='UTF-8'?><modules><module name='presentation'><document url='" .
$presentationurl . "' /></module></modules>";
$xml = $curl->post($createmeetingurl, $payload);
} else {
$xml = $curl->get($createmeetingurl);
}
self::assert_returned_xml($xml);
if (empty($xml->meetingID)) {
throw new bigbluebutton_exception('general_error_cannot_create_meeting');
}
if ($xml->hasBeenForciblyEnded === 'true') {
throw new bigbluebutton_exception('index_error_forciblyended');
}
return [
'meetingID' => (string) $xml->meetingID,
'internalMeetingID' => (string) $xml->internalMeetingID,
'attendeePW' => (string) $xml->attendeePW,
'moderatorPW' => (string) $xml->moderatorPW
];
}
/**
* Get meeting info for a given meeting id
*
* @param string $meetingid
* @param int|null $instanceid
* @return array
*/
public static function get_meeting_info(string $meetingid, ?int $instanceid = null): array {
$xmlinfo = self::fetch_endpoint_xml('getMeetingInfo', ['meetingID' => $meetingid], [], $instanceid);
self::assert_returned_xml($xmlinfo, ['meetingid' => $meetingid]);
return (array) $xmlinfo;
}
/**
* Perform end meeting on BBB.
*
* @param string $meetingid
* @param string $modpw
* @param int|null $instanceid
*/
public static function end_meeting(string $meetingid, string $modpw, ?int $instanceid = null): void {
$xml = self::fetch_endpoint_xml('end', ['meetingID' => $meetingid, 'password' => $modpw], [], $instanceid);
self::assert_returned_xml($xml, ['meetingid' => $meetingid]);
}
/**
* Helper evaluates if the bigbluebutton server used belongs to blindsidenetworks domain.
*
* @return bool
*/
public static function is_bn_server() {
if (config::get('bn_server')) {
return true;
}
$parsedurl = parse_url(config::get('server_url'));
if (!isset($parsedurl['host'])) {
return false;
}
$h = $parsedurl['host'];
$hends = explode('.', $h);
$hendslength = count($hends);
return ($hends[$hendslength - 1] == 'com' && $hends[$hendslength - 2] == 'blindsidenetworks');
}
/**
* Get the poll interval as it is set in the configuration
*
* If configuration value is under the threshold of {@see self::MIN_POLL_INTERVAL},
* then return the {@see self::MIN_POLL_INTERVAL} value.
*
* @return int the poll interval in seconds
*/
public static function get_poll_interval(): int {
$pollinterval = intval(config::get('poll_interval'));
if ($pollinterval < self::MIN_POLL_INTERVAL) {
$pollinterval = self::MIN_POLL_INTERVAL;
}
return $pollinterval;
}
}
@@ -0,0 +1,157 @@
<?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/>.
/**
* A curl wrapper for bbb.
*
* @package mod_bigbluebuttonbn
* @copyright 2021 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_bigbluebuttonbn\local\proxy;
use SimpleXMLElement;
defined('MOODLE_INTERNAL') || die;
global $CFG;
require_once("{$CFG->libdir}/filelib.php");
/**
* A curl wrapper for bbb.
*
* @package mod_bigbluebuttonbn
* @copyright 2021 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class curl extends \curl {
/** @var string */
protected $contenttype;
/**
* Constructor for the class.
*/
public function __construct() {
$settings = [];
if (debugging()) {
$settings = ['ignoresecurity' => true];
}
parent::__construct($settings);
$this->setopt(['SSL_VERIFYPEER' => true]);
$this->set_content_type('application/xml');
}
/**
* Fetch the content type.
*/
public function get_content_type(): string {
return $this->contenttype;
}
/**
* Set the desired current content type.
*
* @param string $type
* @return self
*/
public function set_content_type(string $type): self {
$this->contenttype = $type;
return $this;
}
/**
* HTTP POST method
*
* @param string $url
* @param array|string $params
* @param array $options
* @return null|SimpleXMLElement Null on error
*/
public function post($url, $params = '', $options = []) {
if (!is_string($params)) {
debugging('Only string parameters are supported', DEBUG_DEVELOPER);
$params = '';
}
$options = array_merge($options, [
'CURLOPT_HTTPHEADER' => [
'Content-Type: ' . $this->get_content_type(),
'Content-Length: ' . strlen($params),
'Content-Language: en-US',
]
]);
return $this->handle_response(parent::post($url, $params, $options));
}
/**
* Fetch the specified URL via a HEAD request.
*
* @param string $url
* @param array $options
*/
public function head($url, $options = []) {
$options['followlocation'] = true;
$options['timeout'] = 1;
parent::head($url, $options);
return $this->get_info();
}
/**
* Fetch the specified URL via a GET request.
*
* @param string $url
* @param string $params
* @param array $options
*/
public function get($url, $params = [], $options = []) {
return $this->handle_response(parent::get($url, $params, $options));
}
/**
* Handle the response.
*
* @param mixed $response
* @return null|SimpleXMLElement Null on error
*/
protected function handle_response($response): ?SimpleXMLElement {
if (!$response) {
debugging('No response returned for call', DEBUG_DEVELOPER);
return null;
}
$previous = libxml_use_internal_errors(true);
try {
$xml = simplexml_load_string($response, 'SimpleXMLElement', LIBXML_NOCDATA | LIBXML_NOBLANKS);
} catch (Exception $e) {
libxml_use_internal_errors($previous);
debugging('Caught exception: ' . $e->getMessage(), DEBUG_DEVELOPER);
return null;
}
if ($xml instanceof SimpleXMLElement) {
return $xml;
}
$debugabstract = html_to_text($response);
$debugabstract = substr($debugabstract, 0, 1024); // Limit to small amount of info so we do not overload logs.
debugging('Issue retrieving information from the server: ' . $debugabstract, DEBUG_DEVELOPER);
return null;
}
}
@@ -0,0 +1,206 @@
<?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/>.
namespace mod_bigbluebuttonbn\local\proxy;
use mod_bigbluebuttonbn\extension;
use mod_bigbluebuttonbn\local\config;
use mod_bigbluebuttonbn\local\exceptions\bigbluebutton_exception;
use mod_bigbluebuttonbn\local\exceptions\server_not_available_exception;
use mod_bigbluebuttonbn\plugin;
use moodle_url;
/**
* The abstract proxy base class.
*
* This class provides common and shared functions used when interacting with
* the BigBlueButton API server.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Jesus Federico (jesus [at] blindsidenetworks [dt] com)
*/
abstract class proxy_base {
/**
* Sometimes the server sends back some error and errorKeys that
* can be converted to Moodle error messages
*/
const BBB_TO_MOODLE_ERROR_CODE = [
'checksumError' => 'index_error_checksum',
'notFound' => 'general_error_not_found',
'maxConcurrent' => 'view_error_max_concurrent',
];
/**
* Returns the right URL for the action specified.
*
* @param string $action
* @param array $data
* @param array $metadata
* @param int|null $instanceid
* @return string
*/
protected static function action_url(
string $action = '',
array $data = [],
array $metadata = [],
?int $instanceid = null
): string {
$baseurl = self::sanitized_url() . $action . '?';
['data' => $additionaldata, 'metadata' => $additionalmetadata] =
extension::action_url_addons($action, $data, $metadata, $instanceid);
$data = array_merge($data, $additionaldata ?? []);
$metadata = array_merge($metadata, $additionalmetadata ?? []);
$metadata = array_combine(array_map(function($k) {
return 'meta_' . $k;
}, array_keys($metadata)), $metadata);
$params = http_build_query($data + $metadata, '', '&');
$checksum = self::get_checksum($action, $params);
return $baseurl . $params . '&checksum=' . $checksum;
}
/**
* Makes sure the url used doesn't is in the format required.
*
* @return string
*/
protected static function sanitized_url(): string {
$serverurl = trim(config::get('server_url'));
if (PHPUNIT_TEST) {
$serverurl = (new moodle_url(TEST_MOD_BIGBLUEBUTTONBN_MOCK_SERVER))->out(false);
}
if (substr($serverurl, -1) == '/') {
$serverurl = rtrim($serverurl, '/');
}
if (substr($serverurl, -4) == '/api') {
$serverurl = rtrim($serverurl, '/api');
}
return $serverurl . '/api/';
}
/**
* Makes sure the shared_secret used doesn't have trailing white characters.
*
* @return string
*/
protected static function sanitized_secret(): string {
return trim(config::get('shared_secret'));
}
/**
* Throw an exception if there is a problem in the returned XML value
*
* @param \SimpleXMLElement|bool $xml
* @param array|null $additionaldetails
* @throws bigbluebutton_exception
* @throws server_not_available_exception
*/
protected static function assert_returned_xml($xml, ?array $additionaldetails = null): void {
$messagekey = '';
if (!empty($xml)) {
$messagekey = (string) ($xml->messageKey ?? '');
}
if (empty($xml) || static::is_known_server_unavailable_errorcode($messagekey)) {
$errorcode = self::get_errorcode_from_xml_messagekey($messagekey);
throw new server_not_available_exception(
$errorcode,
plugin::COMPONENT,
(new moodle_url('/admin/settings.php?section=modsettingbigbluebuttonbn'))->out(),
);
}
// If it is a checksum error, this is equivalent to the server not being available.
// So we treat it the same way as if there is not answer.
if (is_bool($xml) && $xml) {
// Nothing to do here, this might be a post returning that everything went well.
return;
}
if ((string) $xml->returncode == 'FAILED') {
$errorcode = self::get_errorcode_from_xml_messagekey($messagekey);
if (!$additionaldetails) {
$additionaldetails = [];
}
$additionaldetails['xmlmessage'] = (string) $xml->message ?? '';
throw new bigbluebutton_exception($errorcode, json_encode($additionaldetails));
}
}
/**
* Get Moodle error code from returned Message Key
*
* @param string $messagekey
* @return string
*/
private static function get_errorcode_from_xml_messagekey(string $messagekey): string {
$errorcode = 'general_error_no_answer';
if ($messagekey) {
$errorcode = self::BBB_TO_MOODLE_ERROR_CODE[$messagekey] ?? $errorcode;
}
return $errorcode;
}
/**
* Get Moodle error code from returned Message Key
*
* @param string $messagekey
* @return string
*/
private static function is_known_server_unavailable_errorcode(string $messagekey): string {
// For now, only checksumError is supposed to mean that the server is unavailable.
// Other errors are recoverable.
return in_array($messagekey, ['checksumError']);
}
/**
* Fetch the XML from an endpoint and test for success.
*
* If the result could not be loaded, or the returncode was not 'SUCCESS', a null value is returned.
*
* @param string $action
* @param array $data
* @param array $metadata
* @param int|null $instanceid
* @return null|bool|\SimpleXMLElement
*/
protected static function fetch_endpoint_xml(
string $action,
array $data = [],
array $metadata = [],
?int $instanceid = null
) {
if (PHPUNIT_TEST && !defined('TEST_MOD_BIGBLUEBUTTONBN_MOCK_SERVER')) {
return true; // In case we still use fetch and mock server is not defined, this prevents
// an error. This can happen if a function from lib.php is called in test from other modules
// for example.
}
$curl = new curl();
return $curl->get(self::action_url($action, $data, $metadata, $instanceid));
}
/**
* Get checksum
*
* @param string $action
* @param string $params
* @return string
*/
public static function get_checksum(string $action, string $params): string {
return hash(config::get('checksum_algorithm'), $action . $params . self::sanitized_secret());
}
}
@@ -0,0 +1,405 @@
<?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/>.
namespace mod_bigbluebuttonbn\local\proxy;
use cache;
use cache_helper;
use SimpleXMLElement;
/**
* The recording proxy.
*
* This class acts as a proxy between Moodle and the BigBlueButton API server,
* and deals with all requests relating to recordings.
*
* @package mod_bigbluebuttonbn
* @copyright 2021 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent [at] call-learning [dt] fr)
* @author Jesus Federico (jesus [at] blindsidenetworks [dt] com)
*/
class recording_proxy extends proxy_base {
/**
* Invalidate the MUC cache for the specified recording.
*
* @param string $recordid
*/
protected static function invalidate_cache_for_recording(string $recordid): void {
cache_helper::invalidate_by_event('mod_bigbluebuttonbn/recordingchanged', [$recordid]);
}
/**
* Perform deleteRecordings on BBB.
*
* @param string $recordid a recording id
* @return bool
*/
public static function delete_recording(string $recordid): bool {
$result = self::fetch_endpoint_xml('deleteRecordings', ['recordID' => $recordid]);
if (!$result || $result->returncode != 'SUCCESS') {
return false;
}
return true;
}
/**
* Perform publishRecordings on BBB.
*
* @param string $recordid
* @param string $publish
* @return bool
*/
public static function publish_recording(string $recordid, string $publish = 'true'): bool {
$result = self::fetch_endpoint_xml('publishRecordings', [
'recordID' => $recordid,
'publish' => $publish,
]);
self::invalidate_cache_for_recording($recordid);
if (!$result || $result->returncode != 'SUCCESS') {
return false;
}
return true;
}
/**
* Perform publishRecordings on BBB.
*
* @param string $recordid
* @param string $protected
* @return bool
*/
public static function protect_recording(string $recordid, string $protected = 'true'): bool {
global $CFG;
// Ignore action if recording_protect_editable is set to false.
if (empty($CFG->bigbluebuttonbn_recording_protect_editable)) {
return false;
}
$result = self::fetch_endpoint_xml('updateRecordings', [
'recordID' => $recordid,
'protect' => $protected,
]);
self::invalidate_cache_for_recording($recordid);
if (!$result || $result->returncode != 'SUCCESS') {
return false;
}
return true;
}
/**
* Perform updateRecordings on BBB.
*
* @param string $recordid a single record identifier
* @param array $params ['key'=>param_key, 'value']
*/
public static function update_recording(string $recordid, array $params): bool {
$result = self::fetch_endpoint_xml('updateRecordings', array_merge([
'recordID' => $recordid
], $params));
self::invalidate_cache_for_recording($recordid);
return $result ? $result->returncode == 'SUCCESS' : false;
}
/**
* Helper function to fetch a single recording from a BigBlueButton server.
*
* @param string $recordingid
* @return null|array
*/
public static function fetch_recording(string $recordingid): ?array {
$data = self::fetch_recordings([$recordingid]);
if (array_key_exists($recordingid, $data)) {
return $data[$recordingid];
}
return null;
}
/**
* Check whether the current recording is a protected recording and purge the cache if necessary.
*
* @param string $recordingid
*/
public static function purge_protected_recording(string $recordingid): void {
$cache = cache::make('mod_bigbluebuttonbn', 'recordings');
$recording = $cache->get($recordingid);
if (empty($recording)) {
// This value was not cached to begin with.
return;
}
$currentfetchcache = cache::make('mod_bigbluebuttonbn', 'currentfetch');
if ($currentfetchcache->has($recordingid)) {
// This item was fetched in the current request.
return;
}
if (array_key_exists('protected', $recording) && $recording['protected'] === 'true') {
// This item is protected. Purge it from the cache.
$cache->delete($recordingid);
return;
}
}
/**
* Helper function to fetch recordings from a BigBlueButton server.
*
* We use a cache to store recording indexed by keyids/recordingID.
* @param array $keyids list of recordingids
* @return array (associative) with recordings indexed by recordID, each recording is a non sequential array
* and sorted by {@see recording_proxy::sort_recordings}
*/
public static function fetch_recordings(array $keyids = []): array {
$recordings = [];
// If $ids is empty return array() to prevent a getRecordings with meetingID and recordID set to ''.
if (empty($keyids)) {
return $recordings;
}
$cache = cache::make('mod_bigbluebuttonbn', 'recordings');
$currentfetchcache = cache::make('mod_bigbluebuttonbn', 'currentfetch');
$recordings = array_filter($cache->get_many($keyids));
$missingkeys = array_diff(array_values($keyids), array_keys($recordings));
$recordings += self::do_fetch_recordings($missingkeys);
$cache->set_many($recordings);
$currentfetchcache->set_many(array_flip(array_keys($recordings)));
return $recordings;
}
/**
* Helper function to retrieve recordings that failed to be fetched from a BigBlueButton server.
*
* @param array $keyids list of recordingids
* @return array array of recording recordingids not fetched from server
* and sorted by {@see recording_proxy::sort_recordings}
*/
public static function fetch_missing_recordings(array $keyids = []): array {
$unfetchedids = [];
$pagesize = 25;
// If $ids is empty return array() to prevent a getRecordings with meetingID and recordID set to ''.
if (empty($keyids)) {
return $unfetchedids;
}
while ($ids = array_splice($keyids, 0, $pagesize)) {
// We make getRecordings API call to check recordings are successfully retrieved.
$xml = self::fetch_endpoint_xml('getRecordings', ['recordID' => implode(',', $ids), 'state' => 'any']);
if (!$xml || $xml->returncode != 'SUCCESS' || !isset($xml->recordings)) {
$unfetchedids = array_merge($unfetchedids, $ids);
continue; // We will keep record of all unfetched ids.
}
}
return $unfetchedids;
}
/**
* Helper function to fetch recordings from a BigBlueButton server.
*
* @param array $keyids list of meetingids
* @return array (associative) with recordings indexed by recordID, each recording is a non sequential array
* and sorted by {@see recording_proxy::sort_recordings}
*/
public static function fetch_recording_by_meeting_id(array $keyids = []): array {
$recordings = [];
// If $ids is empty return array() to prevent a getRecordings with meetingID and recordID set to ''.
if (empty($keyids)) {
return $recordings;
}
$recordings = self::do_fetch_recordings($keyids, 'meetingID');
return $recordings;
}
/**
* Helper function to fetch recordings from a BigBlueButton server.
*
* @param array $keyids list of meetingids or recordingids
* @param string $key the param name used for the BBB request (<recordID>|meetingID)
* @return array (associative) with recordings indexed by recordID, each recording is a non sequential array.
* and sorted {@see recording_proxy::sort_recordings}
*/
private static function do_fetch_recordings(array $keyids = [], string $key = 'recordID'): array {
$recordings = [];
$pagesize = 25;
while ($ids = array_splice($keyids, 0, $pagesize)) {
$fetchrecordings = self::fetch_recordings_page($ids, $key);
$recordings += $fetchrecordings;
}
// Sort recordings.
return self::sort_recordings($recordings);
}
/**
* Helper function to fetch a page of recordings from the remote server.
*
* @param array $ids
* @param string $key
* @return array
*/
private static function fetch_recordings_page(array $ids, $key = 'recordID'): array {
// The getRecordings call is executed using a method GET (supported by all versions of BBB).
$xml = self::fetch_endpoint_xml('getRecordings', [$key => implode(',', $ids), 'state' => 'any']);
if (!$xml) {
return [];
}
if ($xml->returncode != 'SUCCESS') {
return [];
}
if (!isset($xml->recordings)) {
return [];
}
$recordings = [];
// If there were recordings already created.
foreach ($xml->recordings->recording as $recordingxml) {
$recording = self::parse_recording($recordingxml);
$recordings[$recording['recordID']] = $recording;
// Check if there are any child.
if (isset($recordingxml->breakoutRooms->breakoutRoom)) {
$breakoutrooms = [];
foreach ($recordingxml->breakoutRooms->breakoutRoom as $breakoutroom) {
$breakoutrooms[] = trim((string) $breakoutroom);
}
if ($breakoutrooms) {
$xml = self::fetch_endpoint_xml('getRecordings', ['recordID' => implode(',', $breakoutrooms)]);
if ($xml && $xml->returncode == 'SUCCESS' && isset($xml->recordings)) {
// If there were already created meetings.
foreach ($xml->recordings->recording as $subrecordingxml) {
$recording = self::parse_recording($subrecordingxml);
$recordings[$recording['recordID']] = $recording;
}
}
}
}
}
return $recordings;
}
/**
* Helper function to sort an array of recordings. It compares the startTime in two recording objects.
*
* @param array $recordings
* @return array
*/
public static function sort_recordings(array $recordings): array {
global $CFG;
uasort($recordings, function($a, $b) {
if ($a['startTime'] < $b['startTime']) {
return -1;
}
if ($a['startTime'] == $b['startTime']) {
return 0;
}
return 1;
});
return $recordings;
}
/**
* Helper function to parse an xml recording object and produce an array in the format used by the plugin.
*
* @param SimpleXMLElement $recording
*
* @return array
*/
public static function parse_recording(SimpleXMLElement $recording): array {
// Add formats.
$playbackarray = [];
foreach ($recording->playback->format as $format) {
$playbackarray[(string) $format->type] = [
'type' => (string) $format->type,
'url' => trim((string) $format->url), 'length' => (string) $format->length
];
// Add preview per format when existing.
if ($format->preview) {
$playbackarray[(string) $format->type]['preview'] =
self::parse_preview_images($format->preview);
}
}
// Add the metadata to the recordings array.
$metadataarray =
self::parse_recording_meta(get_object_vars($recording->metadata));
$recordingarray = [
'recordID' => (string) $recording->recordID,
'meetingID' => (string) $recording->meetingID,
'meetingName' => (string) $recording->name,
'published' => (string) $recording->published,
'state' => (string) $recording->state,
'startTime' => (string) $recording->startTime,
'endTime' => (string) $recording->endTime,
'playbacks' => $playbackarray
];
if (isset($recording->protected)) {
$recordingarray['protected'] = (string) $recording->protected;
}
return $recordingarray + $metadataarray;
}
/**
* Helper function to convert an xml recording metadata object to an array in the format used by the plugin.
*
* @param array $metadata
*
* @return array
*/
public static function parse_recording_meta(array $metadata): array {
$metadataarray = [];
foreach ($metadata as $key => $value) {
if (is_object($value)) {
$value = '';
}
$metadataarray['meta_' . $key] = $value;
}
return $metadataarray;
}
/**
* Helper function to convert an xml recording preview images to an array in the format used by the plugin.
*
* @param SimpleXMLElement $preview
*
* @return array
*/
public static function parse_preview_images(SimpleXMLElement $preview): array {
$imagesarray = [];
foreach ($preview->images->image as $image) {
$imagearray = ['url' => trim((string) $image)];
foreach ($image->attributes() as $attkey => $attvalue) {
$imagearray[$attkey] = (string) $attvalue;
}
array_push($imagesarray, $imagearray);
}
return $imagesarray;
}
}
+500
View File
@@ -0,0 +1,500 @@
<?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/>.
namespace mod_bigbluebuttonbn;
use mod_bigbluebuttonbn\event\events;
use stdClass;
/**
* Utility class for all logs routines helper.
*
* @package mod_bigbluebuttonbn
* @copyright 2021 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent [at] call-learning [dt] fr)
*/
class logger {
/** @var string The bigbluebuttonbn Add event */
public const EVENT_ADD = 'Add';
/** @var string The bigbluebuttonbn Edit event */
public const EVENT_EDIT = 'Edit';
/** @var string The bigbluebuttonbn Create event */
public const EVENT_CREATE = 'Create';
/** @var string The bigbluebuttonbn Join event */
public const EVENT_JOIN = 'Join';
/** @var string The bigbluebuttonbn Playback event */
public const EVENT_PLAYED = 'Played';
/** @var string The bigbluebuttonbn Logout event */
public const EVENT_LOGOUT = 'Logout';
/** @var string The bigbluebuttonbn Import event */
public const EVENT_IMPORT = 'Import';
/** @var string The bigbluebuttonbn Delete event */
public const EVENT_DELETE = 'Delete';
/** @var string The bigbluebuttonbn Callback event */
public const EVENT_CALLBACK = 'Callback';
/** @var string The bigbluebuttonbn Summary event */
public const EVENT_SUMMARY = 'Summary';
/** @var string This is a specific log to mark this log as upgraded: used only in the upgrade process from 2.4
*
* Note: Migrated event name change: once a log has been migrated we mark
* it as migrated by changing its log name. This will help to recover
* manually if we have an issue in the migration process.
*/
public const EVENT_IMPORT_MIGRATED = 'import-migrated';
/** @var string This is a specific log to mark this log as upgraded: used only in the upgrade process from 2.4 */
public const EVENT_CREATE_MIGRATED = 'create-migrated';
/** @var string The bigbluebuttonbn meeting_start event */
public const EVENT_MEETING_START = 'meeting_start';
/** @var int The user accessed the session from activity page */
public const ORIGIN_BASE = 0;
/** @var int The user accessed the session from Timeline */
public const ORIGIN_TIMELINE = 1;
/** @var int The user accessed the session from Index */
public const ORIGIN_INDEX = 2;
/**
* Get the user event logs related to completion, for the specified user in the named instance.
*
* @param instance $instance
* @param int|null $userid
* @param array|null $filters
* @param int|null $timestart
* @return array
*/
public static function get_user_completion_logs(
instance $instance,
?int $userid,
?array $filters,
?int $timestart = 0
): array {
global $DB;
$filters = $filters ?? [self::EVENT_JOIN, self::EVENT_PLAYED, self::EVENT_SUMMARY];
[$wheresql, $params] = static::get_user_completion_sql_params($instance, $userid, $filters, $timestart);
return $DB->get_records_select('bigbluebuttonbn_logs', $wheresql, $params);
}
/**
* Get the user event logs related to completion, for the specified user in the named instance.
*
* @param instance $instance
* @param int|null $userid
* @param array|null $filters
* @param int|null $timestart
* @return array
*/
public static function get_user_completion_logs_with_userfields(
instance $instance,
?int $userid,
?array $filters,
?int $timestart = 0
): array {
global $DB;
$filters = $filters ?? [self::EVENT_JOIN, self::EVENT_PLAYED, self::EVENT_SUMMARY];
[$wheresql, $params] = static::get_user_completion_sql_params($instance, $userid, $filters, $timestart, 'l');
$userfieldsapi = \core_user\fields::for_userpic();
$userfields = $userfieldsapi->get_sql('u', false, '', 'userid', false)->selects;
$logtable = new \core\dml\table('bigbluebuttonbn_logs', 'l', '');
$logtableselect = $logtable->get_field_select();
$logtablefrom = $logtable->get_from_sql();
$usertable = new \core\dml\table('user', 'u', '');
$usertablefrom = $usertable->get_from_sql();
$sql = <<<EOF
SELECT {$logtableselect}, {$userfields}
FROM {$logtablefrom}
INNER JOIN {$usertablefrom} ON u.id = l.userid
WHERE $wheresql
EOF;
return $DB->get_records_sql($sql, $params);
}
/**
* Get the latest timestamp for any event logs related to completion, for the specified user in the named instance.
*
* @param instance $instance
* @param int|null $userid
* @param array|null $filters
* @param int|null $timestart
* @return int
*/
public static function get_user_completion_logs_max_timestamp(
instance $instance,
?int $userid,
?array $filters,
?int $timestart = 0
): int {
global $DB;
[$wheresql, $params] = static::get_user_completion_sql_params($instance, $userid, $filters, $timestart);
$select = "SELECT MAX(timecreated) ";
$lastlogtime = $DB->get_field_sql($select . ' FROM {bigbluebuttonbn_logs} WHERE ' . $wheresql, $params);
return $lastlogtime ?? 0;
}
/**
* Helper method to get the right SQL query for completion
*
* @param instance $instance
* @param int|null $userid
* @param array|null $filters
* @param int|null $timestart
* @param string|null $logtablealias
* @return array
*/
protected static function get_user_completion_sql_params(instance $instance, ?int $userid, ?array $filters, ?int $timestart,
?string $logtablealias = null) {
global $DB;
$filters = $filters ?? [self::EVENT_JOIN, self::EVENT_PLAYED, self::EVENT_SUMMARY];
[$insql, $params] = $DB->get_in_or_equal($filters, SQL_PARAMS_NAMED);
$wheres = [];
$wheres['bigbluebuttonbnid'] = '= :instanceid';
$wheres['courseid'] = '= :courseid'; // This speeds up the requests masively as courseid is an index.
if ($timestart) {
$wheres['timecreated'] = ' > :timestart';
$params['timestart'] = $timestart;
}
if ($userid) {
$wheres['userid'] = ' = :userid';
$params['userid'] = $userid;
}
$params['instanceid'] = $instance->get_instance_id();
$params['courseid'] = $instance->get_course_id();
$wheres['log'] = " $insql";
$wheresqls = [];
foreach ($wheres as $key => $val) {
$prefix = !empty($logtablealias) ? "$logtablealias." : "";
$wheresqls[] = "$prefix$key $val";
}
return [join(' AND ', $wheresqls), $params];
}
/**
* Log that an instance was created.
*
* Note: This event cannot take the instance class as it is typically called before the cm has been configured.
*
* @param stdClass $instancedata
*/
public static function log_instance_created(stdClass $instancedata): void {
self::raw_log(
self::EVENT_ADD,
$instancedata->id,
$instancedata->course,
$instancedata->meetingid
);
}
/**
* Log that an instance was updated.
*
* @param instance $instance
*/
public static function log_instance_updated(instance $instance): void {
self::log($instance, self::EVENT_EDIT);
}
/**
* Log an instance deleted event.
*
* @param instance $instance
*/
public static function log_instance_deleted(instance $instance): void {
global $DB;
$wheresql = 'bigbluebuttonbnid = :instanceid AND log = :logtype AND ' . $DB->sql_compare_text('meta') . ' = :meta';
$logs = $DB->get_records_select('bigbluebuttonbn_logs', $wheresql, [
'instanceid' => $instance->get_instance_id(),
'logtype' => self::EVENT_CREATE,
'meta' => "{\"record\":true}"
]);
$meta = "{\"has_recordings\":" . empty($logs) ? "true" : "false" . "}";
self::log($instance, self::EVENT_DELETE, [], $meta);
}
/**
* Log an event callback.
*
* @param instance $instance
* @param array $overrides
* @param array $meta
* @return int The new count of callback events
*/
public static function log_event_callback(instance $instance, array $overrides, array $meta): int {
self::log(
$instance,
self::EVENT_CALLBACK,
$overrides,
json_encode($meta)
);
return self::count_callback_events($meta['internalmeetingid'], 'meeting_events');
}
/**
* Log an event summary event.
*
* @param instance $instance
* @param array $overrides
* @param array $meta
*/
public static function log_event_summary(instance $instance, array $overrides = [], array $meta = []): void {
self::log(
$instance,
self::EVENT_SUMMARY,
$overrides,
json_encode($meta)
);
}
/**
* Log that an instance was viewed.
*
* @param instance $instance
*/
public static function log_instance_viewed(instance $instance): void {
self::log_moodle_event($instance, events::$events['view']);
}
/**
* Log the events for when a meeting was ended.
*
* @param instance $instance
*/
public static function log_meeting_ended_event(instance $instance): void {
// Moodle event logger: Create an event for meeting ended.
self::log_moodle_event($instance, events::$events['meeting_end']);
}
/**
* Log the relevant events for when a meeting was joined.
*
* @param instance $instance
* @param int $origin
*/
public static function log_meeting_joined_event(instance $instance, int $origin): void {
// Moodle event logger: Create an event for meeting joined.
self::log_moodle_event($instance, events::$events['meeting_join']);
// Internal logger: Instert a record with the meeting created.
self::log(
$instance,
self::EVENT_JOIN,
['meetingid' => $instance->get_meeting_id()],
json_encode((object) ['origin' => $origin])
);
}
/**
* Log the relevant events for when a user left a meeting.
*
* @param instance $instance
*/
public static function log_meeting_left_event(instance $instance): void {
// Moodle event logger: Create an event for meeting left.
self::log_moodle_event($instance, events::$events['meeting_left']);
}
/**
* Log the relevant events for when a recording has been played.
*
* @param instance $instance
* @param int $rid RecordID
*/
public static function log_recording_played_event(instance $instance, int $rid): void {
// Moodle event logger: Create an event for recording played.
self::log_moodle_event($instance, events::$events['recording_play'], ['other' => $rid]);
// Internal logger: Insert a record with the playback played.
self::log(
$instance,
self::EVENT_PLAYED,
[
'meetingid' => $instance->get_meeting_id(),
],
json_encode(['recordingid' => $rid])
);
}
/**
* Register a bigbluebuttonbn event from an instance.
*
* @param instance $instance
* @param string $event
* @param array $overrides
* @param string|null $meta
* @return bool
*/
protected static function log(instance $instance, string $event, array $overrides = [], ?string $meta = null): bool {
return self::raw_log(
$event,
$instance->get_instance_id(),
$instance->get_course_id(),
$instance->get_meeting_id(),
$overrides,
$meta
);
}
/**
* Register a bigbluebuttonbn event from raw data.
*
* @param string $event
* @param int $instanceid
* @param int $courseid
* @param string $meetingid
* @param array $overrides
* @param string|null $meta
* @return bool
*/
protected static function raw_log(
string $event,
int $instanceid,
int $courseid,
string $meetingid,
array $overrides = [],
?string $meta = null
): bool {
global $DB, $USER;
$log = (object) array_merge([
// Default values.
'courseid' => $courseid,
'bigbluebuttonbnid' => $instanceid,
'userid' => $USER->id,
'meetingid' => $meetingid,
'timecreated' => time(),
'log' => $event,
'meta' => $meta,
], $overrides);
return !!$DB->insert_record('bigbluebuttonbn_logs', $log);
}
/**
* Helper register a bigbluebuttonbn event.
*
* @param instance $instance
* @param string $type
* @param array $options [timecreated, userid, other]
*/
protected static function log_moodle_event(instance $instance, string $type, array $options = []): void {
if (!in_array($type, \mod_bigbluebuttonbn\event\events::$events)) {
// No log will be created.
return;
}
$params = [
'context' => $instance->get_context(),
'objectid' => $instance->get_instance_id(),
];
if (array_key_exists('timecreated', $options)) {
$params['timecreated'] = $options['timecreated'];
}
if (array_key_exists('userid', $options)) {
$params['userid'] = $options['userid'];
}
if (array_key_exists('other', $options)) {
$params['other'] = $options['other'];
}
$event = call_user_func_array("\\mod_bigbluebuttonbn\\event\\{$type}::create", [$params]);
$event->add_record_snapshot('course_modules', $instance->get_cm());
$event->add_record_snapshot('course', $instance->get_course());
$event->add_record_snapshot('bigbluebuttonbn', $instance->get_instance_data());
$event->trigger();
}
/**
* Helper function to count the number of callback logs matching the supplied specifications.
*
* @param string $id
* @param string $callbacktype
* @return int
*/
protected static function count_callback_events(string $id, string $callbacktype = 'recording_ready'): int {
global $DB;
// Look for a log record that is of "Callback" type and is related to the given event.
$conditions = [
"log = :logtype",
$DB->sql_like('meta', ':cbtypelike')
];
$params = [
'logtype' => self::EVENT_CALLBACK,
'cbtypelike' => "%meeting_events%" // All callbacks are meeting events, even recording events.
];
$basesql = 'SELECT COUNT(DISTINCT id) FROM {bigbluebuttonbn_logs}';
switch ($callbacktype) {
case 'recording_ready':
$conditions[] = $DB->sql_like('meta', ':isrecordid');
$params['isrecordid'] = '%recordid%'; // The recordid field in the meta field (json encoded).
break;
case 'meeting_events':
$conditions[] = $DB->sql_like('meta', ':idlike');
$params['idlike'] = "%$id%"; // The unique id of the meeting is the meta field (json encoded).
break;
}
$wheresql = join(' AND ', $conditions);
return $DB->count_records_sql($basesql . ' WHERE ' . $wheresql, $params);
}
/**
* Log event to string that can be internationalised via get_string.
*/
const LOG_TO_STRING = [
self::EVENT_JOIN => 'event_meeting_joined',
self::EVENT_PLAYED => 'event_recording_viewed',
self::EVENT_IMPORT => 'event_recording_imported',
self::EVENT_ADD => 'event_activity_created',
self::EVENT_DELETE => 'event_activity_deleted',
self::EVENT_EDIT => 'event_activity_updated',
self::EVENT_SUMMARY => 'event_meeting_summary',
self::EVENT_LOGOUT => 'event_meeting_left',
self::EVENT_MEETING_START => 'event_meeting_joined',
];
/**
* Get the event name (human friendly version)
*
* @param object $log object as returned by get_user_completion_logs_with_userfields
*/
public static function get_printable_event_name(object $log) {
$logstringname = self::LOG_TO_STRING[$log->log] ?? 'event_unknown';
return get_string($logstringname, 'mod_bigbluebuttonbn');
}
}
+582
View File
@@ -0,0 +1,582 @@
<?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/>.
namespace mod_bigbluebuttonbn;
use cache;
use cache_store;
use context_course;
use core_tag_tag;
use Exception;
use Firebase\JWT\Key;
use mod_bigbluebuttonbn\local\config;
use mod_bigbluebuttonbn\local\exceptions\bigbluebutton_exception;
use mod_bigbluebuttonbn\local\exceptions\meeting_join_exception;
use mod_bigbluebuttonbn\local\helpers\roles;
use mod_bigbluebuttonbn\local\proxy\bigbluebutton_proxy;
use stdClass;
/**
* Class to describe a BBB Meeting.
*
* @package mod_bigbluebuttonbn
* @copyright 2021 Andrew Lyons <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class meeting {
/** @var instance The bbb instance */
protected $instance;
/** @var stdClass Info about the meeting */
protected $meetinginfo = null;
/**
* Constructor for the meeting object.
*
* @param instance $instance
*/
public function __construct(instance $instance) {
$this->instance = $instance;
}
/**
* Helper to join a meeting.
*
*
* It will create the meeting if not already created.
*
* @param instance $instance
* @param int $origin
* @return string
* @throws meeting_join_exception this is sent if we cannot join (meeting full, user needs to wait...)
*/
public static function join_meeting(instance $instance, $origin = logger::ORIGIN_BASE): string {
// See if the session is in progress.
$meeting = new meeting($instance);
// As the meeting doesn't exist, try to create it.
if (empty($meeting->get_meeting_info(true)->createtime)) {
$meeting->create_meeting();
}
return $meeting->join($origin);
}
/**
* Get currently stored meeting info
*
* @return stdClass
*/
public function get_meeting_info() {
if (!$this->meetinginfo) {
$this->meetinginfo = $this->do_get_meeting_info();
}
return $this->meetinginfo;
}
/**
* Return meeting information for the specified instance.
*
* @param instance $instance
* @param bool $updatecache Whether to update the cache when fetching the information
* @return stdClass
*/
public static function get_meeting_info_for_instance(instance $instance, bool $updatecache = false): stdClass {
$meeting = new self($instance);
return $meeting->do_get_meeting_info($updatecache);
}
/**
* Helper function returns a sha1 encoded string that is unique and will be used as a seed for meetingid.
*
* @return string
*/
public static function get_unique_meetingid_seed() {
global $DB;
do {
$encodedseed = sha1(plugin::random_password(12));
$meetingid = (string) $DB->get_field('bigbluebuttonbn', 'meetingid', ['meetingid' => $encodedseed]);
} while ($meetingid == $encodedseed);
return $encodedseed;
}
/**
* Is meeting running ?
*
* @return bool
*/
public function is_running() {
return $this->get_meeting_info()->statusrunning ?? false;
}
/**
* Force update the meeting in cache.
*/
public function update_cache() {
$this->meetinginfo = $this->do_get_meeting_info(true);
}
/**
* Get meeting attendees
*
* @return array[]
*/
public function get_attendees(): array {
return $this->get_meeting_info()->attendees ?? [];
}
/**
* Can the meeting be joined ?
*
* @return bool
*/
public function can_join() {
return $this->get_meeting_info()->canjoin;
}
/**
* Total number of moderators and viewers.
*
* @return int
*/
public function get_participant_count() {
return $this->get_meeting_info()->totalusercount;
}
/**
* Creates a bigbluebutton meeting, send the message to BBB and returns the response in an array.
*
* @return array
*/
public function create_meeting() {
$data = $this->create_meeting_data();
$metadata = $this->create_meeting_metadata();
$presentation = $this->instance->get_presentation_for_bigbluebutton_upload(); // The URL must contain nonce.
$presentationname = $presentation['name'] ?? null;
$presentationurl = $presentation['url'] ?? null;
$response = bigbluebutton_proxy::create_meeting(
$data,
$metadata,
$presentationname,
$presentationurl,
$this->instance->get_instance_id()
);
// New recording management: Insert a recordingID that corresponds to the meeting created.
if ($this->instance->is_recorded()) {
$recording = new recording(0, (object) [
'courseid' => $this->instance->get_course_id(),
'bigbluebuttonbnid' => $this->instance->get_instance_id(),
'recordingid' => $response['internalMeetingID'],
'groupid' => $this->instance->get_group_id()]
);
$recording->create();
}
return $response;
}
/**
* Send an end meeting message to BBB server
*/
public function end_meeting() {
bigbluebutton_proxy::end_meeting(
$this->instance->get_meeting_id(),
$this->instance->get_moderator_password(),
$this->instance->get_instance_id()
);
}
/**
* Get meeting join URL
*
* @return string
*/
public function get_join_url(): string {
return bigbluebutton_proxy::get_join_url($this->instance, $this->get_meeting_info()->createtime);
}
/**
* Get meeting join URL for guest
*
* @param string $userfullname
* @return string
*/
public function get_guest_join_url(string $userfullname): string {
return bigbluebutton_proxy::get_guest_join_url($this->instance, $this->get_meeting_info()->createtime, $userfullname);
}
/**
* Return meeting information for this meeting.
*
* @param bool $updatecache Whether to update the cache when fetching the information
* @return stdClass
*/
protected function do_get_meeting_info(bool $updatecache = false): stdClass {
$instance = $this->instance;
$meetinginfo = (object) [
'instanceid' => $instance->get_instance_id(),
'bigbluebuttonbnid' => $instance->get_instance_id(),
'groupid' => $instance->get_group_id(),
'meetingid' => $instance->get_meeting_id(),
'cmid' => $instance->get_cm_id(),
'ismoderator' => $instance->is_moderator(),
'joinurl' => $instance->get_join_url()->out(),
'userlimit' => $instance->get_user_limit(),
'presentations' => [],
];
if ($instance->get_instance_var('openingtime')) {
$meetinginfo->openingtime = intval($instance->get_instance_var('openingtime'));
}
if ($instance->get_instance_var('closingtime')) {
$meetinginfo->closingtime = intval($instance->get_instance_var('closingtime'));
}
$activitystatus = bigbluebutton_proxy::view_get_activity_status($instance);
// This might raise an exception if info cannot be retrieved.
// But this might be totally fine as the meeting is maybe not yet created on BBB side.
$totalusercount = 0;
// This is the default value for any meeting that has not been created.
$meetinginfo->statusrunning = false;
$meetinginfo->createtime = null;
$info = self::retrieve_cached_meeting_info($this->instance, $updatecache);
if (!empty($info)) {
$meetinginfo->statusrunning = $info['running'] === 'true';
$meetinginfo->createtime = $info['createTime'] ?? null;
$totalusercount = isset($info['participantCount']) ? $info['participantCount'] : 0;
}
$meetinginfo->statusclosed = $activitystatus === 'ended';
$meetinginfo->statusopen = !$meetinginfo->statusrunning && $activitystatus === 'open';
$meetinginfo->totalusercount = $totalusercount;
$canjoin = !$instance->user_must_wait_to_join() || $meetinginfo->statusrunning;
// Limit has not been reached.
$canjoin = $canjoin && (!$instance->has_user_limit_been_reached($totalusercount));
// User should only join during scheduled session start and end time, if defined.
$canjoin = $canjoin && ($instance->is_currently_open());
// Double check that the user has the capabilities to join.
$canjoin = $canjoin && $instance->can_join();
$meetinginfo->canjoin = $canjoin;
// If user is administrator, moderator or if is viewer and no waiting is required, join allowed.
if ($meetinginfo->statusrunning) {
$meetinginfo->startedat = floor(intval($info['startTime']) / 1000); // Milliseconds.
$meetinginfo->moderatorcount = $info['moderatorCount'];
$meetinginfo->moderatorplural = $info['moderatorCount'] > 1;
$meetinginfo->participantcount = $totalusercount - $meetinginfo->moderatorcount;
$meetinginfo->participantplural = $meetinginfo->participantcount > 1;
}
$meetinginfo->statusmessage = $this->get_status_message($meetinginfo, $instance);
$presentation = $instance->get_presentation(); // This is for internal use.
if (!empty($presentation)) {
$meetinginfo->presentations[] = $presentation;
}
$meetinginfo->attendees = [];
if (!empty($info['attendees'])) {
// Ensure each returned attendee is cast to an array, rather than a simpleXML object.
foreach ($info['attendees'] as $attendee) {
$meetinginfo->attendees[] = (array) $attendee;
}
}
$meetinginfo->guestaccessenabled = $instance->is_guest_allowed();
if ($meetinginfo->guestaccessenabled && $instance->is_moderator()) {
$meetinginfo->guestjoinurl = $instance->get_guest_access_url()->out();
$meetinginfo->guestpassword = $instance->get_guest_access_password();
}
$meetinginfo->features = $instance->get_enabled_features();
return $meetinginfo;
}
/**
* Deduce status message from the current meeting info and the instance
*
* Returns the human-readable message depending on if the user must wait to join, the meeting has not
* yet started ...
* @param object $meetinginfo
* @param instance $instance
* @return string
*/
protected function get_status_message(object $meetinginfo, instance $instance): string {
if ($instance->has_user_limit_been_reached($meetinginfo->totalusercount)) {
return get_string('view_message_conference_user_limit_reached', 'bigbluebuttonbn');
}
if ($meetinginfo->statusrunning) {
return get_string('view_message_conference_in_progress', 'bigbluebuttonbn');
}
if ($instance->user_must_wait_to_join() && !$instance->user_can_force_join()) {
return get_string('view_message_conference_wait_for_moderator', 'bigbluebuttonbn');
}
if ($instance->before_start_time()) {
return get_string('view_message_conference_not_started', 'bigbluebuttonbn');
}
if ($instance->has_ended()) {
return get_string('view_message_conference_has_ended', 'bigbluebuttonbn');
}
return get_string('view_message_conference_room_ready', 'bigbluebuttonbn');
}
/**
* Gets a meeting info object cached or fetched from the live session.
*
* @param instance $instance
* @param bool $updatecache
*
* @return array
*/
protected static function retrieve_cached_meeting_info(instance $instance, $updatecache = false) {
$meetingid = $instance->get_meeting_id();
$cachettl = (int) config::get('waitformoderator_cache_ttl');
$cache = cache::make_from_params(cache_store::MODE_APPLICATION, 'mod_bigbluebuttonbn', 'meetings_cache');
$result = $cache->get($meetingid);
$now = time();
if (!$updatecache && !empty($result) && $now < ($result['creation_time'] + $cachettl)) {
// Use the value in the cache.
return (array) json_decode($result['meeting_info']);
}
// We set the cache to an empty value so then if get_meeting_info raises an exception we still have the
// info about the last creation_time, so we don't ask the server again for a bit.
$defaultcacheinfo = ['creation_time' => time(), 'meeting_info' => '[]'];
// Pings again and refreshes the cache.
try {
$meetinginfo = bigbluebutton_proxy::get_meeting_info($meetingid);
$cache->set($meetingid, ['creation_time' => time(), 'meeting_info' => json_encode($meetinginfo)]);
} catch (bigbluebutton_exception $e) {
// The meeting is not created on BBB side, so we set the value in the cache so we don't poll again
// and return an empty array.
$cache->set($meetingid, $defaultcacheinfo);
return [];
}
return $meetinginfo;
}
/**
* Conversion between form settings and lockSettings as set in BBB API.
*/
const LOCK_SETTINGS_MEETING_DATA = [
'disablecam' => 'lockSettingsDisableCam',
'disablemic' => 'lockSettingsDisableMic',
'disableprivatechat' => 'lockSettingsDisablePrivateChat',
'disablepublicchat' => 'lockSettingsDisablePublicChat',
'disablenote' => 'lockSettingsDisableNotes',
'hideuserlist' => 'lockSettingsHideUserList'
];
/**
* Helper to prepare data used for create meeting.
* @todo moderatorPW and attendeePW will be removed from create after release of BBB v2.6.
*
* @return array
*/
protected function create_meeting_data() {
$data = ['meetingID' => $this->instance->get_meeting_id(),
'name' => \mod_bigbluebuttonbn\plugin::html2text($this->instance->get_meeting_name(), 64),
'attendeePW' => $this->instance->get_viewer_password(),
'moderatorPW' => $this->instance->get_moderator_password(),
'logoutURL' => $this->instance->get_logout_url()->out(false),
];
$data['record'] = $this->instance->should_record() ? 'true' : 'false';
// Check if auto_start_record is enable.
if ($data['record'] == 'true' && $this->instance->should_record_from_start()) {
$data['autoStartRecording'] = 'true';
}
// Check if hide_record_button is enable.
if (!$this->instance->should_show_recording_button()) {
$data['allowStartStopRecording'] = 'false';
}
$data['welcome'] = trim($this->instance->get_welcome_message());
$voicebridge = intval($this->instance->get_voice_bridge());
if ($voicebridge > 0 && $voicebridge < 79999) {
$data['voiceBridge'] = $voicebridge;
}
$maxparticipants = intval($this->instance->get_user_limit());
if ($maxparticipants > 0) {
$data['maxParticipants'] = $maxparticipants;
}
if ($this->instance->get_mute_on_start()) {
$data['muteOnStart'] = 'true';
}
// Here a bit of a change compared to the API default behaviour: we should not allow guest to join
// a meeting managed by Moodle by default.
if ($this->instance->is_guest_allowed()) {
$data['guestPolicy'] = $this->instance->is_moderator_approval_required() ? 'ASK_MODERATOR' : 'ALWAYS_ACCEPT';
}
// Locks settings.
foreach (self::LOCK_SETTINGS_MEETING_DATA as $instancevarname => $lockname) {
$instancevar = $this->instance->get_instance_var($instancevarname);
if (!is_null($instancevar)) {
$data[$lockname] = $instancevar ? 'true' : 'false';
if ($instancevar) {
$data['lockSettingsLockOnJoin'] = 'true'; // This will be locked whenever one settings is locked.
}
}
}
return $data;
}
/**
* Helper for preparing metadata used while creating the meeting.
*
* @return array
*/
protected function create_meeting_metadata() {
global $USER;
// Create standard metadata.
$origindata = $this->instance->get_origin_data();
$metadata = [
'bbb-origin' => $origindata->origin,
'bbb-origin-version' => $origindata->originVersion,
'bbb-origin-server-name' => $origindata->originServerName,
'bbb-origin-server-common-name' => $origindata->originServerCommonName,
'bbb-origin-tag' => $origindata->originTag,
'bbb-context' => $this->instance->get_course()->fullname,
'bbb-context-id' => $this->instance->get_course_id(),
'bbb-context-name' => trim(html_to_text($this->instance->get_course()->fullname, 0)),
'bbb-context-label' => trim(html_to_text($this->instance->get_course()->shortname, 0)),
'bbb-recording-name' => plugin::html2text($this->instance->get_meeting_name(), 64),
'bbb-recording-description' => plugin::html2text($this->instance->get_meeting_description(),
64),
'bbb-recording-tags' =>
implode(',', core_tag_tag::get_item_tags_array('core',
'course_modules', $this->instance->get_cm_id())), // Same as $id.
];
// Special metadata for recording processing.
if ((boolean) config::get('recordingstatus_enabled')) {
$metadata["bn-recording-status"] = json_encode(
[
'email' => ['"' . fullname($USER) . '" <' . $USER->email . '>'],
'context' => $this->instance->get_view_url(),
]
);
}
if ((boolean) config::get('recordingready_enabled')) {
$metadata['bbb-recording-ready-url'] = $this->instance->get_record_ready_url()->out(false);
}
if ((boolean) config::get('meetingevents_enabled')) {
$metadata['analytics-callback-url'] = $this->instance->get_meeting_event_notification_url()->out(false);
}
return $metadata;
}
/**
* Helper for responding when storing live meeting events is requested.
*
* The callback with a POST request includes:
* - Authentication: Bearer <A JWT token containing {"exp":<TIMESTAMP>} encoded with HS512>
* - Content Type: application/json
* - Body: <A JSON Object>
*
* @param instance $instance
* @param object $data
* @return string
*/
public static function meeting_events(instance $instance, object $data): string {
$bigbluebuttonbn = $instance->get_instance_data();
// Validate that the bigbluebuttonbn activity corresponds to the meeting_id received.
$meetingidelements = explode('[', $data->{'meeting_id'});
$meetingidelements = explode('-', $meetingidelements[0]);
if (!isset($bigbluebuttonbn) || $bigbluebuttonbn->meetingid != $meetingidelements[0]) {
return 'HTTP/1.0 410 Gone. The activity may have been deleted';
}
// We make sure events are processed only once.
$overrides = ['meetingid' => $data->{'meeting_id'}];
$meta['internalmeetingid'] = $data->{'internal_meeting_id'};
$meta['callback'] = 'meeting_events';
$meta['meetingid'] = $data->{'meeting_id'};
$eventcount = logger::log_event_callback($instance, $overrides, $meta);
if ($eventcount === 1) {
// Process the events.
self::process_meeting_events($instance, $data);
return 'HTTP/1.0 200 Accepted. Enqueued.';
} else {
return 'HTTP/1.0 202 Accepted. Already processed.';
}
}
/**
* Helper function enqueues list of meeting events to be stored and processed as for completion.
*
* @param instance $instance
* @param stdClass $jsonobj
*/
protected static function process_meeting_events(instance $instance, stdClass $jsonobj) {
$meetingid = $jsonobj->{'meeting_id'};
$recordid = $jsonobj->{'internal_meeting_id'};
$attendees = $jsonobj->{'data'}->{'attendees'};
foreach ($attendees as $attendee) {
$userid = $attendee->{'ext_user_id'};
$overrides['meetingid'] = $meetingid;
$overrides['userid'] = $userid;
$meta['recordid'] = $recordid;
$meta['data'] = $attendee;
// Stores the log.
logger::log_event_summary($instance, $overrides, $meta);
// Enqueue a task for processing the completion.
bigbluebutton_proxy::enqueue_completion_event($instance->get_instance_data(), $userid);
}
}
/**
* Prepare join meeting action
*
* @param int $origin
* @return void
*/
protected function prepare_meeting_join_action(int $origin) {
$this->do_get_meeting_info(true);
if ($this->is_running()) {
if ($this->instance->has_user_limit_been_reached($this->get_participant_count())) {
throw new meeting_join_exception('userlimitreached');
}
} else if ($this->instance->user_must_wait_to_join()) {
// If user is not administrator nor moderator (user is student) and waiting is required.
throw new meeting_join_exception('waitformoderator');
}
// Moodle event logger: Create an event for meeting joined.
logger::log_meeting_joined_event($this->instance, $origin);
// Before executing the redirect, increment the number of participants.
roles::participant_joined($this->instance->get_meeting_id(), $this->instance->is_moderator());
}
/**
* Join a meeting.
*
* @param int $origin The spec
* @return string The URL to redirect to
* @throws meeting_join_exception
*/
public function join(int $origin): string {
$this->prepare_meeting_join_action($origin);
return $this->get_join_url();
}
/**
* Join a meeting as a guest.
*
* @param int $origin The spec
* @param string $userfullname Fullname for the guest user
* @return string The URL to redirect to
* @throws meeting_join_exception
*/
public function guest_join(int $origin, string $userfullname): string {
$this->prepare_meeting_join_action($origin);
return $this->get_join_url();
}
}
@@ -0,0 +1,149 @@
<?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/>.
namespace mod_bigbluebuttonbn\output;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\local\config;
use mod_bigbluebuttonbn\local\helpers\roles;
use renderable;
use renderer_base;
use stdClass;
use templatable;
/**
* Renderable for the import page.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Darko Miletic (darko.miletic [at] gmail [dt] com)
*/
class import_view implements renderable, templatable {
/**
* @var instance $destinationinstance
*/
protected $destinationinstance;
/**
* @var int|null $sourceinstanceid the source instance id or null if it is not yet set.
*/
protected $sourceinstanceid;
/**
* @var int|null $sourcecourseid the source instance id or null if it is not yet set.
*/
protected $sourcecourseid;
/**
* import_view constructor.
*
* @param instance $destinationinstance
* @param int $sourcecourseid
* @param int $sourceinstanceid
*/
public function __construct(instance $destinationinstance, int $sourcecourseid, int $sourceinstanceid) {
$this->destinationinstance = $destinationinstance;
$this->sourcecourseid = $sourcecourseid >= 0 ? $sourcecourseid : null;
$this->sourceinstanceid = $sourceinstanceid >= 0 ? $sourceinstanceid : null;
}
/**
* Defer to template.
*
* @param renderer_base $output
* @return stdClass
*/
public function export_for_template(renderer_base $output): stdClass {
$courses = roles::import_get_courses_for_select($this->destinationinstance);
if (config::get('importrecordings_from_deleted_enabled')) {
$courses[0] = get_string('recordings_from_deleted_activities', 'mod_bigbluebuttonbn');
ksort($courses);
}
$context = (object) [
'bbbid' => $this->destinationinstance->get_instance_id(),
'has_recordings' => true,
'bbbsourceid' => 0
];
if (!empty($this->sourceinstanceid)) {
$context->sourceid = $this->sourceinstanceid;
$context->search = [
'value' => ''
];
$sourceinstance = instance::get_from_instanceid($this->sourceinstanceid);
if ($sourceinstance->is_type_room_only()) {
$context->has_recordings = false;
}
$context->bbbsourceid = $sourceinstance->get_instance_id();
}
// Now the selects.
if (!empty($this->sourcecourseid)) {
$selectrecords = [];
$cms = get_fast_modinfo($this->sourcecourseid)->instances['bigbluebuttonbn'];
foreach ($cms as $cm) {
if ($cm->id == $this->destinationinstance->get_cm_id()) {
// Skip the target instance.
continue;
}
if ($cm->deletioninprogress) {
// Check if the BBB is not currently scheduled for deletion.
continue;
}
$selectrecords[$cm->instance] = $cm->name;
}
if (config::get('importrecordings_from_deleted_enabled')) {
$selectrecords[0] =
get_string('recordings_from_deleted_activities', 'mod_bigbluebuttonbn');
}
$actionurl = $this->destinationinstance->get_import_url();
$actionurl->param('sourcecourseid', $this->sourcecourseid);
$select = new \single_select(
$actionurl,
'sourcebn',
$selectrecords,
$this->sourceinstanceid ?? ""
);
$context->bbb_select = $select->export_for_template($output);
}
$context->sourcecourseid = $this->sourcecourseid ?? 0;
// Course selector.
$context->course_select = (new \single_select(
$this->destinationinstance->get_import_url(),
'sourcecourseid',
$courses,
$this->sourcecourseid ?? ""
))->export_for_template($output);
if (!is_null($this->sourcecourseid)) {
$context->has_selected_course = true;
}
// Back button.
$context->back_button = (new \single_button(
$this->destinationinstance->get_view_url(),
get_string('view_recording_button_return', 'mod_bigbluebuttonbn')
))->export_for_template($output);
return $context;
}
}
@@ -0,0 +1,222 @@
<?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/>.
namespace mod_bigbluebuttonbn\output;
use html_table;
use html_writer;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\meeting;
use mod_bigbluebuttonbn\plugin;
use renderable;
use renderer_base;
use stdClass;
/**
* Renderer for the Index page.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class index implements renderable {
/** @var stdClass */
protected $course;
/** @var stdClass[] */
protected $instances;
/**
* Constructor for the index renderable.
*
* @param stdClass $course
* @param instance[] List of bbbbn instances
*/
public function __construct(stdClass $course, array $instances) {
$this->course = $course;
$this->instances = $instances;
}
/**
* Get the table for the index page.
*
* @param renderer_base $output
* @return html_table
*/
public function get_table(renderer_base $output): html_table {
// Print the list of instances.
$table = new html_table();
if (course_format_uses_sections($this->course->format)) {
$sectionheading = get_string('sectionname', "format_{$this->course->format}");
} else {
$sectionheading = '';
}
$table->head = [
$sectionheading,
get_string('index_heading_name', plugin::COMPONENT),
get_string('index_heading_group', plugin::COMPONENT),
get_string('index_heading_users', plugin::COMPONENT),
get_string('index_heading_viewer', plugin::COMPONENT),
get_string('index_heading_moderator', plugin::COMPONENT),
get_string('index_heading_recording', plugin::COMPONENT),
get_string('index_heading_actions', plugin::COMPONENT),
];
$table->align = ['center', 'left', 'center', 'center', 'center', 'center', 'center'];
foreach ($this->instances as $instance) {
$this->add_instance_to_table($output, $table, $instance);
}
return $table;
}
/**
* Add details of the bigbluebuttonbn instance to the table.
*
* @param renderer_base $output
* @param html_table $table
* @param instance $instance
*/
protected function add_instance_to_table(renderer_base $output, html_table $table, instance $instance): void {
$cm = $instance->get_cm();
if (!$cm->uservisible) {
return;
}
if (groups_get_activity_groupmode($cm) == 0) {
$table->data[] = $this->add_room_row_to_table($output, $instance);
} else {
// Add 'All participants' room information.
$table->data[] = $this->add_room_row_to_table($output, $instance, 0);
// Add data for the groups belonging to the bbb instance, if any.
$groups = groups_get_activity_allowed_groups($cm);
foreach ($groups as $group) {
$table->data[] = $this->add_room_row_to_table($output, $instance, $group->id);
}
}
}
/**
* Displays the general view.
*
* @param renderer_base $output
* @param instance $instance
* @param int|null $group
* @return array
*/
protected function add_room_row_to_table(renderer_base $output, instance $instance, ?int $group = null): array {
if ($group) {
$instance = instance::get_group_instance_from_instance($instance, $group);
}
$meeting = new meeting($instance);
if (course_format_uses_sections($this->course->format)) {
$sectionname = get_section_name($this->course, $instance->get_cm()->sectionnum);
} else {
$sectionname = '';
}
$viewurl = $instance->get_view_url();
if ($groupid = $instance->get_group_id()) {
$viewurl->param('group', $groupid);
}
$joinurl = html_writer::link($viewurl, format_string($instance->get_meeting_name()));
// The meeting info was returned.
if ($meeting->is_running()) {
return [
$sectionname,
$joinurl,
$instance->get_group_name(),
$this->get_room_usercount($meeting),
$this->get_room_attendee_list($meeting, 'VIEWER'),
$this->get_room_attendee_list($meeting, 'MODERATOR'),
$this->get_room_record_info($output, $instance),
$this->get_room_actions($output, $instance, $meeting),
];
}
return [$sectionname, $joinurl, $instance->get_group_name(), '', '', '', '', ''];
}
/**
* Count the number of users in the meeting.
*
* @param meeting $meeting
* @return int
*/
protected function get_room_usercount(meeting $meeting): int {
return count($meeting->get_attendees());
}
/**
* Returns attendee list.
*
* @param meeting $meeting
* @param string $role
* @return string
*/
protected function get_room_attendee_list(meeting $meeting, string $role): string {
$attendees = [];
// Iterate attendees, matching by their "role" property.
foreach ($meeting->get_attendees() as $attendee) {
if (strcmp((string) $attendee['role'], $role) === 0) {
$attendees[] = $attendee['fullName'];
}
}
return implode(', ', $attendees);
}
/**
* Returns indication of recording enabled.
*
* @param renderer_base $output
* @param instance $instance
* @return string
*/
protected function get_room_record_info(renderer_base $output, instance $instance): string {
if ($instance->is_recorded()) {
// If it has been set when meeting created, set the variable on/off.
return get_string('index_enabled', 'bigbluebuttonbn');
}
return '';
}
/**
* Returns room actions.
*
* @param renderer_base $output
* @param instance $instance
* @param meeting $meeting
* @return string
*/
protected function get_room_actions(renderer_base $output, instance $instance, meeting $meeting): string {
if ($instance->is_moderator()) {
return $output->render_from_template('mod_bigbluebuttonbn/end_session_button', (object) [
'bigbluebuttonbnid' => $instance->get_instance_id(),
'groupid' => $instance->get_group_id(),
'statusrunning' => $meeting->is_running(),
]);
}
return '';
}
}
@@ -0,0 +1,79 @@
<?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/>.
namespace mod_bigbluebuttonbn\output;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\local\helpers\roles;
use moodle_url;
use renderable;
use renderer_base;
use stdClass;
use templatable;
/**
* Renderable for the instance notification updated message
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Darko Miletic (darko.miletic [at] gmail [dt] com)
*/
class instance_updated_message implements renderable, templatable {
/** @var int The activity was created */
const TYPE_CREATED = 0;
/** @var int The activity was updated */
const TYPE_UPDATED = 1;
/**
* @var instance $instance
*/
protected $instance;
/** @var int activity type. */
protected $type;
/**
* Instance updated constructor
*
* @param instance $instance
* @param int $type
*/
public function __construct(instance $instance, int $type) {
$this->instance = $instance;
$this->type = $type;
}
/**
* Defer to template.
*
* @param renderer_base $output
* @return stdClass
*/
public function export_for_template(renderer_base $output): stdClass {
return (object) [
'is_update' => $this->type === self::TYPE_UPDATED,
'is_create' => $this->type === self::TYPE_CREATED,
'name' => $this->instance->get_meeting_name(),
'link' => $this->instance->get_view_url()->out(),
'description' => $this->instance->get_meeting_description(),
'openingtime' => $this->instance->get_instance_var('openingtime'),
'closingtime' => $this->instance->get_instance_var('closingtime'),
];
}
}
@@ -0,0 +1,227 @@
<?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/>.
/**
* Mobile output class for bigbluebuttonbn
*
* @package mod_bigbluebuttonbn
* @copyright 2018 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Jesus Federico (jesus [at] blindsidenetworks [dt] com)
*/
namespace mod_bigbluebuttonbn\output;
defined('MOODLE_INTERNAL') || die();
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\local\exceptions\bigbluebutton_exception;
use mod_bigbluebuttonbn\local\exceptions\meeting_join_exception;
use mod_bigbluebuttonbn\local\exceptions\server_not_available_exception;
use mod_bigbluebuttonbn\local\proxy\bigbluebutton_proxy;
use mod_bigbluebuttonbn\logger;
use mod_bigbluebuttonbn\meeting;
global $CFG;
require_once($CFG->dirroot . '/lib/grouplib.php');
/**
* Mobile output class for bigbluebuttonbn
*
* @package mod_bigbluebuttonbn
* @copyright 2018 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Jesus Federico (jesus [at] blindsidenetworks [dt] com)
*/
class mobile {
/**
* Returns the bigbluebuttonbn course view for the mobile app.
*
* @param mixed $args
* @return array HTML, javascript and other data.
*/
public static function mobile_course_view($args): array {
global $OUTPUT;
$args = (object) $args;
$versionname = $args->appversioncode >= 3950 ? 'latest' : 'ionic3';
$instance = instance::get_from_cmid($args->cmid);
if (!$instance) {
return self::mobile_print_error(get_string('view_error_url_missing_parameters', 'bigbluebuttonbn'));
}
$cm = $instance->get_cm();
$course = $instance->get_course();
// Check activity status.
if ($instance->before_start_time()) {
$message = get_string('view_message_conference_not_started', 'bigbluebuttonbn');
$notstarted = [
'starts_at' => '',
'ends_at' => '',
];
if (!empty($instance->get_instance_var('openingtime'))) {
$notstarted['starts_at'] = sprintf(
'%s: %s',
get_string('mod_form_field_openingtime', 'bigbluebuttonbn'),
userdate($instance->get_instance_var('openingtime'))
);
}
if (!empty($instance->get_instance_var('closingtime'))) {
$notstarted['ends_at'] = sprintf(
'%s: %s',
get_string('mod_form_field_closingtime', 'bigbluebuttonbn'),
userdate($instance->get_instance_var('closingtime'))
);
}
return self::mobile_print_notification($instance, $message, $notstarted);
}
if ($instance->has_ended()) {
$message = get_string('view_message_conference_has_ended', 'bigbluebuttonbn');
return self::mobile_print_notification($instance, $message);
}
// Check if the BBB server is working.
$serverversion = bigbluebutton_proxy::get_server_version();
if ($serverversion === null) {
return self::mobile_print_error(bigbluebutton_proxy::get_server_not_available_message($instance));
}
// Mark viewed by user (if required).
$completion = new \completion_info($course);
$completion->set_module_viewed($cm);
// Validate if the user is in a role allowed to join.
if (!$instance->can_join()) {
return self::mobile_print_error(get_string('view_nojoin', 'bigbluebuttonbn'));
}
// Note: This logic should match bbb_view.php.
// Logic of bbb_view for join to session.
if ($instance->user_must_wait_to_join()) {
// If user is not administrator nor moderator (user is student) and waiting is required.
return self::mobile_print_notification(
$instance,
get_string('view_message_conference_wait_for_moderator', 'bigbluebuttonbn')
);
}
// See if the BBB session is already in progress.
$urltojoin = '';
try {
$urltojoin = meeting::join_meeting($instance);
} catch (meeting_join_exception $e) {
return self::mobile_print_notification($instance, $e->getMessage());
} catch (server_not_available_exception $e) {
return self::mobile_print_error(bigbluebutton_proxy::get_server_not_available_message($instance));
}
// Check groups access and show message.
$msjgroup = [];
$groupmode = groups_get_activity_groupmode($instance->get_cm());
if ($groupmode != NOGROUPS) {
$msjgroup['message'] = get_string('view_mobile_message_groups_not_supported', 'bigbluebuttonbn');
}
$data = [
'bigbluebuttonbn' => $instance->get_instance_data(),
'msjgroup' => $msjgroup,
'urltojoin' => $urltojoin,
'cmid' => $cm->id,
'courseid' => $course->id,
];
// We want to show a notification when user excedded 45 seconds without click button.
$jstimecreatedmeeting = 'setTimeout(function(){
document.getElementById("bigbluebuttonbn-mobile-notifications").style.display = "block";
document.getElementById("bigbluebuttonbn-mobile-join").disabled = true;
document.getElementById("bigbluebuttonbn-mobile-meetingready").style.display = "none";
}, 45000);';
return [
'templates' => [
[
'id' => 'main',
'html' => $OUTPUT->render_from_template("mod_bigbluebuttonbn/mobile_view_page_$versionname", $data),
],
],
'javascript' => $jstimecreatedmeeting,
'otherdata' => '',
'files' => '',
];
}
/**
* Returns the view for errors.
*
* @param string $error Error to display.
* @return array HTML, javascript and otherdata
*/
protected static function mobile_print_error($error): array {
global $OUTPUT;
return [
'templates' => [
[
'id' => 'main',
'html' => $OUTPUT->render_from_template('mod_bigbluebuttonbn/mobile_view_error', [
'error' => $error,
]),
],
],
'javascript' => '',
'otherdata' => '',
'files' => '',
];
}
/**
* Returns the view for messages.
*
* @param instance $instance
* @param string $message Message to display.
* @param array $notstarted Extra messages for not started session.
* @return array HTML, javascript and otherdata
*/
protected static function mobile_print_notification(instance $instance, $message, $notstarted = []): array {
global $OUTPUT, $CFG;
$data = [
'bigbluebuttonbn' => $instance->get_instance_data(),
'cmid' => $instance->get_cm_id(),
'message' => $message,
'not_started' => $notstarted,
];
return [
'templates' => [
[
'id' => 'main',
'html' => $OUTPUT->render_from_template('mod_bigbluebuttonbn/mobile_view_notification', $data),
],
],
'javascript' => file_get_contents($CFG->dirroot . '/mod/bigbluebuttonbn/mobileapp/mobile.notification.js'),
'otherdata' => '',
'files' => ''
];
}
}
@@ -0,0 +1,60 @@
<?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/>.
namespace mod_bigbluebuttonbn\output;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\recording;
/**
* Renderer for recording name in place editable.
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent.david [at] call-learning [dt] fr)
*/
class recording_description_editable extends recording_editable {
/**
* Specific constructor with the right label/hint for this editable
*
* @param recording $rec
* @param instance $instance
*/
public function __construct(recording $rec, instance $instance) {
parent::__construct($rec, $instance,
get_string('view_recording_description_editlabel', 'mod_bigbluebuttonbn'),
get_string('view_recording_description_edithint', 'mod_bigbluebuttonbn'));
}
/**
* Get the value to display
*
* @param recording $recording a recording
* @return string
*/
public function get_recording_value(recording $recording): string {
$metadescription = $recording->get('description');
return \html_writer::span($metadescription);
}
/**
* Get the type of editable
*/
protected static function get_type() {
return 'description';
}
}
@@ -0,0 +1,137 @@
<?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/>.
namespace mod_bigbluebuttonbn\output;
use lang_string;
use mod_bigbluebuttonbn\local\bigbluebutton;
use moodle_exception;
use core\output\inplace_editable;
use mod_bigbluebuttonbn\instance;
use mod_bigbluebuttonbn\local\proxy\bigbluebutton_proxy;
use mod_bigbluebuttonbn\local\proxy\recording_proxy;
use mod_bigbluebuttonbn\recording;
use stdClass;
/**
* Renderer for recording in place editable.
*
* Generic class
*
* @package mod_bigbluebuttonbn
* @copyright 2010 onwards, Blindside Networks Inc
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @author Laurent David (laurent.david [at] call-learning [dt] fr)
*/
abstract class recording_editable extends \core\output\inplace_editable {
/** @var instance The bbb instance */
protected $instance;
/**
* Constructor.
*
* @param recording $rec
* @param instance $instance
* @param string $edithint
* @param string $editlabel
*/
public function __construct(recording $rec, instance $instance, string $edithint, string $editlabel) {
$this->instance = $instance;
$editable = $this->check_capability();
$displayvalue = format_string(
$this->get_recording_value($rec),
true,
[
'context' => $instance->get_context(),
]
);
// Hack here: the ID is the recordID and the meeting ID.
parent::__construct(
'mod_bigbluebuttonbn',
static::get_type(),
$rec->get('id'),
$editable,
$displayvalue,
$displayvalue,
$edithint,
$editlabel
);
}
/**
* Check user can access and or modify this item.
*
* @return bool
* @throws \moodle_exception
*/
protected function check_capability() {
global $USER;
if (!can_access_course($this->instance->get_course(), $USER)) {
throw new moodle_exception('noaccess', 'mod_bigbluebuttonbn');
}
return $this->instance->can_manage_recordings();
}
/**
* Get the type of editable
*/
protected static function get_type() {
return '';
}
/**
* Get the real recording value
*
* @param recording $rec
* @return mixed
*/
abstract public function get_recording_value(recording $rec): string;
/**
* Update the recording with the new value
*
* @param int $itemid
* @param mixed $value
* @return recording_editable
*/
public static function update($itemid, $value) {
$recording = recording::get_record(['id' => $itemid]);
$instance = instance::get_from_instanceid($recording->get('bigbluebuttonbnid'));
require_login($instance->get_course(), true, $instance->get_cm());
require_capability('mod/bigbluebuttonbn:managerecordings', $instance->get_context());
$recording->set(static::get_type(), $value);
$recording->update();
return new static($recording, $instance);
}
/**
* Helper function evaluates if a row for the data used by the recording table is editable.
*
* @return bool
*/
protected function row_editable() {
// Since the request to BBB are cached, it is safe to use the wrapper to check the server version.
return $this->instance->can_manage_recordings()
&& (bigbluebutton_proxy::get_server_version() >= 1.0 || $this->instance->is_blindside_network_server());
}
}

Some files were not shown because too many files have changed in this diff Show More