diff --git a/mod/bigbluebuttonbn/amd/build/guest_access_modal.min.js b/mod/bigbluebuttonbn/amd/build/guest_access_modal.min.js new file mode 100644 index 00000000000..b2766b230da --- /dev/null +++ b/mod/bigbluebuttonbn/amd/build/guest_access_modal.min.js @@ -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),modalForm=new _modalform.default({modalConfig:{title:(0,_str.get_string)("guestaccess_title","mod_bigbluebuttonbn"),large:!0},args:guestInfo,saveButtonText:(0,_str.get_string)("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.get_string)("guestaccess_invite_success","mod_bigbluebuttonbn",e.detail),{type:"success"}):(0,_toast.add)((0,_str.get_string)("guestaccess_invite_failure","mod_bigbluebuttonbn",e.detail),{type:"warning"})}),{once:!0}),event.stopPropagation()}))}})); + +//# sourceMappingURL=guest_access_modal.min.js.map \ No newline at end of file diff --git a/mod/bigbluebuttonbn/amd/build/guest_access_modal.min.js.map b/mod/bigbluebuttonbn/amd/build/guest_access_modal.min.js.map new file mode 100644 index 00000000000..59099e6ef4f --- /dev/null +++ b/mod/bigbluebuttonbn/amd/build/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 .\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 {get_string as 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\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,iCAE/CK,UAAY,IAAIC,mBAAU,CAC5BC,YAAa,CACTC,OAAO,mBAAU,oBAAqB,uBACtCC,OAAO,GAEXC,KAAMT,UACNU,gBAAgB,mBAAU,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,mBAAU,6BAA8B,sBAAuBP,EAAEK,QACtE,CACIG,KAAM,4BAKT,mBAAU,6BAA8B,sBAAuBR,EAAEK,QACtE,CACIG,KAAM,cAInB,CAACC,MAAM,IACVlB,MAAMmB"} \ No newline at end of file diff --git a/mod/bigbluebuttonbn/amd/build/modform.min.js b/mod/bigbluebuttonbn/amd/build/modform.min.js index 68c698afa67..27700a54412 100644 --- a/mod/bigbluebuttonbn/amd/build/modform.min.js +++ b/mod/bigbluebuttonbn/amd/build/modform.min.js @@ -5,6 +5,6 @@ define("mod_bigbluebuttonbn/modform",["exports","core/str","core/notification"," * @module mod_bigbluebuttonbn/mod_form * @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.init=void 0,_notification=_interopRequireDefault(_notification),_templates=_interopRequireDefault(_templates);const ELEMENT_SELECTOR_instanceTypeSelection=()=>document.querySelector("select#id_type"),ELEMENT_SELECTOR_instanceTypeProfiles=()=>document.querySelector("[data-profile-types]"),ELEMENT_SELECTOR_participantData=()=>document.querySelector("[data-participant-data]"),ELEMENT_SELECTOR_participantList=()=>document.getElementsByName("participants")[0],ELEMENT_SELECTOR_participantTable=()=>document.getElementById("participant_list_table"),ELEMENT_SELECTOR_participantSelectionType=()=>document.getElementsByName("bigbluebuttonbn_participant_selection_type")[0],ELEMENT_SELECTOR_participantSelection=()=>document.getElementsByName("bigbluebuttonbn_participant_selection")[0],ELEMENT_SELECTOR_participantAddButton=()=>document.getElementsByName("bigbluebuttonbn_participant_selection_add")[0];_exports.init=info=>{const selectedType=ELEMENT_SELECTOR_instanceTypeSelection(),instanceTypeProfiles=JSON.parse(ELEMENT_SELECTOR_instanceTypeProfiles().dataset.profileTypes);let profileType=info.instanceTypeDefault;null!==selectedType&&-1!==selectedType.selectedIndex&&(profileType=selectedType.options[selectedType.selectedIndex].value);const isFeatureEnabled=(profileType,feature)=>-1!==instanceTypeProfiles[profileType].features.indexOf(feature);applyInstanceTypeProfile(profileType,isFeatureEnabled),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()};const applyInstanceTypeProfile=(profileType,isFeatureEnabled)=>{let showAll=isFeatureEnabled(profileType,"all");const showFieldset=(id,show)=>{const node=document.querySelector("#"+id);node&&(node.style.display=show?"block":"none")},showInput=(id,show)=>{const node=document.querySelector("#"+id);if(node){var ancestor=node.closest("div").closest("div");ancestor.style.display=show?"block":"none"}},showFormGroup=(id,show)=>{const node=document.querySelector("#fgroup_id_"+id);node&&(show?node.classList.remove("hidden"):node.classList.add("hidden"))};showFieldset("id_room",showAll||isFeatureEnabled(profileType,"showroom")),showInput("id_record",showAll||isFeatureEnabled(profileType,"showroom")),showFieldset("id_recordings",showAll||isFeatureEnabled(profileType,"showrecordings")),showInput("id_recordings_imported",showAll||isFeatureEnabled(profileType,"showrecordings")),showFieldset("id_lock",showAll||isFeatureEnabled(profileType,"lock")),showFieldset("id_preuploadpresentation",showAll||isFeatureEnabled(profileType,"preuploadpresentation")),showFieldset("id_permissions",showAll||isFeatureEnabled(profileType,"permissions")),showFieldset("id_schedule",showAll||isFeatureEnabled(profileType,"schedule")),showFieldset("id_modstandardelshdr",showAll||isFeatureEnabled(profileType,"modstandardelshdr")),showFieldset("id_availabilityconditionsheader",showAll||isFeatureEnabled(profileType,"availabilityconditionsheader")),showFieldset("id_tagshdr",showAll||isFeatureEnabled(profileType,"tagshdr")),showFieldset("id_competenciessection",showAll||isFeatureEnabled(profileType,"competenciessection")),showFormGroup("completionattendancegroup",showAll||isFeatureEnabled(profileType,"completionattendance")),showFormGroup("completionengagementgroup",showAll||isFeatureEnabled(profileType,"completionengagement"))},participantListInit=()=>{const participantData=JSON.parse(ELEMENT_SELECTOR_participantData().dataset.participantData);getParticipantList().forEach((participant=>{const selectionTypeValue=participant.selectiontype,selectionValue=participant.selectionid,selectionRole=participant.role;"all"!==participant.selectiontype&&void 0===participantData[participant.selectiontype].children[participant.selectionid]||participantAddToForm(selectionTypeValue,selectionValue,selectionRole,!0).then()}))},participantAddToForm=async(selectionTypeValue,selectionValue,selectedRole,canRemove)=>{const participantData=JSON.parse(ELEMENT_SELECTOR_participantData().dataset.participantData);let roles={viewer:{id:"viewer",label:await(0,_str.get_string)("mod_form_field_participant_bbb_role_viewer","mod_bigbluebuttonbn")},moderator:{id:"moderator",label:await(0,_str.get_string)("mod_form_field_participant_bbb_role_moderator","mod_bigbluebuttonbn")}};roles[selectedRole].isselected=!0;try{const listTable=document.querySelector("#participant_list_table tbody"),templateContext={selectiontypevalue:selectionTypeValue,selectionvalue:selectionValue,participanttype:participantData[selectionTypeValue].name,participantvalue:"all"!==selectionTypeValue?participantData[selectionTypeValue].children[selectionValue].name:null,roles:Object.values(roles),canRemove:canRemove},{html:html,js:js}=await _templates.default.renderForPromise("mod_bigbluebuttonbn/participant_form_add",templateContext),newNode=_templates.default.appendNodeContents(listTable,html,js)[0];newNode.querySelector(".participant-select").addEventListener("change",(()=>{participantListRoleUpdate(selectionTypeValue,selectionValue)}));const removeNode=newNode.querySelector(".remove-button");removeNode&&removeNode.addEventListener("click",(()=>{participantRemove(selectionTypeValue,selectionValue)}))}catch(e){_notification.default.exception(e)}},participantListUpdate=list=>{ELEMENT_SELECTOR_participantList().value=JSON.stringify(list)},getParticipantList=()=>{const participantListValue=ELEMENT_SELECTOR_participantList().value;return participantListValue?JSON.parse(participantListValue):[]},participantRemove=(selectionTypeValue,selectionValue)=>{const pList=getParticipantList(),id="participant_list_tr_"+selectionTypeValue+"-"+selectionValue,participantListTable=ELEMENT_SELECTOR_participantTable(),selectionid=""===selectionValue?null:selectionValue;for(let i=0;i{const participantListRoleSelection=document.querySelector("#participant_list_tr_".concat(type,"-").concat(id," .participant-select")),pList=getParticipantList();for(var i=0;i{let selectionType=ELEMENT_SELECTOR_participantSelectionType(),selection=ELEMENT_SELECTOR_participantSelection();const pList=getParticipantList();for(var i=0;i{const createNewOption=(selectItem,label,value)=>{const option=document.createElement("option");option.text=label,option.value=value,selectItem.add(option)},participantData=JSON.parse(ELEMENT_SELECTOR_participantData().dataset.participantData),participantSelect=ELEMENT_SELECTOR_participantSelection();for(;participantSelect.firstChild;)participantSelect.removeChild(participantSelect.firstChild);if(-1!==currentTypeSelect.selectedIndex){Object.values(participantData[currentTypeSelect.value].children).forEach((option=>{createNewOption(participantSelect,option.name,option.id)})),"all"===currentTypeSelect.value?(createNewOption(participantSelect,"---------------","all"),participantSelect.disabled=!0):participantSelect.disabled=!1}}})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_notification=_interopRequireDefault(_notification),_templates=_interopRequireDefault(_templates);const ELEMENT_SELECTOR_instanceTypeSelection=()=>document.querySelector("select#id_type"),ELEMENT_SELECTOR_instanceTypeProfiles=()=>document.querySelector("[data-profile-types]"),ELEMENT_SELECTOR_participantData=()=>document.querySelector("[data-participant-data]"),ELEMENT_SELECTOR_participantList=()=>document.getElementsByName("participants")[0],ELEMENT_SELECTOR_participantTable=()=>document.getElementById("participant_list_table"),ELEMENT_SELECTOR_participantSelectionType=()=>document.getElementsByName("bigbluebuttonbn_participant_selection_type")[0],ELEMENT_SELECTOR_participantSelection=()=>document.getElementsByName("bigbluebuttonbn_participant_selection")[0],ELEMENT_SELECTOR_participantAddButton=()=>document.getElementsByName("bigbluebuttonbn_participant_selection_add")[0];_exports.init=info=>{const selectedType=ELEMENT_SELECTOR_instanceTypeSelection(),instanceTypeProfiles=JSON.parse(ELEMENT_SELECTOR_instanceTypeProfiles().dataset.profileTypes);let profileType=info.instanceTypeDefault;null!==selectedType&&-1!==selectedType.selectedIndex&&(profileType=selectedType.options[selectedType.selectedIndex].value);const isFeatureEnabled=(profileType,feature)=>-1!==instanceTypeProfiles[profileType].features.indexOf(feature);applyInstanceTypeProfile(profileType,isFeatureEnabled),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()};const applyInstanceTypeProfile=(profileType,isFeatureEnabled)=>{let showAll=isFeatureEnabled(profileType,"all");const showFieldset=(id,show)=>{const node=document.querySelector("#"+id);node&&(node.style.display=show?"block":"none")},showInput=(id,show)=>{const node=document.querySelector("#"+id);if(node){var ancestor=node.closest("div").closest("div");ancestor.style.display=show?"block":"none"}},showFormGroup=(id,show)=>{const node=document.querySelector("#fgroup_id_"+id);node&&(show?node.classList.remove("hidden"):node.classList.add("hidden"))};showFieldset("id_room",showAll||isFeatureEnabled(profileType,"showroom")),showInput("id_record",showAll||isFeatureEnabled(profileType,"showroom")),showFieldset("id_recordings",showAll||isFeatureEnabled(profileType,"showrecordings")),showInput("id_recordings_imported",showAll||isFeatureEnabled(profileType,"showrecordings")),showFieldset("id_lock",showAll||isFeatureEnabled(profileType,"lock")),showFieldset("id_guestaccess",showAll||isFeatureEnabled(profileType,"showroom")),showFieldset("id_preuploadpresentation",showAll||isFeatureEnabled(profileType,"preuploadpresentation")),showFieldset("id_permissions",showAll||isFeatureEnabled(profileType,"permissions")),showFieldset("id_schedule",showAll||isFeatureEnabled(profileType,"schedule")),showFieldset("id_modstandardelshdr",showAll||isFeatureEnabled(profileType,"modstandardelshdr")),showFieldset("id_availabilityconditionsheader",showAll||isFeatureEnabled(profileType,"availabilityconditionsheader")),showFieldset("id_tagshdr",showAll||isFeatureEnabled(profileType,"tagshdr")),showFieldset("id_competenciessection",showAll||isFeatureEnabled(profileType,"competenciessection")),showFormGroup("completionattendancegroup",showAll||isFeatureEnabled(profileType,"completionattendance")),showFormGroup("completionengagementgroup",showAll||isFeatureEnabled(profileType,"completionengagement"))},participantListInit=()=>{const participantData=JSON.parse(ELEMENT_SELECTOR_participantData().dataset.participantData);getParticipantList().forEach((participant=>{const selectionTypeValue=participant.selectiontype,selectionValue=participant.selectionid,selectionRole=participant.role;"all"!==participant.selectiontype&&void 0===participantData[participant.selectiontype].children[participant.selectionid]||participantAddToForm(selectionTypeValue,selectionValue,selectionRole,!0).then()}))},participantAddToForm=async(selectionTypeValue,selectionValue,selectedRole,canRemove)=>{const participantData=JSON.parse(ELEMENT_SELECTOR_participantData().dataset.participantData);let roles={viewer:{id:"viewer",label:await(0,_str.get_string)("mod_form_field_participant_bbb_role_viewer","mod_bigbluebuttonbn")},moderator:{id:"moderator",label:await(0,_str.get_string)("mod_form_field_participant_bbb_role_moderator","mod_bigbluebuttonbn")}};roles[selectedRole].isselected=!0;try{const listTable=document.querySelector("#participant_list_table tbody"),templateContext={selectiontypevalue:selectionTypeValue,selectionvalue:selectionValue,participanttype:participantData[selectionTypeValue].name,participantvalue:"all"!==selectionTypeValue?participantData[selectionTypeValue].children[selectionValue].name:null,roles:Object.values(roles),canRemove:canRemove},{html:html,js:js}=await _templates.default.renderForPromise("mod_bigbluebuttonbn/participant_form_add",templateContext),newNode=_templates.default.appendNodeContents(listTable,html,js)[0];newNode.querySelector(".participant-select").addEventListener("change",(()=>{participantListRoleUpdate(selectionTypeValue,selectionValue)}));const removeNode=newNode.querySelector(".remove-button");removeNode&&removeNode.addEventListener("click",(()=>{participantRemove(selectionTypeValue,selectionValue)}))}catch(e){_notification.default.exception(e)}},participantListUpdate=list=>{ELEMENT_SELECTOR_participantList().value=JSON.stringify(list)},getParticipantList=()=>{const participantListValue=ELEMENT_SELECTOR_participantList().value;return participantListValue?JSON.parse(participantListValue):[]},participantRemove=(selectionTypeValue,selectionValue)=>{const pList=getParticipantList(),id="participant_list_tr_"+selectionTypeValue+"-"+selectionValue,participantListTable=ELEMENT_SELECTOR_participantTable(),selectionid=""===selectionValue?null:selectionValue;for(let i=0;i{const participantListRoleSelection=document.querySelector("#participant_list_tr_".concat(type,"-").concat(id," .participant-select")),pList=getParticipantList();for(var i=0;i{let selectionType=ELEMENT_SELECTOR_participantSelectionType(),selection=ELEMENT_SELECTOR_participantSelection();const pList=getParticipantList();for(var i=0;i{const createNewOption=(selectItem,label,value)=>{const option=document.createElement("option");option.text=label,option.value=value,selectItem.add(option)},participantData=JSON.parse(ELEMENT_SELECTOR_participantData().dataset.participantData),participantSelect=ELEMENT_SELECTOR_participantSelection();for(;participantSelect.firstChild;)participantSelect.removeChild(participantSelect.firstChild);if(-1!==currentTypeSelect.selectedIndex){Object.values(participantData[currentTypeSelect.value].children).forEach((option=>{createNewOption(participantSelect,option.name,option.id)})),"all"===currentTypeSelect.value?(createNewOption(participantSelect,"---------------","all"),participantSelect.disabled=!0):participantSelect.disabled=!1}}})); //# sourceMappingURL=modform.min.js.map \ No newline at end of file diff --git a/mod/bigbluebuttonbn/amd/build/modform.min.js.map b/mod/bigbluebuttonbn/amd/build/modform.min.js.map index a64fd42813c..cdd5f83f49c 100644 --- a/mod/bigbluebuttonbn/amd/build/modform.min.js.map +++ b/mod/bigbluebuttonbn/amd/build/modform.min.js.map @@ -1 +1 @@ -{"version":3,"file":"modform.min.js","sources":["../src/modform.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 .\n\n/**\n * JS for the mod_form page on mod_bigbluebuttonbn plugin.\n *\n * @module mod_bigbluebuttonbn/mod_form\n * @copyright 2021 Blindside Networks Inc\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {get_string as getString} from 'core/str';\nimport Notification from 'core/notification';\nimport Templates from \"core/templates\";\n\n/**\n * Get all selectors in one place.\n *\n */\nconst ELEMENT_SELECTOR = {\n instanceTypeSelection: () => document.querySelector('select#id_type'),\n instanceTypeProfiles: () => document.querySelector('[data-profile-types]'),\n participantData: () => document.querySelector('[data-participant-data]'),\n participantList: () => document.getElementsByName('participants')[0],\n participantTable: () => document.getElementById('participant_list_table'),\n participantSelectionType: () => document.getElementsByName('bigbluebuttonbn_participant_selection_type')[0],\n participantSelection: () => document.getElementsByName('bigbluebuttonbn_participant_selection')[0],\n participantAddButton: () => document.getElementsByName('bigbluebuttonbn_participant_selection_add')[0],\n};\n/**\n * Initialise the moodle form code.\n *\n * This will help hide or show items depending on the selection of the instance type.\n *\n * @method init\n * @param {object} info\n */\nexport const init = (info) => {\n const selectedType = ELEMENT_SELECTOR.instanceTypeSelection();\n const instanceTypeProfiles = JSON.parse(ELEMENT_SELECTOR.instanceTypeProfiles().dataset.profileTypes);\n\n let profileType = info.instanceTypeDefault;\n if (selectedType !== null && selectedType.selectedIndex !== -1) {\n profileType = selectedType.options[selectedType.selectedIndex].value;\n }\n\n const isFeatureEnabled = (profileType, feature) => {\n const features = instanceTypeProfiles[profileType].features;\n return (features.indexOf(feature) !== -1);\n };\n applyInstanceTypeProfile(profileType, isFeatureEnabled);\n\n // Change form visible fields depending on the selection.\n selectedType.addEventListener('change', (e) => {\n applyInstanceTypeProfile(e.target.value, isFeatureEnabled);\n });\n\n ELEMENT_SELECTOR.participantSelectionType().addEventListener('change', (e) => {\n const currentTypeSelect = e.target;\n updateSelectionFromType(currentTypeSelect);\n });\n\n ELEMENT_SELECTOR.participantAddButton().addEventListener('click', (e) => {\n e.stopPropagation();\n e.preventDefault();\n participantAddFromCurrentSelection();\n });\n\n participantListInit();\n};\n\n/**\n * Show or hide form element depending on the selected profile\n *\n * @param {string} profileType\n * @param {function} isFeatureEnabled\n */\nconst applyInstanceTypeProfile = (profileType, isFeatureEnabled) => {\n let showAll = isFeatureEnabled(profileType, 'all');\n const showFieldset = (id, show) => {\n // Show room settings validation.\n const node = document.querySelector('#' + id);\n if (!node) {\n return;\n }\n if (show) {\n node.style.display = 'block';\n return;\n }\n node.style.display = 'none';\n };\n const showInput = (id, show) => {\n // Show room settings validation.\n const node = document.querySelector('#' + id);\n if (!node) {\n return;\n }\n var ancestor = node.closest('div').closest('div');\n if (show) {\n ancestor.style.display = 'block';\n return;\n }\n ancestor.style.display = 'none';\n };\n const showFormGroup = (id, show) => {\n // Show room settings validation.\n const node = document.querySelector('#fgroup_id_' + id);\n if (!node) {\n return;\n }\n if (show) {\n node.classList.remove('hidden');\n return;\n }\n node.classList.add('hidden');\n };\n // Show room settings validation.\n showFieldset('id_room', showAll ||\n isFeatureEnabled(profileType, 'showroom'));\n showInput('id_record', showAll ||\n isFeatureEnabled(profileType, 'showroom'));\n // Show recordings settings validation.\n showFieldset('id_recordings', showAll ||\n isFeatureEnabled(profileType, 'showrecordings'));\n // Show recordings imported settings validation.\n showInput('id_recordings_imported', showAll ||\n isFeatureEnabled(profileType, 'showrecordings'));\n // Show lock settings validation.\n showFieldset('id_lock', showAll ||\n isFeatureEnabled(profileType, 'lock'));\n // Preuploadpresentation feature validation.\n showFieldset('id_preuploadpresentation', showAll ||\n isFeatureEnabled(profileType, 'preuploadpresentation'));\n // Participants feature validation.\n showFieldset('id_permissions', showAll ||\n isFeatureEnabled(profileType, 'permissions'));\n // Schedule feature validation.\n showFieldset('id_schedule', showAll ||\n isFeatureEnabled(profileType, 'schedule'));\n // Common module settings validation.\n showFieldset('id_modstandardelshdr', showAll ||\n isFeatureEnabled(profileType, 'modstandardelshdr'));\n // Restrict access validation.\n showFieldset('id_availabilityconditionsheader', showAll ||\n isFeatureEnabled(profileType, 'availabilityconditionsheader'));\n // Tags validation.\n showFieldset('id_tagshdr', showAll || isFeatureEnabled(profileType, 'tagshdr'));\n // Competencies validation.\n showFieldset('id_competenciessection', showAll ||\n isFeatureEnabled(profileType, 'competenciessection'));\n // Completion validation.\n showFormGroup('completionattendancegroup', showAll ||\n isFeatureEnabled(profileType, 'completionattendance'));\n // Completion validation.\n showFormGroup('completionengagementgroup', showAll ||\n isFeatureEnabled(profileType, 'completionengagement'));\n};\n\n/**\n * Init the participant list\n */\nconst participantListInit = () => {\n const participantData = JSON.parse(ELEMENT_SELECTOR.participantData().dataset.participantData);\n const participantList = getParticipantList();\n participantList.forEach(participant => {\n const selectionTypeValue = participant.selectiontype;\n const selectionValue = participant.selectionid;\n const selectionRole = participant.role;\n if (participant.selectiontype === 'all' ||\n typeof participantData[participant.selectiontype].children[participant.selectionid] !== 'undefined') {\n // Add it to the form, but don't add the delete button if it is the first item.\n participantAddToForm(selectionTypeValue, selectionValue, selectionRole, true).then();\n }\n });\n};\n\n/**\n * Add rows to the participant list depending on the current selection.\n *\n * @param {string} selectionTypeValue\n * @param {string} selectionValue\n * @param {string} selectedRole\n * @param {boolean} canRemove\n * @returns {Promise}\n */\nconst participantAddToForm = async(selectionTypeValue, selectionValue, selectedRole, canRemove) => {\n const participantData = JSON.parse(ELEMENT_SELECTOR.participantData().dataset.participantData);\n const sviewer = await getString('mod_form_field_participant_bbb_role_viewer', 'mod_bigbluebuttonbn');\n const smoderator = await getString('mod_form_field_participant_bbb_role_moderator', 'mod_bigbluebuttonbn');\n let roles = {\n viewer: {'id': 'viewer', label: sviewer},\n moderator: {'id': 'moderator', label: smoderator}\n };\n roles[selectedRole].isselected = true;\n try {\n const listTable = document.querySelector('#participant_list_table tbody');\n const templateContext = {\n 'selectiontypevalue': selectionTypeValue,\n 'selectionvalue': selectionValue,\n 'participanttype': participantData[selectionTypeValue].name,\n 'participantvalue':\n (selectionTypeValue !== 'all') ?\n participantData[selectionTypeValue].children[selectionValue].name : null,\n 'roles': Object.values(roles),\n 'canRemove': canRemove\n };\n const {html, js} = await Templates.renderForPromise('mod_bigbluebuttonbn/participant_form_add', templateContext);\n const newNode = Templates.appendNodeContents(listTable, html, js)[0];\n newNode.querySelector('.participant-select').addEventListener('change', () => {\n participantListRoleUpdate(selectionTypeValue, selectionValue);\n });\n // Now add the callbacks: participantListRoleUpdate() and participantRemove().\n const removeNode = newNode.querySelector('.remove-button');\n if (removeNode) {\n removeNode\n .addEventListener('click', () => {\n participantRemove(selectionTypeValue, selectionValue);\n });\n }\n\n } catch (e) {\n Notification.exception(e);\n }\n};\n/*\n\n */\n\n/**\n * Update the related form element with the list value.\n *\n * @param {object} list\n */\nconst participantListUpdate = (list) => {\n const participantList = ELEMENT_SELECTOR.participantList();\n participantList.value = JSON.stringify(list);\n};\n\n/**\n *\n * @returns {any}\n */\nconst getParticipantList = () => {\n const participantListValue = ELEMENT_SELECTOR.participantList().value;\n if (participantListValue) {\n return JSON.parse(participantListValue);\n }\n return [];\n};\n\n/**\n * Remove participant both in the table/form and in the form element.\n *\n * @param {string} selectionTypeValue\n * @param {string} selectionValue\n */\nconst participantRemove = (selectionTypeValue, selectionValue) => {\n const pList = getParticipantList();\n const id = 'participant_list_tr_' + selectionTypeValue + '-' + selectionValue;\n const participantListTable = ELEMENT_SELECTOR.participantTable();\n const selectionid = (selectionValue === '' ? null : selectionValue);\n for (let i = 0; i < pList.length; i++) {\n if (pList[i].selectiontype === selectionTypeValue &&\n pList[i].selectionid === selectionid) {\n pList.splice(i, 1);\n }\n }\n // Remove from the form.\n for (let i = 0; i < participantListTable.rows.length; i++) {\n if (participantListTable.rows[i].id === id) {\n participantListTable.deleteRow(i);\n }\n }\n // Update value in the form.\n participantListUpdate(pList);\n};\n\n/**\n * Role update\n *\n * @param {string} type\n * @param {string} id\n */\nconst participantListRoleUpdate = (type, id) => {\n // Update in memory.\n const participantListRoleSelection = document.querySelector(`#participant_list_tr_${type}-${id} .participant-select`);\n const pList = getParticipantList();\n\n for (var i = 0; i < pList.length; i++) {\n if (pList[i].selectiontype === type && pList[i].selectionid === id) {\n pList[i].role = participantListRoleSelection.value;\n }\n }\n // Update in the form.\n participantListUpdate(pList);\n};\n\n/**\n * Add participant from the currently selected options\n */\nconst participantAddFromCurrentSelection = () => {\n let selectionType = ELEMENT_SELECTOR.participantSelectionType();\n let selection = ELEMENT_SELECTOR.participantSelection();\n const pList = getParticipantList();\n // Lookup to see if it has been added already.\n for (var i = 0; i < pList.length; i++) {\n if (pList[i].selectiontype === selectionType.value &&\n pList[i].selectionid === selection.value) {\n return;\n }\n }\n pList.push({\n \"selectiontype\": selectionType.value,\n \"selectionid\": selection.value,\n \"role\": \"viewer\"\n });\n // Add it to the form.\n participantAddToForm(selectionType.value, selection.value, 'viewer', true).then();\n // Update in the form.\n participantListUpdate(pList);\n};\n\n/**\n * Update selectable options when changing types\n *\n * @param {HTMLNode} currentTypeSelect\n */\nconst updateSelectionFromType = (currentTypeSelect) => {\n const createNewOption = (selectItem, label, value) => {\n const option = document.createElement('option');\n option.text = label;\n option.value = value;\n\n selectItem.add(option);\n };\n\n const participantData = JSON.parse(ELEMENT_SELECTOR.participantData().dataset.participantData);\n // Clear all selection items.\n const participantSelect = ELEMENT_SELECTOR.participantSelection();\n while (participantSelect.firstChild) {\n participantSelect.removeChild(participantSelect.firstChild);\n }\n // Add options depending on the selection.\n if (currentTypeSelect.selectedIndex !== -1) {\n const options = Object.values(participantData[currentTypeSelect.value].children);\n options.forEach(option => {\n createNewOption(participantSelect, option.name, option.id);\n });\n\n if (currentTypeSelect.value === 'all') {\n createNewOption(participantSelect, '---------------', 'all');\n participantSelect.disabled = true;\n } else {\n participantSelect.disabled = false;\n }\n }\n};\n"],"names":["ELEMENT_SELECTOR","document","querySelector","getElementsByName","getElementById","info","selectedType","instanceTypeProfiles","JSON","parse","dataset","profileTypes","profileType","instanceTypeDefault","selectedIndex","options","value","isFeatureEnabled","feature","features","indexOf","applyInstanceTypeProfile","addEventListener","e","target","currentTypeSelect","updateSelectionFromType","stopPropagation","preventDefault","participantAddFromCurrentSelection","participantListInit","showAll","showFieldset","id","show","node","style","display","showInput","ancestor","closest","showFormGroup","classList","remove","add","participantData","getParticipantList","forEach","participant","selectionTypeValue","selectiontype","selectionValue","selectionid","selectionRole","role","children","participantAddToForm","then","async","selectedRole","canRemove","roles","viewer","label","moderator","isselected","listTable","templateContext","name","Object","values","html","js","Templates","renderForPromise","newNode","appendNodeContents","participantListRoleUpdate","removeNode","participantRemove","exception","participantListUpdate","list","stringify","participantListValue","pList","participantListTable","i","length","splice","rows","deleteRow","type","participantListRoleSelection","selectionType","selection","push","createNewOption","selectItem","option","createElement","text","participantSelect","firstChild","removeChild","disabled"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"modform.min.js","sources":["../src/modform.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 .\n\n/**\n * JS for the mod_form page on mod_bigbluebuttonbn plugin.\n *\n * @module mod_bigbluebuttonbn/mod_form\n * @copyright 2021 Blindside Networks Inc\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport {get_string as getString} from 'core/str';\nimport Notification from 'core/notification';\nimport Templates from \"core/templates\";\n\n/**\n * Get all selectors in one place.\n *\n */\nconst ELEMENT_SELECTOR = {\n instanceTypeSelection: () => document.querySelector('select#id_type'),\n instanceTypeProfiles: () => document.querySelector('[data-profile-types]'),\n participantData: () => document.querySelector('[data-participant-data]'),\n participantList: () => document.getElementsByName('participants')[0],\n participantTable: () => document.getElementById('participant_list_table'),\n participantSelectionType: () => document.getElementsByName('bigbluebuttonbn_participant_selection_type')[0],\n participantSelection: () => document.getElementsByName('bigbluebuttonbn_participant_selection')[0],\n participantAddButton: () => document.getElementsByName('bigbluebuttonbn_participant_selection_add')[0],\n};\n/**\n * Initialise the moodle form code.\n *\n * This will help hide or show items depending on the selection of the instance type.\n *\n * @method init\n * @param {object} info\n */\nexport const init = (info) => {\n const selectedType = ELEMENT_SELECTOR.instanceTypeSelection();\n const instanceTypeProfiles = JSON.parse(ELEMENT_SELECTOR.instanceTypeProfiles().dataset.profileTypes);\n\n let profileType = info.instanceTypeDefault;\n if (selectedType !== null && selectedType.selectedIndex !== -1) {\n profileType = selectedType.options[selectedType.selectedIndex].value;\n }\n\n const isFeatureEnabled = (profileType, feature) => {\n const features = instanceTypeProfiles[profileType].features;\n return (features.indexOf(feature) !== -1);\n };\n applyInstanceTypeProfile(profileType, isFeatureEnabled);\n\n // Change form visible fields depending on the selection.\n selectedType.addEventListener('change', (e) => {\n applyInstanceTypeProfile(e.target.value, isFeatureEnabled);\n });\n\n ELEMENT_SELECTOR.participantSelectionType().addEventListener('change', (e) => {\n const currentTypeSelect = e.target;\n updateSelectionFromType(currentTypeSelect);\n });\n\n ELEMENT_SELECTOR.participantAddButton().addEventListener('click', (e) => {\n e.stopPropagation();\n e.preventDefault();\n participantAddFromCurrentSelection();\n });\n\n participantListInit();\n};\n\n/**\n * Show or hide form element depending on the selected profile\n *\n * @param {string} profileType\n * @param {function} isFeatureEnabled\n */\nconst applyInstanceTypeProfile = (profileType, isFeatureEnabled) => {\n let showAll = isFeatureEnabled(profileType, 'all');\n const showFieldset = (id, show) => {\n // Show room settings validation.\n const node = document.querySelector('#' + id);\n if (!node) {\n return;\n }\n if (show) {\n node.style.display = 'block';\n return;\n }\n node.style.display = 'none';\n };\n const showInput = (id, show) => {\n // Show room settings validation.\n const node = document.querySelector('#' + id);\n if (!node) {\n return;\n }\n var ancestor = node.closest('div').closest('div');\n if (show) {\n ancestor.style.display = 'block';\n return;\n }\n ancestor.style.display = 'none';\n };\n const showFormGroup = (id, show) => {\n // Show room settings validation.\n const node = document.querySelector('#fgroup_id_' + id);\n if (!node) {\n return;\n }\n if (show) {\n node.classList.remove('hidden');\n return;\n }\n node.classList.add('hidden');\n };\n // Show room settings validation.\n showFieldset('id_room', showAll ||\n isFeatureEnabled(profileType, 'showroom'));\n showInput('id_record', showAll ||\n isFeatureEnabled(profileType, 'showroom'));\n // Show recordings settings validation.\n showFieldset('id_recordings', showAll ||\n isFeatureEnabled(profileType, 'showrecordings'));\n // Show recordings imported settings validation.\n showInput('id_recordings_imported', showAll ||\n isFeatureEnabled(profileType, 'showrecordings'));\n // Show lock settings validation.\n showFieldset('id_lock', showAll ||\n isFeatureEnabled(profileType, 'lock'));\n // Show guest settings validation.\n showFieldset('id_guestaccess', showAll ||\n isFeatureEnabled(profileType, 'showroom'));\n // Preuploadpresentation feature validation.\n showFieldset('id_preuploadpresentation', showAll ||\n isFeatureEnabled(profileType, 'preuploadpresentation'));\n // Participants feature validation.\n showFieldset('id_permissions', showAll ||\n isFeatureEnabled(profileType, 'permissions'));\n // Schedule feature validation.\n showFieldset('id_schedule', showAll ||\n isFeatureEnabled(profileType, 'schedule'));\n // Common module settings validation.\n showFieldset('id_modstandardelshdr', showAll ||\n isFeatureEnabled(profileType, 'modstandardelshdr'));\n // Restrict access validation.\n showFieldset('id_availabilityconditionsheader', showAll ||\n isFeatureEnabled(profileType, 'availabilityconditionsheader'));\n // Tags validation.\n showFieldset('id_tagshdr', showAll || isFeatureEnabled(profileType, 'tagshdr'));\n // Competencies validation.\n showFieldset('id_competenciessection', showAll ||\n isFeatureEnabled(profileType, 'competenciessection'));\n // Completion validation.\n showFormGroup('completionattendancegroup', showAll ||\n isFeatureEnabled(profileType, 'completionattendance'));\n // Completion validation.\n showFormGroup('completionengagementgroup', showAll ||\n isFeatureEnabled(profileType, 'completionengagement'));\n};\n\n/**\n * Init the participant list\n */\nconst participantListInit = () => {\n const participantData = JSON.parse(ELEMENT_SELECTOR.participantData().dataset.participantData);\n const participantList = getParticipantList();\n participantList.forEach(participant => {\n const selectionTypeValue = participant.selectiontype;\n const selectionValue = participant.selectionid;\n const selectionRole = participant.role;\n if (participant.selectiontype === 'all' ||\n typeof participantData[participant.selectiontype].children[participant.selectionid] !== 'undefined') {\n // Add it to the form, but don't add the delete button if it is the first item.\n participantAddToForm(selectionTypeValue, selectionValue, selectionRole, true).then();\n }\n });\n};\n\n/**\n * Add rows to the participant list depending on the current selection.\n *\n * @param {string} selectionTypeValue\n * @param {string} selectionValue\n * @param {string} selectedRole\n * @param {boolean} canRemove\n * @returns {Promise}\n */\nconst participantAddToForm = async(selectionTypeValue, selectionValue, selectedRole, canRemove) => {\n const participantData = JSON.parse(ELEMENT_SELECTOR.participantData().dataset.participantData);\n const sviewer = await getString('mod_form_field_participant_bbb_role_viewer', 'mod_bigbluebuttonbn');\n const smoderator = await getString('mod_form_field_participant_bbb_role_moderator', 'mod_bigbluebuttonbn');\n let roles = {\n viewer: {'id': 'viewer', label: sviewer},\n moderator: {'id': 'moderator', label: smoderator}\n };\n roles[selectedRole].isselected = true;\n try {\n const listTable = document.querySelector('#participant_list_table tbody');\n const templateContext = {\n 'selectiontypevalue': selectionTypeValue,\n 'selectionvalue': selectionValue,\n 'participanttype': participantData[selectionTypeValue].name,\n 'participantvalue':\n (selectionTypeValue !== 'all') ?\n participantData[selectionTypeValue].children[selectionValue].name : null,\n 'roles': Object.values(roles),\n 'canRemove': canRemove\n };\n const {html, js} = await Templates.renderForPromise('mod_bigbluebuttonbn/participant_form_add', templateContext);\n const newNode = Templates.appendNodeContents(listTable, html, js)[0];\n newNode.querySelector('.participant-select').addEventListener('change', () => {\n participantListRoleUpdate(selectionTypeValue, selectionValue);\n });\n // Now add the callbacks: participantListRoleUpdate() and participantRemove().\n const removeNode = newNode.querySelector('.remove-button');\n if (removeNode) {\n removeNode\n .addEventListener('click', () => {\n participantRemove(selectionTypeValue, selectionValue);\n });\n }\n\n } catch (e) {\n Notification.exception(e);\n }\n};\n/*\n\n */\n\n/**\n * Update the related form element with the list value.\n *\n * @param {object} list\n */\nconst participantListUpdate = (list) => {\n const participantList = ELEMENT_SELECTOR.participantList();\n participantList.value = JSON.stringify(list);\n};\n\n/**\n *\n * @returns {any}\n */\nconst getParticipantList = () => {\n const participantListValue = ELEMENT_SELECTOR.participantList().value;\n if (participantListValue) {\n return JSON.parse(participantListValue);\n }\n return [];\n};\n\n/**\n * Remove participant both in the table/form and in the form element.\n *\n * @param {string} selectionTypeValue\n * @param {string} selectionValue\n */\nconst participantRemove = (selectionTypeValue, selectionValue) => {\n const pList = getParticipantList();\n const id = 'participant_list_tr_' + selectionTypeValue + '-' + selectionValue;\n const participantListTable = ELEMENT_SELECTOR.participantTable();\n const selectionid = (selectionValue === '' ? null : selectionValue);\n for (let i = 0; i < pList.length; i++) {\n if (pList[i].selectiontype === selectionTypeValue &&\n pList[i].selectionid === selectionid) {\n pList.splice(i, 1);\n }\n }\n // Remove from the form.\n for (let i = 0; i < participantListTable.rows.length; i++) {\n if (participantListTable.rows[i].id === id) {\n participantListTable.deleteRow(i);\n }\n }\n // Update value in the form.\n participantListUpdate(pList);\n};\n\n/**\n * Role update\n *\n * @param {string} type\n * @param {string} id\n */\nconst participantListRoleUpdate = (type, id) => {\n // Update in memory.\n const participantListRoleSelection = document.querySelector(`#participant_list_tr_${type}-${id} .participant-select`);\n const pList = getParticipantList();\n\n for (var i = 0; i < pList.length; i++) {\n if (pList[i].selectiontype === type && pList[i].selectionid === id) {\n pList[i].role = participantListRoleSelection.value;\n }\n }\n // Update in the form.\n participantListUpdate(pList);\n};\n\n/**\n * Add participant from the currently selected options\n */\nconst participantAddFromCurrentSelection = () => {\n let selectionType = ELEMENT_SELECTOR.participantSelectionType();\n let selection = ELEMENT_SELECTOR.participantSelection();\n const pList = getParticipantList();\n // Lookup to see if it has been added already.\n for (var i = 0; i < pList.length; i++) {\n if (pList[i].selectiontype === selectionType.value &&\n pList[i].selectionid === selection.value) {\n return;\n }\n }\n pList.push({\n \"selectiontype\": selectionType.value,\n \"selectionid\": selection.value,\n \"role\": \"viewer\"\n });\n // Add it to the form.\n participantAddToForm(selectionType.value, selection.value, 'viewer', true).then();\n // Update in the form.\n participantListUpdate(pList);\n};\n\n/**\n * Update selectable options when changing types\n *\n * @param {HTMLNode} currentTypeSelect\n */\nconst updateSelectionFromType = (currentTypeSelect) => {\n const createNewOption = (selectItem, label, value) => {\n const option = document.createElement('option');\n option.text = label;\n option.value = value;\n\n selectItem.add(option);\n };\n\n const participantData = JSON.parse(ELEMENT_SELECTOR.participantData().dataset.participantData);\n // Clear all selection items.\n const participantSelect = ELEMENT_SELECTOR.participantSelection();\n while (participantSelect.firstChild) {\n participantSelect.removeChild(participantSelect.firstChild);\n }\n // Add options depending on the selection.\n if (currentTypeSelect.selectedIndex !== -1) {\n const options = Object.values(participantData[currentTypeSelect.value].children);\n options.forEach(option => {\n createNewOption(participantSelect, option.name, option.id);\n });\n\n if (currentTypeSelect.value === 'all') {\n createNewOption(participantSelect, '---------------', 'all');\n participantSelect.disabled = true;\n } else {\n participantSelect.disabled = false;\n }\n }\n};\n"],"names":["ELEMENT_SELECTOR","document","querySelector","getElementsByName","getElementById","info","selectedType","instanceTypeProfiles","JSON","parse","dataset","profileTypes","profileType","instanceTypeDefault","selectedIndex","options","value","isFeatureEnabled","feature","features","indexOf","applyInstanceTypeProfile","addEventListener","e","target","currentTypeSelect","updateSelectionFromType","stopPropagation","preventDefault","participantAddFromCurrentSelection","participantListInit","showAll","showFieldset","id","show","node","style","display","showInput","ancestor","closest","showFormGroup","classList","remove","add","participantData","getParticipantList","forEach","participant","selectionTypeValue","selectiontype","selectionValue","selectionid","selectionRole","role","children","participantAddToForm","then","async","selectedRole","canRemove","roles","viewer","label","moderator","isselected","listTable","templateContext","name","Object","values","html","js","Templates","renderForPromise","newNode","appendNodeContents","participantListRoleUpdate","removeNode","participantRemove","exception","participantListUpdate","list","stringify","participantListValue","pList","participantListTable","i","length","splice","rows","deleteRow","type","participantListRoleSelection","selectionType","selection","push","createNewOption","selectItem","option","createElement","text","participantSelect","firstChild","removeChild","disabled"],"mappings":"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"} \ No newline at end of file diff --git a/mod/bigbluebuttonbn/amd/build/roomupdater.min.js b/mod/bigbluebuttonbn/amd/build/roomupdater.min.js index 4825735118e..579c5887ec7 100644 --- a/mod/bigbluebuttonbn/amd/build/roomupdater.min.js +++ b/mod/bigbluebuttonbn/amd/build/roomupdater.min.js @@ -5,6 +5,6 @@ define("mod_bigbluebuttonbn/roomupdater",["exports","core/templates","core/notif * @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,_templates=(obj=_templates)&&obj.__esModule?obj:{default:obj};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("bbb-room-view"),bbbId=bbbRoomViewElement.dataset.bbbId,groupId=bbbRoomViewElement.dataset.groupId;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.replaceNodeContents(bbbRoomViewElement,html,js)})).then((()=>!0)).catch((ex=>((0,_notification.exception)(ex),!1)))};_exports.updateRoom=updateRoom})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.updateRoom=_exports.stop=_exports.start=void 0,_templates=(obj=_templates)&&obj.__esModule?obj:{default:obj};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"),bbbId=bbbRoomViewElement.dataset.bbbId,groupId=bbbRoomViewElement.dataset.groupId;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((()=>!0)).catch((ex=>((0,_notification.exception)(ex),!1)))};_exports.updateRoom=updateRoom})); //# sourceMappingURL=roomupdater.min.js.map \ No newline at end of file diff --git a/mod/bigbluebuttonbn/amd/build/roomupdater.min.js.map b/mod/bigbluebuttonbn/amd/build/roomupdater.min.js.map index 8e4f5469e4b..f09538072c3 100644 --- a/mod/bigbluebuttonbn/amd/build/roomupdater.min.js.map +++ b/mod/bigbluebuttonbn/amd/build/roomupdater.min.js.map @@ -1 +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 .\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 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('bbb-room-view');\n const bbbId = bbbRoomViewElement.dataset.bbbId;\n const groupId = bbbRoomViewElement.dataset.groupId;\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.replaceNodeContents(bbbRoomViewElement, html, js))\n .then(() => true)\n .catch((ex) => {\n displayException(ex);\n return false;\n });\n};\n"],"names":["timerReference","timerRunning","pollInterval","pollIntervalFactor","resetValues","interval","poll","clearTimeout","updateRoom","then","updateOk","setTimeout","catch","updatecache","bbbRoomViewElement","document","getElementById","bbbId","dataset","groupId","data","haspresentations","presentations","length","Templates","renderForPromise","_ref","html","js","replaceNodeContents","ex"],"mappings":";;;;;;;uLA2BIA,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,iBAC7CC,MAAQH,mBAAmBI,QAAQD,MACnCE,QAAUL,mBAAmBI,QAAQC,eACpC,8BAAeF,MAAOE,QAASN,aACjCJ,MAAKW,OAEFA,KAAKC,oBAAsBD,KAAKE,gBAAiBF,KAAKE,cAAcC,QAC7DC,mBAAUC,iBAAiB,gCAAiCL,SAEtEX,MAAKiB,WAACC,KAACA,KAADC,GAAOA,gBAAQJ,mBAAUK,oBAAoBf,mBAAoBa,KAAMC,OAC7EnB,MAAK,KAAM,IACXG,OAAOkB,iCACaA,KACV"} \ No newline at end of file +{"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 .\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 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 const bbbId = bbbRoomViewElement.dataset.bbbId;\n const groupId = bbbRoomViewElement.dataset.groupId;\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(() => true)\n .catch((ex) => {\n displayException(ex);\n return false;\n });\n};\n"],"names":["timerReference","timerRunning","pollInterval","pollIntervalFactor","resetValues","interval","poll","clearTimeout","updateRoom","then","updateOk","setTimeout","catch","updatecache","bbbRoomViewElement","document","getElementById","bbbId","dataset","groupId","data","haspresentations","presentations","length","Templates","renderForPromise","_ref","html","js","replaceNode","ex"],"mappings":";;;;;;;uLA2BIA,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,6BAC7CC,MAAQH,mBAAmBI,QAAQD,MACnCE,QAAUL,mBAAmBI,QAAQC,eACpC,8BAAeF,MAAOE,QAASN,aACjCJ,MAAKW,OAEFA,KAAKC,oBAAsBD,KAAKE,gBAAiBF,KAAKE,cAAcC,QAC7DC,mBAAUC,iBAAiB,gCAAiCL,SAEtEX,MAAKiB,WAACC,KAACA,KAADC,GAAOA,gBAAQJ,mBAAUK,YAAYf,mBAAoBa,KAAMC,OACrEnB,MAAK,KAAM,IACXG,OAAOkB,iCACaA,KACV"} \ No newline at end of file diff --git a/mod/bigbluebuttonbn/amd/src/guest_access_modal.js b/mod/bigbluebuttonbn/amd/src/guest_access_modal.js new file mode 100644 index 00000000000..d2a1e4d0bcf --- /dev/null +++ b/mod/bigbluebuttonbn/amd/src/guest_access_modal.js @@ -0,0 +1,82 @@ +// 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 . + +/** + * 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 {get_string as 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); + + 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(); + }); +}; diff --git a/mod/bigbluebuttonbn/amd/src/modform.js b/mod/bigbluebuttonbn/amd/src/modform.js index 4b43f5f6ad0..b669f8b97a0 100644 --- a/mod/bigbluebuttonbn/amd/src/modform.js +++ b/mod/bigbluebuttonbn/amd/src/modform.js @@ -140,6 +140,9 @@ const applyInstanceTypeProfile = (profileType, isFeatureEnabled) => { // 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')); diff --git a/mod/bigbluebuttonbn/amd/src/roomupdater.js b/mod/bigbluebuttonbn/amd/src/roomupdater.js index 4792c8aba90..420f847cee6 100644 --- a/mod/bigbluebuttonbn/amd/src/roomupdater.js +++ b/mod/bigbluebuttonbn/amd/src/roomupdater.js @@ -87,7 +87,7 @@ const poll = () => { * @returns {Promise} */ export const updateRoom = (updatecache = false) => { - const bbbRoomViewElement = document.getElementById('bbb-room-view'); + const bbbRoomViewElement = document.getElementById('bigbluebuttonbn-room-view'); const bbbId = bbbRoomViewElement.dataset.bbbId; const groupId = bbbRoomViewElement.dataset.groupId; return getMeetingInfo(bbbId, groupId, updatecache) @@ -96,7 +96,7 @@ export const updateRoom = (updatecache = false) => { data.haspresentations = !!(data.presentations && data.presentations.length); return Templates.renderForPromise('mod_bigbluebuttonbn/room_view', data); }) - .then(({html, js}) => Templates.replaceNodeContents(bbbRoomViewElement, html, js)) + .then(({html, js}) => Templates.replaceNode(bbbRoomViewElement, html, js)) .then(() => true) .catch((ex) => { displayException(ex); diff --git a/mod/bigbluebuttonbn/backup/moodle2/backup_bigbluebuttonbn_stepslib.php b/mod/bigbluebuttonbn/backup/moodle2/backup_bigbluebuttonbn_stepslib.php index f484933834e..f943110ed6e 100644 --- a/mod/bigbluebuttonbn/backup/moodle2/backup_bigbluebuttonbn_stepslib.php +++ b/mod/bigbluebuttonbn/backup/moodle2/backup_bigbluebuttonbn_stepslib.php @@ -51,7 +51,8 @@ class backup_bigbluebuttonbn_activity_structure_step extends backup_activity_str 'recordings_html', 'recordings_deleted', 'recordings_imported', 'recordings_preview', 'clienttype', 'muteonstart', 'completionattendance', 'completionengagementchats', 'completionengagementtalks', 'completionengagementraisehand', - 'completionengagementpollvotes', 'completionengagementemojis']); + 'completionengagementpollvotes', 'completionengagementemojis', + 'guestallowed', 'mustapproveuser']); $logs = new backup_nested_element('logs'); diff --git a/mod/bigbluebuttonbn/classes/external/meeting_info.php b/mod/bigbluebuttonbn/classes/external/meeting_info.php index e486ac27b8d..b61be8fd5d8 100644 --- a/mod/bigbluebuttonbn/classes/external/meeting_info.php +++ b/mod/bigbluebuttonbn/classes/external/meeting_info.php @@ -117,6 +117,7 @@ class meeting_info extends external_api { '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), @@ -140,6 +141,9 @@ class meeting_info extends external_api { ]) ), '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.'), diff --git a/mod/bigbluebuttonbn/classes/form/guest_add.php b/mod/bigbluebuttonbn/classes/form/guest_add.php new file mode 100644 index 00000000000..c32da01e764 --- /dev/null +++ b/mod/bigbluebuttonbn/classes/form/guest_add.php @@ -0,0 +1,220 @@ +. +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]); + } +} diff --git a/mod/bigbluebuttonbn/classes/form/guest_login.php b/mod/bigbluebuttonbn/classes/form/guest_login.php new file mode 100644 index 00000000000..e442594d964 --- /dev/null +++ b/mod/bigbluebuttonbn/classes/form/guest_login.php @@ -0,0 +1,78 @@ +. + +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; + } +} + diff --git a/mod/bigbluebuttonbn/classes/form/text_with_copy_element.php b/mod/bigbluebuttonbn/classes/form/text_with_copy_element.php new file mode 100644 index 00000000000..3d08933cd78 --- /dev/null +++ b/mod/bigbluebuttonbn/classes/form/text_with_copy_element.php @@ -0,0 +1,94 @@ +. +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 { + /** + * 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; + } + +} diff --git a/mod/bigbluebuttonbn/classes/instance.php b/mod/bigbluebuttonbn/classes/instance.php index 79956bc8efb..cc798294c2e 100644 --- a/mod/bigbluebuttonbn/classes/instance.php +++ b/mod/bigbluebuttonbn/classes/instance.php @@ -536,7 +536,6 @@ EOF; */ public function get_user(): stdClass { global $USER; - return $USER; } @@ -547,8 +546,7 @@ EOF; */ public function get_user_id(): int { $user = $this->get_user(); - - return $user->id; + return $user->id ?? 0; } /** @@ -558,7 +556,6 @@ EOF; */ public function get_user_fullname(): string { $user = $this->get_user(); - return fullname($user); } @@ -763,6 +760,16 @@ EOF; return (bool) $this->get_instance_var('record'); } + /** + * Moderator approval required ? + * + * By default we leave it as false as "ALWAYS_ACCEPT" is the default value for + * the guestPolicy create parameter (https://docs.bigbluebutton.org/dev/api.html) + * @return bool + */ + public function is_moderator_approval_required(): bool { + return $this->get_instance_var('mustapproveuser') ?? false; + } /** * Whether this instance can import recordings from another instance. * @@ -1170,4 +1177,54 @@ EOF; } return true; } + + /** + * Get current guest link url + * + * @return moodle_url + */ + public function get_guest_access_url(): moodle_url { + $guestlinkuid = $this->get_instance_var('guestlinkuid'); + if (empty($guestlinkuid)) { + $this->generate_guest_credentials(); + $guestlinkuid = $this->get_instance_var('guestlinkuid'); + } + return new moodle_url('/mod/bigbluebuttonbn/guest.php', ['uid' => $guestlinkuid]); + } + + /** + * Is guest access allowed in this instance. + * + * @return bool + */ + public function is_guest_allowed(): bool { + return !$this->is_type_recordings_only() && + config::get('guestaccess_enabled') && $this->get_instance_var('guestallowed'); + } + + /** + * Get current meeting password + * + * @return string + */ + public function get_guest_access_password() : string { + $guestpassword = $this->get_instance_var('guestpassword'); + if (empty($guestpassword)) { + $this->generate_guest_credentials(); + $guestpassword = $this->get_instance_var('guestpassword'); + } + return $guestpassword; + } + + /** + * Generate credentials for this instance and persist the value in the database + * + * @return void + */ + private function generate_guest_credentials():void { + global $DB; + [$this->instancedata->guestlinkuid, $this->instancedata->guestpassword] = + \mod_bigbluebuttonbn\plugin::generate_guest_meeting_credentials(); + $DB->update_record('bigbluebuttonbn', $this->instancedata); + } } diff --git a/mod/bigbluebuttonbn/classes/local/config.php b/mod/bigbluebuttonbn/classes/local/config.php index 39a0d0124d7..55074672d71 100644 --- a/mod/bigbluebuttonbn/classes/local/config.php +++ b/mod/bigbluebuttonbn/classes/local/config.php @@ -218,6 +218,7 @@ class config { 'welcome_default' => self::get('welcome_default'), 'welcome_editable' => self::get('welcome_editable'), 'poll_interval' => self::get('poll_interval'), + 'guestaccess_enabled' => self::get('guestaccess_enabled'), ]; } diff --git a/mod/bigbluebuttonbn/classes/local/proxy/bigbluebutton_proxy.php b/mod/bigbluebuttonbn/classes/local/proxy/bigbluebutton_proxy.php index dfe2221d7de..7e0111185eb 100644 --- a/mod/bigbluebuttonbn/classes/local/proxy/bigbluebutton_proxy.php +++ b/mod/bigbluebuttonbn/classes/local/proxy/bigbluebutton_proxy.php @@ -59,7 +59,7 @@ class bigbluebutton_proxy extends proxy_base { * @param string $logouturl * @param string $role * @param string|null $configtoken - * @param string|null $userid + * @param int $userid * @param string|null $createtime * * @return string @@ -71,9 +71,9 @@ class bigbluebutton_proxy extends proxy_base { string $logouturl, string $role, string $configtoken = null, - string $userid = null, + int $userid = 0, string $createtime = null - ): ?string { + ): string { $data = [ 'meetingID' => $meetingid, 'fullName' => $username, @@ -86,8 +86,11 @@ class bigbluebutton_proxy extends proxy_base { $data['configToken'] = $configtoken; } - if (!is_null($userid)) { + if (!empty($userid)) { $data['userID'] = $userid; + $data['guest'] = "false"; + } else { + $data['guest'] = "true"; } if (!is_null($createtime)) { diff --git a/mod/bigbluebuttonbn/classes/meeting.php b/mod/bigbluebuttonbn/classes/meeting.php index 28241a64200..8b3e138ac7c 100644 --- a/mod/bigbluebuttonbn/classes/meeting.php +++ b/mod/bigbluebuttonbn/classes/meeting.php @@ -192,7 +192,7 @@ class meeting { * * @return string */ - public function get_join_url() { + public function get_join_url(): string { return bigbluebutton_proxy::get_join_url( $this->instance->get_meeting_id(), $this->instance->get_user_fullname(), @@ -205,6 +205,26 @@ class meeting { ); } + /** + * Get meeting join URL for guest + * + * @param string $fullname + * @return string + */ + public function get_guest_join_url(string $fullname): string { + return bigbluebutton_proxy::get_join_url( + $this->instance->get_meeting_id(), + $fullname, + $this->instance->get_current_user_password(), + $this->instance->get_guest_access_url()->out(false), + $this->instance->get_current_user_role(), + null, + 0, + $this->get_meeting_info()->createtime + ); + } + + /** * Return meeting information for this meeting. * @@ -281,9 +301,13 @@ class meeting { $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; } @@ -390,6 +414,11 @@ class meeting { 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); @@ -513,18 +542,17 @@ class meeting { } /** - * Join a meeting. + * Prepare join meeting action * - * @param int $origin The spec - * @return string The URL to redirect to - * @throws meeting_join_exception + * @param int $origin + * @return void */ - public function join(int $origin): string { + 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()) - && $this->instance->does_current_user_count_towards_user_limit() + $this->instance->has_user_limit_been_reached($this->get_participant_count()) + && $this->instance->does_current_user_count_towards_user_limit() ) { throw new meeting_join_exception('userlimitreached'); } @@ -538,6 +566,29 @@ class meeting { // 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(); } } diff --git a/mod/bigbluebuttonbn/classes/plugin.php b/mod/bigbluebuttonbn/classes/plugin.php index cf106e3a589..e160c851280 100644 --- a/mod/bigbluebuttonbn/classes/plugin.php +++ b/mod/bigbluebuttonbn/classes/plugin.php @@ -65,4 +65,15 @@ abstract class plugin { } while ($unique == $password); return $password; } + + /** + * Generate random credentials for guest access + * + * @return array + */ + public static function generate_guest_meeting_credentials(): array { + $password = self::random_password(); + $guestlinkuid = sha1(self::random_password(1024)); + return [$guestlinkuid, $password]; + } } diff --git a/mod/bigbluebuttonbn/classes/settings.php b/mod/bigbluebuttonbn/classes/settings.php index 0ea2bc274c0..2d05b4f9ee7 100644 --- a/mod/bigbluebuttonbn/classes/settings.php +++ b/mod/bigbluebuttonbn/classes/settings.php @@ -907,6 +907,18 @@ class settings { $item, $experimentalfeaturessetting ); + // UI for 'register meeting events' feature. + $item = new admin_setting_configcheckbox( + 'bigbluebuttonbn_guestaccess_enabled', + get_string('config_guestaccess_enabled', 'bigbluebuttonbn'), + get_string('config_guestaccess_enabled_description', 'bigbluebuttonbn'), + 0 + ); + $this->add_conditional_element( + 'guestaccess_enabled', + $item, + $experimentalfeaturessetting + ); } $this->admin->add($this->parent, $experimentalfeaturessetting); } diff --git a/mod/bigbluebuttonbn/classes/task/send_guest_emails.php b/mod/bigbluebuttonbn/classes/task/send_guest_emails.php new file mode 100644 index 00000000000..fadbc004ef4 --- /dev/null +++ b/mod/bigbluebuttonbn/classes/task/send_guest_emails.php @@ -0,0 +1,109 @@ +. +namespace mod_bigbluebuttonbn\task; +use core_user; +use html_writer; + +/** + * This adhoc task will send emails to guest users with the meeting's details + * + * @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 send_guest_emails extends send_notification { + + /** + * Get the notification type. + * + * @return string + */ + protected function get_notification_type(): string { + return 'guest_invited'; + } + + /** + * Send all the emails + */ + protected function send_all_notifications(): void { + $customdata = $this->get_custom_data(); + if (!empty($customdata->emails)) { + foreach ($customdata->emails as $email) { + $user = core_user::get_noreply_user(); + $user->email = $email; + $user->mailformat = 1; // HTML format. + + email_to_user( + $user, + core_user::get_noreply_user(), + $this->get_subject(), + $this->get_small_message(), + $this->get_html_message() + ); + } + } + + } + + /** + * Get the subject of the notification. + * + * @return string + */ + protected function get_subject(): string { + return get_string('guest_invitation_subject', 'mod_bigbluebuttonbn', $this->get_string_vars()); + } + + /** + * Get variables to make available to strings. + * + * @return array + */ + protected function get_string_vars(): array { + $customdata = $this->get_custom_data(); + $sender = core_user::get_user($customdata->useridfrom); + return [ + 'course_fullname' => $this->get_instance()->get_course()->fullname, + 'course_shortname' => $this->get_instance()->get_course()->shortname, + 'name' => $this->get_instance()->get_cm()->name, + 'guestjoinurl' => $this->get_instance()->get_guest_access_url()->out(false), + 'guestpassword' => $this->get_instance()->get_guest_access_password(), + 'sender' => fullname($sender) + ]; + } + + /** + * Get the short summary message. + * + * @return string + */ + protected function get_small_message(): string { + return get_string('guest_invitation_small_message', 'mod_bigbluebuttonbn', $this->get_string_vars()); + } + + /** + * Get the HTML message content. + * + * @return string + */ + protected function get_html_message(): string { + return html_writer::tag( + 'p', + get_string('guest_invitation_full_message', 'mod_bigbluebuttonbn', $this->get_string_vars()) + ); + } +} diff --git a/mod/bigbluebuttonbn/db/install.xml b/mod/bigbluebuttonbn/db/install.xml index 8eea8eaf053..cbd72d66d51 100644 --- a/mod/bigbluebuttonbn/db/install.xml +++ b/mod/bigbluebuttonbn/db/install.xml @@ -1,5 +1,5 @@ - @@ -47,6 +47,10 @@ + + + + diff --git a/mod/bigbluebuttonbn/db/upgrade.php b/mod/bigbluebuttonbn/db/upgrade.php index e31e24d8052..b7f539ba2f6 100644 --- a/mod/bigbluebuttonbn/db/upgrade.php +++ b/mod/bigbluebuttonbn/db/upgrade.php @@ -452,6 +452,34 @@ function xmldb_bigbluebuttonbn_upgrade($oldversion = 0) { // Bigbluebuttonbn savepoint reached. upgrade_mod_savepoint(true, 2022080400, 'bigbluebuttonbn'); } + if ($oldversion < 2022101900) { + $table = new xmldb_table('bigbluebuttonbn'); + + $field = new xmldb_field('guestallowed', XMLDB_TYPE_INTEGER, '2', null, null, null, '0', 'completionengagementemojis'); + // Conditionally launch add field guestallowed. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + $field = new xmldb_field('mustapproveuser', XMLDB_TYPE_INTEGER, '2', null, null, null, '1', 'guestallowed'); + // Conditionally launch add field mustapproveuser. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + $field = new xmldb_field('guestlinkuid', XMLDB_TYPE_CHAR, '1024', null, null, null, null, 'mustapproveuser'); + // Conditionally launch add field guestlinkuid. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + + $field = new xmldb_field('guestpassword', XMLDB_TYPE_CHAR, '255', null, null, null, null, 'guestlinkuid'); + // Conditionally launch add field guestpassword. + if (!$dbman->field_exists($table, $field)) { + $dbman->add_field($table, $field); + } + upgrade_mod_savepoint(true, 2022101900, 'bigbluebuttonbn'); + } return true; } diff --git a/mod/bigbluebuttonbn/guest.php b/mod/bigbluebuttonbn/guest.php new file mode 100644 index 00000000000..8a424adcfe8 --- /dev/null +++ b/mod/bigbluebuttonbn/guest.php @@ -0,0 +1,80 @@ +. + +/** + * Guest access implementation + * + * @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) + */ + +use mod_bigbluebuttonbn\form\guest_login; +use mod_bigbluebuttonbn\local\exceptions\server_not_available_exception; +use mod_bigbluebuttonbn\local\proxy\bigbluebutton_proxy; +use mod_bigbluebuttonbn\meeting; +use mod_bigbluebuttonbn\plugin; + +require(__DIR__.'/../../config.php'); +global $PAGE, $OUTPUT, $DB, $SITE; +// Still run through basic setup of the page. +require_course_login($SITE); +$uid = required_param('uid', PARAM_ALPHANUMEXT); + +$bbid = $DB->get_field('bigbluebuttonbn', 'id', ['guestlinkuid' => trim($uid)]); +if (empty($bbid)) { + throw new moodle_exception('guestaccess_activitynotfound', 'mod_bigbluebuttonbn'); +} +$instance = \mod_bigbluebuttonbn\instance::get_from_instanceid($bbid); +// Prevent access to this page if the guest access has been disabled on this instance. +if (!$instance->is_guest_allowed()) { + throw new moodle_exception('guestaccess_feature_disabled', 'mod_bigbluebuttonbn'); +} + +// Get the guest matching guest access link. +$PAGE->set_url('/mod/bigbluebuttonbn/guest.php', ['uid' => $uid]); +$title = $instance->get_course()->shortname . ': ' . format_string($instance->get_meeting_name()); +$PAGE->set_title($title); +$PAGE->set_heading($title); +$PAGE->set_pagelayout('standard'); +$form = new guest_login(null, ['uid' => $uid, 'instance' => $instance]); +// Specific for the tests: we allow to set the password in the form here. +if (defined('BEHAT_SITE_RUNNING')) { + $form->set_data(['password' => optional_param('password', '', PARAM_RAW)]); +} + +if ($data = $form->get_data()) { + $username = $data->username; + try { + $meeting = new meeting($instance); + // As the meeting doesn't exist, we raise an exception. + if (!empty($meeting->get_meeting_info()->createtime)) { + $url = $meeting->get_guest_join_url($username); + redirect($url); + } else { + \core\notification::add( + get_string('guestaccess_meeting_not_started', 'mod_bigbluebuttonbn'), + \core\output\notification::NOTIFY_ERROR + ); + } + } catch (server_not_available_exception $e) { + bigbluebutton_proxy::handle_server_not_available($instance); + } +} +echo $OUTPUT->header(); +echo $form->render(); +echo $OUTPUT->footer(); diff --git a/mod/bigbluebuttonbn/lang/en/bigbluebuttonbn.php b/mod/bigbluebuttonbn/lang/en/bigbluebuttonbn.php index 94b1cce0044..e1993c08eb9 100644 --- a/mod/bigbluebuttonbn/lang/en/bigbluebuttonbn.php +++ b/mod/bigbluebuttonbn/lang/en/bigbluebuttonbn.php @@ -48,7 +48,8 @@ $string['bigbluebuttondisablednotification_subject'] = 'BigBlueButton activity m $string['bigbluebuttondisablednotification'] = 'The BigBlueButton activity module has been disabled and any existing BigBlueButton course activities are currently not accessible. Prior to re-enabling this plugin, please ensure that you have read and accepted the data processing agreement with Blindside Networks Inc.'; $string['cannotperformaction'] = 'Cannot perform action {$a} on this recording'; $string['enablingbigbluebutton'] = 'Enabling BigBlueButton activity'; -$string['enablingbigbluebuttondpainfo'] = 'In order to meet your data protection obligations, before enabling this plugin, you must confirm that you have read and accepted the Blindside Networks data processing agreement. Please consult with your own privacy professionals for advice.'; +$string['enablingbigbluebuttondpainfo'] = 'In order to meet your data protection obligations, prior to enabling this plugin, you may need to ensure that you have read and accepted the data processing agreement with Blindside Networks Inc.
+Please consult with your own privacy professionals for advice.'; $string['indicator:cognitivedepth'] = 'BigBlueButton cognitive'; $string['indicator:cognitivedepth_help'] = 'This indicator is based on the cognitive depth reached by the student in a BigBlueButton activity.'; $string['indicator:socialbreadth'] = 'BigBlueButton social'; @@ -130,6 +131,10 @@ $string['minute'] = 'minute'; $string['minutes'] = 'minutes'; $string['config_dpa_note'] = 'Note: In order to meet your data protection obligations, before using a service provider for this plugin, you must ensure that you have read and accepted the service provider\'s data processing agreement. For the default free BigBlueButton service, this is the Blindside Networks data processing agreement. Please consult with your own privacy professionals for advice.'; + +$string['config_guestaccess_enabled'] = 'Create guest access link for external guests.'; +$string['config_guestaccess_enabled_description'] = 'You can use the guest access link to invite external guests who do not have a Moodle account to this conference room.'; + $string['config_general'] = 'General settings'; $string['config_general_description'] = 'These settings are always used.'; $string['config_server_url'] = 'BigBlueButton server URL'; @@ -300,6 +305,42 @@ $string['general_error_not_allowed_to_create_instances'] = 'User is not allowed $string['general_error_not_found'] = 'Cannot find the BigBlueButton activity ({$a}).'; $string['general_error_cannot_create_meeting'] = 'Cannot create session.'; $string['general_error_cannot_get_recordings'] = 'Cannot get recordings.'; +$string['guestaccess_add'] = 'Add guests'; +$string['guestaccess_add_no_id'] = 'No bigbluebutton instance ID provided.'; +$string['guestaccess_emails'] = 'Add guests emails'; +$string['guestaccess_emails_help'] = 'Emails of users you want to invite, separated by commas'; +$string['guestaccess_emails_invalidemail'] = 'The email {$a} is invalid, please correct it.'; +$string['guestaccess_username'] = 'Guest username'; +$string['guestaccess_meeting_link'] = 'Meeting link'; +$string['guestaccess_meeting_password'] = 'Meeting password'; +$string['guestaccess_password'] = 'Password'; +$string['guestaccess_meeting_not_started'] = 'Sorry, the meeting has not started yet. Please try again later.'; +$string['guestaccess_meeting_invalid_password'] = 'Incorrect password.'; +$string['guestaccess_activitynotfound'] = 'The BigblueButton activity is not found for this access as guest. Contact your +support.'; +$string['guestaccess_feature_disabled'] = 'Guest access: feature is disabled.'; +$string['guestaccess_title'] = 'Add guests to this meeting'; +$string['guestaccess_copy_link'] = 'Copy link'; +$string['guestaccess_copy_password'] = 'Copy password'; +$string['guestaccess_invite_success'] = 'An invitation will be sent to guest(s) {$a->emails} via email.'; +$string['guestaccess_invite_failure'] = 'Error {$a->errors} when inviting guests {$a->emails}, please contact your support.'; +$string['guestaccess_join_meeting'] = 'Join meeting'; +$string['guest_invitation_subject'] = 'You have been invited to the session {$a->name} on {$a->course_fullname} !'; +$string['guest_invitation_small_message'] = 'Hi, + +You have been invited as a guest to a BigblueButton session "{$a->name}" on "{$a->course_fullname}" course. + +You can join the session by following the link here : {$a->guestjoinurl}. +You will then be prompted for your name and the following password: "{$a->guestpassword}". + +If you need help, please contact {$a->sender}. +'; +$string['guest_invitation_full_message'] = 'Hi, +

You have been invited as a guest to a BigblueButton session "{$a->name}" on "{$a->course_fullname}" course.

+

You can join the session by following the link here. +You will then be prompted for your name and the following password: "{$a->guestpassword}".

+

If you need help, please contact {$a->sender}.

'; + $string['index_confirm_end'] = 'Do you want to end the session?'; $string['index_disabled'] = 'disabled'; $string['index_enabled'] = 'enabled'; @@ -317,6 +358,7 @@ $string['index_heading_viewer'] = 'Viewers'; $string['index_heading'] = 'BigBlueButton rooms'; $string['instanceprofilewithoutrecordings'] = 'This instance profile cannot display recordings'; $string['mod_form_block_general'] = 'General'; +$string['mod_form_block_guestaccess'] = 'Guest access'; $string['mod_form_block_room'] = 'Room settings'; $string['mod_form_block_recordings'] = 'Recording view'; $string['mod_form_block_presentation'] = 'Presentation content'; @@ -330,6 +372,8 @@ $string['mod_form_field_intro'] = 'Description'; $string['mod_form_field_intro_help'] = 'A short description of the room.'; $string['mod_form_field_duration_help'] = 'Setting the duration for a meeting will establish the maximum time for a meeting to keep alive before the recording finish'; $string['mod_form_field_duration'] = 'Duration'; +$string['mod_form_field_guestallowed'] = 'Allow guest access in the meeting'; +$string['mod_form_field_mustapproveuser'] = 'User must be approved by moderators'; $string['mod_form_field_userlimit'] = 'User limit'; $string['mod_form_field_userlimit_help'] = 'The maximum number of users allowed in a session. Set to 0 to allow an unlimited number of users.'; $string['mod_form_field_name'] = 'Room name'; @@ -443,18 +487,18 @@ $string['view_message_notavailableyet'] = 'This session is not yet available.'; $string['view_recording_select_course'] = 'Select a course first in the drop down menu'; -$string['view_message_session_started_at'] = 'This session started at'; +$string['view_message_session_started_at'] = 'Session started at'; $string['view_message_session_running_for'] = 'This session has been running for'; $string['view_message_hour'] = 'hour'; $string['view_message_hours'] = 'hours'; $string['view_message_minute'] = 'minute'; $string['view_message_minutes'] = 'minutes'; -$string['view_message_moderator'] = 'moderator'; -$string['view_message_moderators'] = 'moderators'; -$string['view_message_viewer'] = 'viewer'; -$string['view_message_viewers'] = 'viewers'; -$string['view_message_user'] = 'user'; -$string['view_message_users'] = 'users'; +$string['view_message_moderator'] = 'Moderator'; +$string['view_message_moderators'] = 'Moderators'; +$string['view_message_viewer'] = 'Viewer'; +$string['view_message_viewers'] = 'Viewers'; +$string['view_message_user'] = 'User'; +$string['view_message_users'] = 'Users'; $string['view_message_has_joined'] = 'has joined'; $string['view_message_have_joined'] = 'have joined'; $string['view_message_session_no_users'] = 'There are no users in this session'; diff --git a/mod/bigbluebuttonbn/lib.php b/mod/bigbluebuttonbn/lib.php index 42bd42155a6..9bad150b749 100644 --- a/mod/bigbluebuttonbn/lib.php +++ b/mod/bigbluebuttonbn/lib.php @@ -99,6 +99,8 @@ function bigbluebuttonbn_add_instance($bigbluebuttonbn) { $bigbluebuttonbn->presentation = files::save_media_file($bigbluebuttonbn); // Encode meetingid. $bigbluebuttonbn->meetingid = meeting::get_unique_meetingid_seed(); + [$bigbluebuttonbn->guestlinkuid, $bigbluebuttonbn->guestpassword] = + \mod_bigbluebuttonbn\plugin::generate_guest_meeting_credentials(); // Insert a record. $bigbluebuttonbn->id = $DB->insert_record('bigbluebuttonbn', $bigbluebuttonbn); // Log insert action. @@ -125,6 +127,10 @@ function bigbluebuttonbn_update_instance($bigbluebuttonbn) { $bigbluebuttonbn->id = $bigbluebuttonbn->instance; $bigbluebuttonbn->presentation = files::save_media_file($bigbluebuttonbn); + if (empty($bigbluebuttonbn->guestjoinurl) || empty($bigbluebuttonbn->guestpassword)) { + [$bigbluebuttonbn->guestlinkuid, $bigbluebuttonbn->guestpassword] = + \mod_bigbluebuttonbn\plugin::generate_guest_meeting_credentials(); + } // Update a record. $DB->update_record('bigbluebuttonbn', $bigbluebuttonbn); diff --git a/mod/bigbluebuttonbn/mod_form.php b/mod/bigbluebuttonbn/mod_form.php index 4f39eece603..01c86e5c9de 100644 --- a/mod/bigbluebuttonbn/mod_form.php +++ b/mod/bigbluebuttonbn/mod_form.php @@ -24,6 +24,7 @@ * @author Fred Dixon (ffdixon [at] blindsidenetworks [dt] com) */ +use mod_bigbluebuttonbn\instance; use mod_bigbluebuttonbn\local\helpers\roles; use mod_bigbluebuttonbn\local\proxy\bigbluebutton_proxy; @@ -80,6 +81,8 @@ class mod_bigbluebuttonbn_mod_form extends moodleform_mod { $this->bigbluebuttonbn_mform_add_block_preuploads($mform, $cfg); // Add block 'Participant List'. $this->bigbluebuttonbn_mform_add_block_user_role_mapping($mform, $participantlist); + // Add block 'Guest Access'. + $this->bigbluebuttonbn_mform_add_block_guest_access($mform, $cfg, $this->current); // Add block 'Schedule'. $this->bigbluebuttonbn_mform_add_block_schedule($mform, $this->current); // Add standard elements, common to all modules. @@ -597,6 +600,42 @@ class mod_bigbluebuttonbn_mod_form extends moodleform_mod { get_string('mod_form_field_participant_list', 'bigbluebuttonbn'), $html); } + /** + * Function to add guest acces settings to the instance + * + * @param MoodleQuickForm $mform + * @param array $cfg + * @param stdClass $current + * @return void + * @throws coding_exception + */ + private function bigbluebuttonbn_mform_add_block_guest_access(MoodleQuickForm $mform, array $cfg, stdClass $current) { + if (!empty($cfg['guestaccess_enabled'])) { + $mform->addElement('header', 'guestaccess', get_string('mod_form_block_guestaccess', 'bigbluebuttonbn')); + $mform->setExpanded('guestaccess'); + $mform->addElement('advcheckbox', 'guestallowed', + get_string('mod_form_field_guestallowed', 'bigbluebuttonbn')); + $mform->addElement('advcheckbox', 'mustapproveuser', + get_string('mod_form_field_mustapproveuser', 'bigbluebuttonbn')); + $mform->hideIf('mustapproveuser', 'guestallowed'); + if (!empty($this->_cm)) { + $instance = instance::get_from_cmid($this->_cm->id); + \mod_bigbluebuttonbn\form\guest_add::add_meeting_links_elements($mform); + $mform->setDefault('guestjoinurl', $instance->get_guest_access_url()); + $mform->setDefault('guestpassword', $instance->get_guest_access_password()); + $mform->hideIf('guestjoinurl', 'guestallowed'); + $mform->hideIf('guestpassword', 'guestallowed'); + } + } else { + $mform->addElement('hidden', 'guestallowed', 0); + $mform->addElement('hidden', 'mustapproveuser', 0); + } + $mform->setType('guestallowed', PARAM_BOOL); + $mform->setType('mustapproveuser', PARAM_BOOL); + $mform->setDefault('guestallowed', 0); + $mform->setDefault('mustapproveuser', 1); + } + /** * Function for showing the block for integration with the calendar. * diff --git a/mod/bigbluebuttonbn/styles.css b/mod/bigbluebuttonbn/styles.css index c3ed3edc06d..445d7cf58ad 100644 --- a/mod/bigbluebuttonbn/styles.css +++ b/mod/bigbluebuttonbn/styles.css @@ -34,3 +34,12 @@ .bbb_index_form { display: inline-block; } + +.bigbluebuttonbn_icon_btn i.icon { + margin-right: auto; +} +/* Disable gray background for text input */ +input.form-control[readonly][name="guestpassword"], +input.form-control[readonly][name="guestjoinurl"] { + background: initial; +} \ No newline at end of file diff --git a/mod/bigbluebuttonbn/templates/element_text_with_copy.mustache b/mod/bigbluebuttonbn/templates/element_text_with_copy.mustache new file mode 100644 index 00000000000..2913954ae7d --- /dev/null +++ b/mod/bigbluebuttonbn/templates/element_text_with_copy.mustache @@ -0,0 +1,71 @@ +{{! + 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 . +}} +{{! + @template mod_bigbluebuttonbn/text_with-copy + + This template renders a text input form element with a copy link next to it. + + Context variables required for this template: + * label + * required + * advanced + * helpbutton + * error + * element + * id + * name + + Example context (json): + { + "label": "Course full name", + "required": true, + "advanced": false, + "error": null, + "element": { + "wrapperid": "fitem_id_fullname", + "id": "id_fullname", + "name": "fullname", + "iderror": "id_error" + } + } +}} +{{< core_form/element-template }} + {{$element}} + + + {{/element}} +{{/ core_form/element-template }} +{{#js}} + require(['core/copy_to_clipboard']); +{{/js}} \ No newline at end of file diff --git a/mod/bigbluebuttonbn/templates/end_session_button.mustache b/mod/bigbluebuttonbn/templates/end_session_button.mustache index 9e9ae452d42..dbab4077951 100644 --- a/mod/bigbluebuttonbn/templates/end_session_button.mustache +++ b/mod/bigbluebuttonbn/templates/end_session_button.mustache @@ -34,7 +34,7 @@ }} data-action="end"{{! }} data-bbb-id="{{bigbluebuttonbnid}}"{{! }} data-group-id="{{groupid}}"{{! - }} class="btn btn-secondary bbb-btn-action"{{! + }} class="btn btn-outline-dark bbb-btn-action m-1"{{! }}> {{#str}}view_conference_action_end, mod_bigbluebuttonbn{{/str}} diff --git a/mod/bigbluebuttonbn/templates/guest_links.mustache b/mod/bigbluebuttonbn/templates/guest_links.mustache new file mode 100644 index 00000000000..eceecdc36bd --- /dev/null +++ b/mod/bigbluebuttonbn/templates/guest_links.mustache @@ -0,0 +1,41 @@ +{{! + 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 . +}} +{{! + @template mod_bigbluebuttonbn/guest_links + + This template renders the mobile page. + + Example context (json): + { + "guestaccessenabled": 1, + "guestjoinurl": "http://moodle.org/", + "guestpassword": "password" + } +}} +{{#guestaccessenabled}} + +{{/guestaccessenabled}} +{{#js}} + require(['mod_bigbluebuttonbn/guest_access_modal'], function(GuestLinkAccess) { + GuestLinkAccess.init({ + id: "{{ bigbluebuttonbnid }}", + groupid: "{{ groupid }}" + }); + }); +{{/js}} diff --git a/mod/bigbluebuttonbn/templates/room_view.mustache b/mod/bigbluebuttonbn/templates/room_view.mustache index a6465fdcf4a..420894ddc57 100644 --- a/mod/bigbluebuttonbn/templates/room_view.mustache +++ b/mod/bigbluebuttonbn/templates/room_view.mustache @@ -21,6 +21,8 @@ Example context (json): { + "bigbluebuttonbnid": 1, + "groupid": 0, "statusclosed": true, "statusclosed": false, "openingtime": 123456789, @@ -28,71 +30,86 @@ } }} -
- - {{statusmessage}} - - {{#openingtime}} - - {{#str}}mod_form_field_openingtime, bigbluebuttonbn{{/str}}: - {{#userdate}} {{.}}, {{#str}} strftimedaydatetime, langconfig {{/str}} {{/userdate}} - - {{/openingtime}} - {{#closingtime}} - - {{#str}}mod_form_field_closingtime, bigbluebuttonbn{{/str}}: - {{#userdate}} {{.}}, {{#str}} strftimedaydatetime, langconfig {{/str}} {{/userdate}} - - {{/closingtime}} - - {{#statusrunning}} -
- {{#str}}view_message_session_started_at, mod_bigbluebuttonbn{{/str}} - {{#userdate}} {{startedat}}, {{#str}} strftimetime, core_langconfig{{/str}} {{/userdate}} - - {{#moderatorplural}}{{#str}}view_message_moderators, mod_bigbluebuttonbn{{/str}}{{/moderatorplural}} - {{^moderatorplural}}{{#str}}view_message_moderator, mod_bigbluebuttonbn{{/str}}{{/moderatorplural}} - {{moderatorcount}} - - - {{#viewerplural}}{{#str}}view_message_viewers, mod_bigbluebuttonbn{{/str}}{{/viewerplural}} - {{^viewerplural}}{{#str}}view_message_viewer, mod_bigbluebuttonbn{{/str}}{{/viewerplural}} - {{participantcount}}. - -
- {{/statusrunning}} -
- -
- {{#haspresentations}} -

{{#str}}view_section_title_presentation, mod_bigbluebuttonbn{{/str}}

-
- {{#presentations}} - - {{#pix}}{{iconname}},core, {{icondesc}}{{/pix}} - {{name}} - - {{/presentations}} +
+
+ {{#statusrunning}} +
+ {{#str}}view_message_session_started_at, mod_bigbluebuttonbn{{/str}}: + {{#userdate}} {{startedat}}, {{#str}} strftimetime, core_langconfig{{/str}} {{/userdate}}. + {{statusmessage}} +
+ {{/statusrunning}} + {{^statusrunning}} +
{{statusmessage}}
+ {{/statusrunning}} +
+ {{#openingtime}} + + {{#str}}mod_form_field_openingtime, bigbluebuttonbn{{/str}}: + {{#userdate}} {{.}}, {{#str}} strftimedaydatetime, langconfig {{/str}} {{/userdate}} + + {{/openingtime}} + {{#closingtime}} +
+ {{#str}}mod_form_field_closingtime, bigbluebuttonbn{{/str}}: + {{#userdate}} {{.}}, {{#str}} strftimedaydatetime, langconfig {{/str}} {{/userdate}} +
+ {{/closingtime}}
- {{/haspresentations}} + {{#statusrunning}} +
+ + {{#moderatorplural}}{{#str}}view_message_moderators, mod_bigbluebuttonbn{{/str}}{{/moderatorplural}} + {{^moderatorplural}}{{#str}}view_message_moderator, mod_bigbluebuttonbn{{/str}}{{/moderatorplural}}: + + {{moderatorcount}} +
+
+ + {{#viewerplural}}{{#str}}view_message_viewers, mod_bigbluebuttonbn{{/str}}{{/viewerplural}} + {{^viewerplural}}{{#str}}view_message_viewer, mod_bigbluebuttonbn{{/str}}{{/viewerplural}}: + + {{participantcount}} +
+ {{/statusrunning}} +
+ +
+ {{#haspresentations}} +
{{#str}}view_section_title_presentation, mod_bigbluebuttonbn{{/str}}
+
+ {{#presentations}} + + {{#pix}}{{iconname}},core, {{icondesc}}{{/pix}} + {{name}} + + {{/presentations}} +
+ {{/haspresentations}} +
-
-
+
+
+ {{#ismoderator}} + {{> mod_bigbluebuttonbn/guest_links}} + {{/ismoderator}} + {{#ismoderator}} + {{> mod_bigbluebuttonbn/end_session_button}} + {{/ismoderator}} + {{#canjoin}} {{#str}}view_conference_action_join, mod_bigbluebuttonbn{{/str}} {{/canjoin}} - - {{#ismoderator}} - {{> mod_bigbluebuttonbn/end_session_button}} - {{/ismoderator}}
diff --git a/mod/bigbluebuttonbn/templates/view_page.mustache b/mod/bigbluebuttonbn/templates/view_page.mustache index b467f643b1f..7363bdffcde 100644 --- a/mod/bigbluebuttonbn/templates/view_page.mustache +++ b/mod/bigbluebuttonbn/templates/view_page.mustache @@ -51,12 +51,15 @@ {{/sitenotification}}

{{meetingname}}

+
{{{meetingdescription}}}
{{#room}} {{>mod_bigbluebuttonbn/room_view}} {{/room}} + + {{#recordings}} {{>mod_bigbluebuttonbn/recordings_session}} {{/recordings}} diff --git a/mod/bigbluebuttonbn/tests/behat/behat_mod_bigbluebuttonbn.php b/mod/bigbluebuttonbn/tests/behat/behat_mod_bigbluebuttonbn.php index 7c3994409ff..84c6b7d43b6 100644 --- a/mod/bigbluebuttonbn/tests/behat/behat_mod_bigbluebuttonbn.php +++ b/mod/bigbluebuttonbn/tests/behat/behat_mod_bigbluebuttonbn.php @@ -27,6 +27,7 @@ require_once(__DIR__ . '/../../../../lib/behat/behat_base.php'); use Behat\Behat\Hook\Scope\BeforeScenarioScope; use Behat\Gherkin\Node\TableNode; +use mod_bigbluebuttonbn\instance; use Moodle\BehatExtension\Exception\SkippedException; /** @@ -138,7 +139,13 @@ XPATH return new moodle_url('/mod/bigbluebuttonbn/index.php', [ 'id' => $this->get_course_id($identifier), ]); - + case 'BigblueButtonBN Guest': + $cm = $this->get_cm_by_activity_name('bigbluebuttonbn', $identifier); + $instance = instance::get_from_cmid($cm->id); + $url = $instance->get_guest_access_url(); + // We have to make sure we set the password. It makes it then easy to submit the form with the right password. + $url->param('password', $instance->get_guest_access_password()); + return $url; default: throw new Exception("Unrecognised page type '{$type}'."); } diff --git a/mod/bigbluebuttonbn/tests/behat/guest_access.feature b/mod/bigbluebuttonbn/tests/behat/guest_access.feature new file mode 100644 index 00000000000..c15e8ec4eca --- /dev/null +++ b/mod/bigbluebuttonbn/tests/behat/guest_access.feature @@ -0,0 +1,63 @@ +@mod @mod_bigbluebuttonbn +Feature: Guest access allows external users to connect to a meeting + + Background: + Given a BigBlueButton mock server is configured + And I enable "bigbluebuttonbn" "mod" plugin + And the following "courses" exist: + | fullname | shortname | category | + | Test Course 1 | C1 | 0 | + And the following "activities" exist: + | activity | name | intro | course | idnumber | type | recordings_imported | guestallowed | + | bigbluebuttonbn | RoomRecordings | Test Room Recording description | C1 | bigbluebuttonbn1 | 0 | 0 | 0 | + | bigbluebuttonbn | RoomRecordingsWithguest | Test Room with guest | C1 | bigbluebuttonbn1 | 0 | 0 | 1 | + + @javascript + Scenario: I need to enable guest access to see the instance parameters + Given the following config values are set as admin: + | bigbluebuttonbn_guestaccess_enabled | 1 | + When I am on the "RoomRecordings" "bigbluebuttonbn activity editing" page logged in as "admin" + Then I should see "Guest access" + Then I log out + Given the following config values are set as admin: + | bigbluebuttonbn_guestaccess_enabled | 0 | + When I am on the "RoomRecordings" "bigbluebuttonbn activity editing" page logged in as "admin" + Then I should not see "Guest access" + Then I log out + + @javascript + Scenario: I should see Guest settings on the module form + Given the following config values are set as admin: + | bigbluebuttonbn_guestaccess_enabled | 1 | + When I am on the "RoomRecordings" "bigbluebuttonbn activity editing" page logged in as "admin" + Then I should see "Guest access" + Then I click on "Expand all" "link" + Then I should see "Allow guest access in the meeting" + And I should not see "Meeting link" + And I should not see "Meeting password" + When I set the field "Allow guest access in the meeting" to "1" + Then I should see "User must be approved by moderators" + And I should see "Meeting link" + And I should see "Meeting password" + And I should see "Copy link" + And I should see "Copy password" + Then I log out + + @javascript + Scenario: I should be able to invite guest to the meeting + Given the following config values are set as admin: + | bigbluebuttonbn_guestaccess_enabled | 1 | + When I am on the "RoomRecordingsWithguest" "bigbluebuttonbn activity" page logged in as "admin" + Then I should see "Add guests" + And I click on "Add guests" "button" + And I should see "Meeting link" + And I should see "Meeting password" + And I should see "Copy link" + And I should see "Copy password" + When I set the field "Add guests emails" to "123" + When I click on "OK" "button" in the "Add guests to this meeting" "dialogue" + Then I should see "The email 123 is invalid, please correct it." + When I set the field "Add guests emails" to "testuser@email.com" + When I click on "OK" "button" in the "Add guests to this meeting" "dialogue" + Then I should see "An invitation will be sent to guest(s) testuser@email.com via email." + Then I log out diff --git a/mod/bigbluebuttonbn/tests/behat/join_meeting_as_guest.feature b/mod/bigbluebuttonbn/tests/behat/join_meeting_as_guest.feature new file mode 100644 index 00000000000..3588b496a40 --- /dev/null +++ b/mod/bigbluebuttonbn/tests/behat/join_meeting_as_guest.feature @@ -0,0 +1,53 @@ +@mod @mod_bigbluebuttonbn @javascript +Feature: Test the ability to run the full meeting lifecycle (start to end) for guest users + + Background: + Given a BigBlueButton mock server is configured + And I enable "bigbluebuttonbn" "mod" plugin + And the following config values are set as admin: + | bigbluebuttonbn_guestaccess_enabled | 1 | + And the following course exists: + | name | Test course | + | shortname | C1 | + And the following "users" exist: + | username | firstname | lastname | email | + | traverst | Terry | Travers | t.travers@example.com | + | teacher | Teacher | Teacher | t.eacher@example.com | + And the following "course enrolments" exist: + | user | course | role | + | traverst | C1 | student | + | teacher | C1 | editingteacher | + And the following "activity" exists: + | course | C1 | + | activity | bigbluebuttonbn | + | name | Room recordings | + | idnumber | Room recordings | + | moderators | role:editingteacher | + | wait | 0 | + | guestallowed | 1 | + + Scenario: Student users should be able to see the guest user information + When I am on the "Room recordings" Activity page logged in as traverst + Then I should not see "Add guests" + + Scenario: Teacher users should be able to see the guest user information + When I am on the "Room recordings" Activity page logged in as teacher + Then I should see "Add guests" + When I click on "Add guests" "button" + Then I should see "Add guests to this meeting" in the ".modal-dialog" "css_element" + + Scenario: Guest users should be able to join a meeting as guest when the meeting is running. + When I am on the "Room recordings" Activity page logged in as traverst + Then "Join session" "link" should exist + When I click on "Join session" "link" + And I switch to the main window + Then I log out + And I close all opened windows + Then I am on the "Room recordings" "mod_bigbluebuttonbn > BigblueButtonBN Guest" page + Then I should see "Guest username" + And I should see "Password" + And I set the field "username" to "Test Guest User" + And I click on "Join meeting" "button" + Then I should see "Test Guest User" + And I click on "Leave Meeting" "link" + Then I should see "C1: Room recordings" diff --git a/mod/bigbluebuttonbn/tests/instance_test.php b/mod/bigbluebuttonbn/tests/instance_test.php index ccd03256566..8fe81cb2277 100644 --- a/mod/bigbluebuttonbn/tests/instance_test.php +++ b/mod/bigbluebuttonbn/tests/instance_test.php @@ -542,4 +542,64 @@ class instance_test extends advanced_testcase { ]; } + + /** + * Test get user id (guest or current user) + * @covers \mod_bigbluebuttonbn\instance::get_user_id + */ + public function test_get_user_id(): void { + $this->resetAfterTest(); + $this->setUser(null); + ['record' => $record ] = $this->get_test_instance(); + $instance = instance::get_from_instanceid($record->id); + $this->assertEquals(0, $instance->get_user_id()); + $user = $this->getDataGenerator()->create_user(); + $this->setUser($user); + $this->assertEquals($user->id, $instance->get_user_id()); + } + + /** + * Test guest access URL + * + * @covers ::get_guest_access_url + */ + public function test_get_guest_access_url() { + global $CFG; + $this->resetAfterTest(); + ['record' => $record ] = $this->get_test_instance(['guestallowed' => true]); + $CFG->bigbluebuttonbn['guestaccess_enabled'] = 1; + $instance = instance::get_from_instanceid($record->id); + $this->assertNotEmpty($instance->get_guest_access_url()); + } + + /** + * Test guest allowed flag + * + * @covers ::is_guest_allowed + */ + public function test_is_guest_allowed() { + global $CFG; + $this->resetAfterTest(); + ['record' => $record ] = $this->get_test_instance(['guestallowed' => true]); + $CFG->bigbluebuttonbn['guestaccess_enabled'] = 1; + $instance = instance::get_from_instanceid($record->id); + $this->assertTrue($instance->is_guest_allowed()); + $CFG->bigbluebuttonbn['guestaccess_enabled'] = 0; + $this->assertFalse($instance->is_guest_allowed()); + } + + /** + * Test guest access password + * + * @covers ::get_guest_access_password + */ + public function get_guest_access_password() { + global $CFG; + $this->resetAfterTest(); + ['record' => $record ] = $this->get_test_instance(['guestallowed' => true]); + $CFG->bigbluebuttonbn['guestaccess_enabled'] = 1; + $instance = instance::get_from_instanceid($record->id); + $this->assertNotEmpty($instance->get_guest_access_password()); + } + } diff --git a/mod/bigbluebuttonbn/tests/plugin_test.php b/mod/bigbluebuttonbn/tests/plugin_test.php new file mode 100644 index 00000000000..6e3fefbba60 --- /dev/null +++ b/mod/bigbluebuttonbn/tests/plugin_test.php @@ -0,0 +1,62 @@ +. +namespace mod_bigbluebuttonbn; + +use advanced_testcase; +use moodle_exception; + +/** + * Tests for the Big Blue Button Plugin class. + * + * @package mod_bigbluebuttonbn + * @copyright 2021 Andrew Lyons + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @coversDefaultClass \mod_bigbluebuttonbn\plugin + */ +class plugin_test extends advanced_testcase { + + /** + * Test html2text + * + * @covers ::html2text + */ + public function test_html2text(): void { + $this->assertEquals('My text is in HTML', plugin::html2text('

My text is in HTML

', 100)); + $this->assertEquals('My...', plugin::html2text('

My text is in HTML

', 2)); + } + + /** + * Test random_password + * + * @covers ::random_password + */ + public function test_random_password(): void { + $password = plugin::random_password(10); + $this->assertEquals(10, strlen($password)); + $this->assertMatchesRegularExpression( + '/[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789]+/', $password); + } + + /** + * Test generate_guest_meeting_credentials + * + * @covers ::generate_guest_meeting_credentials + */ + public function test_generate_guest_meeting_credentials(): void { + [$guestlinkuid, $password] = plugin::generate_guest_meeting_credentials(); + $this->assertEquals(40, strlen($guestlinkuid)); + } +} diff --git a/mod/bigbluebuttonbn/tests/task/send_guest_emails_test.php b/mod/bigbluebuttonbn/tests/task/send_guest_emails_test.php new file mode 100644 index 00000000000..2552be27218 --- /dev/null +++ b/mod/bigbluebuttonbn/tests/task/send_guest_emails_test.php @@ -0,0 +1,66 @@ +. + +namespace mod_bigbluebuttonbn\task; + +use advanced_testcase; + +/** + * Send guest email tests + * + * @package mod_bigbluebuttonbn + * @copyright 2019 onwards, Blindside Networks Inc + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @covers \mod_bigbluebuttonbn\task\send_guest_emails + * @coversDefaultClass \mod_bigbluebuttonbn\task\send_guest_emails + */ +class send_guest_emails_test extends advanced_testcase { + /** + * Check if set instance ID works correctly + * + */ + public function test_send_emails(): void { + $this->resetAfterTest(); + $emailsink = $this->redirectEmails(); + $generator = $this->getDataGenerator(); + $course = $generator->create_course(); + $instancedata = $generator->create_module('bigbluebuttonbn', [ + 'course' => $course->id, + ]); + $moderatoruser = $generator->create_user(); + + $guestemail = new send_guest_emails(); + $guestemail->set_custom_data( + [ + 'emails' => ['test1@email.com', 'test2@email.com'], + 'useridfrom' => $moderatoruser->id + ] + ); + $guestemail->set_instance_id($instancedata->id); + \core\task\manager::queue_adhoc_task($guestemail); + $this->runAdhocTasks(); + + // Check the events. + $messages = $emailsink->get_messages(); + $this->assertCount(2, $messages); + + $this->assertEquals('You have been invited to the session BigBlueButton 1 on Test course 1 !', $messages[0]->subject); + $this->assertEquals('noreply@www.example.com', $messages[0]->from); + $this->assertEquals('test1@email.com', $messages[0]->to); + $this->assertEquals('test2@email.com', $messages[1]->to); + + } +} diff --git a/mod/bigbluebuttonbn/upgrade.txt b/mod/bigbluebuttonbn/upgrade.txt index eb12b854ff6..08d604351e4 100644 --- a/mod/bigbluebuttonbn/upgrade.txt +++ b/mod/bigbluebuttonbn/upgrade.txt @@ -3,3 +3,5 @@ This files describes API changes in the bigbluebuttonbn code. === 4.1 === * External function mod_bigbluebuttonbn\external\meeting_info now return the list of the instance features and whether they are enabled or not. +* External function mod_bigbluebuttonbn\external\meeting_info may now return guestaccessenabled, guestjoinurl, and guestpassword + if the callee can have access to them. \ No newline at end of file diff --git a/mod/bigbluebuttonbn/version.php b/mod/bigbluebuttonbn/version.php index 34b4df704fc..a26303684f6 100644 --- a/mod/bigbluebuttonbn/version.php +++ b/mod/bigbluebuttonbn/version.php @@ -27,6 +27,6 @@ defined('MOODLE_INTERNAL') || die; -$plugin->version = 2022080400; +$plugin->version = 2022101900; $plugin->requires = 2022041200; $plugin->component = 'mod_bigbluebuttonbn';