diff --git a/amd/build/block_index.min.js b/amd/build/block_index.min.js index fa369cc9..683567b2 100644 --- a/amd/build/block_index.min.js +++ b/amd/build/block_index.min.js @@ -5,6 +5,6 @@ * @copyright 2021 Tamara Gunkel, University of Münster * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -define("block_opencast/block_index",["jquery","core/modal_factory","core/modal_events","core/str","core/url","core/notification","core/toast","core/ajax"],(function($,ModalFactory,ModalEvents,str,url,Notification,Toast,Ajax){window.liveUpdateInterval=null,window.liveUpdateItemsWithError=[],window.liveUpdateState=null;var pauseLiveUpdate=function(liveupdate){liveupdate.enabled&&null!==window.liveUpdateInterval&&(clearInterval(window.liveUpdateInterval),window.liveUpdateState="paused")},resumeLiveUpdate=function(ocinstanceid,contextid,liveupdate){liveupdate.enabled&&"paused"==window.liveUpdateState&&(initLiveUpdate(ocinstanceid,contextid,liveupdate.timeout),window.liveUpdateState="resumed")},displayWorkflowPrivacyNotice=function(privacyworkflows,workflowid){Array.isArray(privacyworkflows)&&(0===privacyworkflows.length||privacyworkflows.includes(workflowid))?$("#privacynoticediv").removeClass("d-none"):$("#privacynoticediv").addClass("d-none")},displayWorkflowDescription=function(workflowobj){null!=workflowobj&&workflowobj.description?($("#workflowdescdiv").removeClass("d-none"),$("#workflowdesc").html(workflowobj.description)):$("#workflowdescdiv").addClass("d-none")},displayWorkflowConfigPanel=function(ocinstanceid,courseid,workflowid){$("#workflowconfigpaneldiv").addClass("d-none"),$("#workflowconfigpanelloading").removeClass("d-none"),$("#config-frame").attr("src","");var configpanelsrc=url.relativeUrl("blocks/opencast/serveworkflowconfigpanel.php",{ocinstanceid:ocinstanceid,courseid:courseid,workflowid:workflowid});$.ajax({url:configpanelsrc,success:data=>{""!==data.trim()&&($("#workflowconfigpaneldiv").removeClass("d-none"),$("#config-frame").attr("src",configpanelsrc))},async:!1})},initLiveUpdate=function(ocinstanceid,contextid,reloadtimeout){null!==window.liveUpdateInterval&&clearInterval(window.liveUpdateInterval),window.liveUpdateItemsWithError=[],getLiveUpdateItems().length&&(window.liveUpdateInterval=setInterval((function(){var processingItems=getLiveUpdateProcessingItems(),uploadingItems=getLiveUpdateUploadingItems();if(0!=processingItems.length||0!=uploadingItems.length){for(var processingItem of processingItems)liveUpdatePerformAjax("processing",ocinstanceid,contextid,processingItem,reloadtimeout);for(var uploadingItem of uploadingItems)liveUpdatePerformAjax("uploading",ocinstanceid,contextid,uploadingItem,reloadtimeout)}else if(clearInterval(window.liveUpdateInterval),window.liveUpdateItemsWithError.length>0){var titles=window.liveUpdateItemsWithError.join("
  • ");str.get_string("liveupdate_fail_notification_message","block_opencast",titles).done((function(result){Notification.addNotification({message:result,type:"error"})})).fail(Notification.exception)}}),5e3,ocinstanceid,contextid,url,reloadtimeout))},getLiveUpdateItems=function(){var processingItems=getLiveUpdateProcessingItems(),uploadingItems=getLiveUpdateUploadingItems();return processingItems.concat(uploadingItems)},getLiveUpdateProcessingItems=function(){var itemsNodeList=document.getElementsByName("liveupdate_processing_item");return Array.from(itemsNodeList)},getLiveUpdateUploadingItems=function(){var itemsNodeList=document.getElementsByName("liveupdate_uploading_item");return Array.from(itemsNodeList)},liveUpdatePerformAjax=function(type,ocinstanceid,contextid,item,reloadtimeout){var _item$dataset,identifier=item.value,title=null!=item&&null!==(_item$dataset=item.dataset)&&void 0!==_item$dataset&&_item$dataset.title?item.dataset.title:"";if(null==identifier||""==title)return window.liveUpdateItemsWithError.push(title),void item.remove();Ajax.call([{methodname:"block_opencast_get_liveupdate_info",args:{contextid:contextid,ocinstanceid:ocinstanceid,type:type,identifier:identifier},done:function(status){if(""==status)return window.liveUpdateItemsWithError.push(title),void item.remove();var statusObject=JSON.parse(status);if(""!=statusObject.replace&&replaceLiveUpdateInfo(item,statusObject.replace),1==statusObject.remove){item.remove();var stringparams={timeout:reloadtimeout,title:title};str.get_string("liveupdate_toast_notification","block_opencast",stringparams).done((function(result){Toast.add(result)})).fail(Notification.exception),setTimeout((function(){window.location.reload()}),1e3*reloadtimeout)}},fail:function(){window.liveUpdateItemsWithError.push(title),item.remove()}}])},replaceLiveUpdateInfo=function(item,replace){if(null!=item&&""!=replace&&"string"==typeof replace){var newDiv=document.createElement("div");newDiv.innerHTML=replace.trim();var replaceElm=newDiv.firstChild;if("#text"==replaceElm.nodeName){item.parentNode.firstChild.remove();var newText=document.createTextNode(replace.trim());item.parentNode.insertBefore(newText,item)}else if(item.previousElementSibling){var prevElm=item.previousElementSibling;newDiv.innerHTML=replace.trim(),areElementsEqual(replaceElm,prevElm)||(prevElm.remove(),item.parentNode.insertBefore(replaceElm,item))}}},areElementsEqual=function(baseElm,checkElm){var isEqual=!0,attributes=baseElm.getAttributeNames();for(var attributeName of attributes){var baseAttributeValue=baseElm.getAttribute(attributeName).trim(),checkAttributeValue="";checkElm.hasAttribute(attributeName)&&(checkAttributeValue=checkElm.getAttribute(attributeName).trim()),""!=checkAttributeValue&&(checkAttributeValue!=baseAttributeValue&&(isEqual=!1))}return isEqual};return{init:function(courseid,ocinstanceid,contextid,liveupdate){str.get_strings([{key:"reportproblem_modal_title",component:"block_opencast"},{key:"reportproblem_modal_body",component:"block_opencast"},{key:"reportproblem_modal_placeholder",component:"block_opencast"},{key:"reportproblem_modal_required",component:"block_opencast"},{key:"reportproblem_modal_submit",component:"block_opencast"},{key:"startworkflow",component:"block_opencast"},{key:"startworkflow_modal_body",component:"block_opencast"},{key:"startworkflow_modal_description_title",component:"block_opencast"},{key:"startworkflow_modal_configpanel_title",component:"block_opencast"}]).then((function(results){!function(ocinstanceid,courseid,langstrings,contextid,liveupdate){if(document.getElementById("workflowsjson")){var workflows=JSON.parse($("#workflowsjson").text()),privacyinfohtml=null,privacytitle=null,privacyworkflows=null,hasprivacyinfo=!1;document.getElementById("workflowprivacynotice")&&(hasprivacyinfo=!0,privacyinfohtml=$("#swprivacynoticeinfotext").html(),privacytitle=$("#swprivacynoticetitle").text(),privacyworkflows=JSON.parse($("#swprivacynoticewfds").text())),$(".start-workflow").on("click",(function(e){e.preventDefault();var clickedVideo=$(e.currentTarget),select='";var privacynoticediv="";hasprivacyinfo&&(privacynoticediv='
    ',privacynoticediv+=""+privacytitle+"",privacynoticediv+='
    '+privacyinfohtml+"
    ",privacynoticediv+="
    ");var workflowdescdiv='
    '+langstrings[7]+'

    ',workflowconfigpaneldiv='
    '+langstrings[8]+'
    ',body='

    '+langstrings[6]+"

    "+select+workflowdescdiv+privacynoticediv+workflowconfigpaneldiv+"
    ";ModalFactory.create({type:ModalFactory.types.SAVE_CANCEL,title:langstrings[5],body:body},void 0).then((function(modal){pauseLiveUpdate(liveupdate),modal.setSaveButtonText(langstrings[5]);var root=modal.getRoot();root.on(ModalEvents.save,(function(e){$("#config-frame").is(":visible")?(document.getElementById("config-frame").contentWindow.postMessage("getdata","*"),e.preventDefault()):$("#startWorkflowForm").submit()})),root.on(ModalEvents.hidden,(function(){resumeLiveUpdate(ocinstanceid,contextid,liveupdate),modal.destroy()})),modal.show().then((function(){const workflowselect=$("#workflowselect");let workflowid=workflowselect.val();displayWorkflowDescription(workflows[workflowid]),displayWorkflowConfigPanel(ocinstanceid,courseid,workflowid),displayWorkflowPrivacyNotice(privacyworkflows,workflowid),workflowselect.change((function(){let workflowid=$(this).val();displayWorkflowDescription(workflows[workflowid]),displayWorkflowConfigPanel(ocinstanceid,courseid,workflowid),displayWorkflowPrivacyNotice(privacyworkflows,workflowid)}))})).catch(Notification.exception)})).catch(Notification.exception)}))}}(ocinstanceid,courseid,results,contextid,liveupdate),function(ocinstanceid,courseid,langstrings,contextid,liveupdate){$(".report-problem").on("click",(function(e){e.preventDefault();var clickedVideo=$(e.currentTarget);ModalFactory.create({type:ModalFactory.types.SAVE_CANCEL,title:langstrings[0],body:'
    '+langstrings[3]+"
    "}).then((function(modal){pauseLiveUpdate(liveupdate),modal.setSaveButtonText(langstrings[4]);var root=modal.getRoot();root.on(ModalEvents.save,(function(e){$("#inputMessage").val()?$("#reportProblemForm").submit():($("#inputMessage").addClass("is-invalid"),$("#messageValidation").removeClass("d-none")),e.preventDefault()})),root.on(ModalEvents.hidden,(function(){resumeLiveUpdate(ocinstanceid,contextid,liveupdate),modal.destroy()})),modal.show()})).catch(Notification.exception)}))}(ocinstanceid,courseid,results,contextid,liveupdate)})).catch(Notification.exception),window.addEventListener("message",(function(event){"null"===event.origin&&(event.data===parseInt(event.data)?$("#config-frame").height(event.data):($("#configparams").val(event.data),$("#startWorkflowForm").submit()))})),liveupdate.enabled&&initLiveUpdate(ocinstanceid,contextid,liveupdate.timeout),$(".access-link-copytoclipboard").on("click",(function(e){e.preventDefault();var link=e.currentTarget.getAttribute("href");link?navigator.clipboard?navigator.clipboard.writeText(link).then((()=>{str.get_string("directaccess_copy_success","block_opencast").done((function(result){Toast.add(result)})).fail(Notification.exception)})).catch():str.get_string("directaccess_copytoclipboard_unavialable","block_opencast").done((function(result){Toast.add(result,{type:"danger",autohide:!1,closeButton:!0})})).fail(Notification.exception):str.get_string("directaccess_copy_no_link","block_opencast").done((function(result){Toast.add(result,{type:"warning"})})).fail(Notification.exception)}))}}})); +define("block_opencast/block_index",["jquery","core/modal_factory","core/modal_events","core/str","core/url","core/notification","core/toast","core/ajax"],(function($,ModalFactory,ModalEvents,str,url,Notification,Toast,Ajax){window.liveUpdateInterval=null,window.liveUpdateItemsWithError=[],window.liveUpdateState=null;var pauseLiveUpdate=function(liveupdate){liveupdate.enabled&&null!==window.liveUpdateInterval&&(clearInterval(window.liveUpdateInterval),window.liveUpdateState="paused")},resumeLiveUpdate=function(ocinstanceid,contextid,liveupdate){liveupdate.enabled&&"paused"==window.liveUpdateState&&(initLiveUpdate(ocinstanceid,contextid,liveupdate.timeout),window.liveUpdateState="resumed")},displayWorkflowPrivacyNotice=function(privacyworkflows,workflowid){Array.isArray(privacyworkflows)&&(0===privacyworkflows.length||privacyworkflows.includes(workflowid))?$("#privacynoticediv").removeClass("d-none"):$("#privacynoticediv").addClass("d-none")},displayWorkflowDescription=function(workflowobj){null!=workflowobj&&workflowobj.description?($("#workflowdescdiv").removeClass("d-none"),$("#workflowdesc").html(workflowobj.description)):$("#workflowdescdiv").addClass("d-none")},displayWorkflowConfigPanel=function(ocinstanceid,courseid,workflowid){$("#workflowconfigpaneldiv").addClass("d-none"),$("#workflowconfigpanelloading").removeClass("d-none"),$("#config-frame").attr("src","");var configpanelsrc=url.relativeUrl("blocks/opencast/serveworkflowconfigpanel.php",{ocinstanceid:ocinstanceid,courseid:courseid,workflowid:workflowid});$.ajax({url:configpanelsrc,success:data=>{""!==data.trim()&&($("#workflowconfigpaneldiv").removeClass("d-none"),$("#config-frame").attr("src",configpanelsrc))},async:!1})},initLiveUpdate=function(ocinstanceid,contextid,reloadtimeout){null!==window.liveUpdateInterval&&clearInterval(window.liveUpdateInterval),window.liveUpdateItemsWithError=[],getLiveUpdateItems().length&&(window.liveUpdateInterval=setInterval((function(){if("paused"!==window.liveUpdateState){var processingItems=getLiveUpdateProcessingItems(),uploadingItems=getLiveUpdateUploadingItems();if(0!=processingItems.length||0!=uploadingItems.length){for(var processingItem of processingItems)liveUpdatePerformAjax("processing",ocinstanceid,contextid,processingItem,reloadtimeout);for(var uploadingItem of uploadingItems)liveUpdatePerformAjax("uploading",ocinstanceid,contextid,uploadingItem,reloadtimeout)}else if(clearInterval(window.liveUpdateInterval),window.liveUpdateItemsWithError.length>0){var titles=window.liveUpdateItemsWithError.join("
  • ");str.get_string("liveupdate_fail_notification_message","block_opencast",titles).done((function(result){Notification.addNotification({message:result,type:"error"})})).fail(Notification.exception)}}}),5e3,ocinstanceid,contextid,url,reloadtimeout))},getLiveUpdateItems=function(){var processingItems=getLiveUpdateProcessingItems(),uploadingItems=getLiveUpdateUploadingItems();return processingItems.concat(uploadingItems)},getLiveUpdateProcessingItems=function(){var itemsNodeList=document.getElementsByName("liveupdate_processing_item");return Array.from(itemsNodeList)},getLiveUpdateUploadingItems=function(){var itemsNodeList=document.getElementsByName("liveupdate_uploading_item");return Array.from(itemsNodeList)},liveUpdatePerformAjax=function(type,ocinstanceid,contextid,item,reloadtimeout){var _item$dataset,identifier=item.value,title=null!=item&&null!==(_item$dataset=item.dataset)&&void 0!==_item$dataset&&_item$dataset.title?item.dataset.title:"";if(null==identifier||""==title)return window.liveUpdateItemsWithError.push(title),void item.remove();Ajax.call([{methodname:"block_opencast_get_liveupdate_info",args:{contextid:contextid,ocinstanceid:ocinstanceid,type:type,identifier:identifier},done:function(status){if(""==status)return window.liveUpdateItemsWithError.push(title),void item.remove();var statusObject=JSON.parse(status);if(""!=statusObject.replace&&replaceLiveUpdateInfo(item,statusObject.replace),1==statusObject.remove){item.remove();var stringparams={timeout:reloadtimeout,title:title};str.get_string("liveupdate_toast_notification","block_opencast",stringparams).done((function(result){Toast.add(result)})).fail(Notification.exception),setTimeout((function(){window.location.reload()}),1e3*reloadtimeout)}},fail:function(){window.liveUpdateItemsWithError.push(title),item.remove()}}])},replaceLiveUpdateInfo=function(item,replace){if(null!=item&&""!=replace&&"string"==typeof replace){var newDiv=document.createElement("div");newDiv.innerHTML=replace.trim();var replaceElm=newDiv.firstChild;if("#text"==replaceElm.nodeName){item.parentNode.firstChild.remove();var newText=document.createTextNode(replace.trim());item.parentNode.insertBefore(newText,item)}else if(item.previousElementSibling){var prevElm=item.previousElementSibling;newDiv.innerHTML=replace.trim(),areElementsEqual(replaceElm,prevElm)||(prevElm.remove(),item.parentNode.insertBefore(replaceElm,item))}}},areElementsEqual=function(baseElm,checkElm){var isEqual=!0,attributes=baseElm.getAttributeNames();for(var attributeName of attributes){var baseAttributeValue=baseElm.getAttribute(attributeName).trim(),checkAttributeValue="";checkElm.hasAttribute(attributeName)&&(checkAttributeValue=checkElm.getAttribute(attributeName).trim()),""!=checkAttributeValue&&(checkAttributeValue!=baseAttributeValue&&(isEqual=!1))}return isEqual},resetVideosTableBulkActions=function(){$(".opencast-videos-table-massactions").val(""),$(".opencast-videos-table-massactions").attr("disabled",!0),$("input.opencast-videos-selectall, input.opencast-video-select").prop("checked",!1)};return{init:function(courseid,ocinstanceid,contextid,liveupdate){str.get_strings([{key:"reportproblem_modal_title",component:"block_opencast"},{key:"reportproblem_modal_body",component:"block_opencast"},{key:"reportproblem_modal_placeholder",component:"block_opencast"},{key:"reportproblem_modal_required",component:"block_opencast"},{key:"reportproblem_modal_submit",component:"block_opencast"},{key:"startworkflow",component:"block_opencast"},{key:"startworkflow_modal_body",component:"block_opencast"},{key:"startworkflow_modal_description_title",component:"block_opencast"},{key:"startworkflow_modal_configpanel_title",component:"block_opencast"},{key:"videostable_massaction_startworkflow_modal_body",component:"block_opencast"},{key:"videostable_massaction_startworkflow_modal_title",component:"block_opencast"}]).then((function(results){!function(ocinstanceid,courseid,langstrings,contextid,liveupdate){if(document.getElementById("workflowsjson")){var workflows=JSON.parse($("#workflowsjson").text()),privacyinfohtml=null,privacytitle=null,privacyworkflows=null,hasprivacyinfo=!1;document.getElementById("workflowprivacynotice")&&(hasprivacyinfo=!0,privacyinfohtml=$("#swprivacynoticeinfotext").html(),privacytitle=$("#swprivacynoticetitle").text(),privacyworkflows=JSON.parse($("#swprivacynoticewfds").text())),$(".start-workflow").on("click",(function(e){e.preventDefault();const detail=(null==e?void 0:e.detail)||{};var clickedVideo=$(e.currentTarget),actionurl=url.relativeUrl("blocks/opencast/startworkflow.php",{ocinstanceid:ocinstanceid,courseid:courseid,videoid:clickedVideo.data("id")}),ismassaction=!1,bulkinfodiv="";if("bulk"===(null==detail?void 0:detail.type)&&null!=detail&&detail.selectedids){ismassaction=!0,bulkinfodiv='
    ',bulkinfodiv+="

    "+langstrings[9].replace("{$a}",detail.selectedtitles.join("

  • "))+"

    ",bulkinfodiv+="";for(let videoid of detail.selectedids)bulkinfodiv+='';bulkinfodiv+='',actionurl=url.relativeUrl(detail.url,{ocinstanceid:ocinstanceid,courseid:courseid})}var select='";var privacynoticediv="";hasprivacyinfo&&(privacynoticediv='
    ',privacynoticediv+=""+privacytitle+"",privacynoticediv+='
    '+privacyinfohtml+"
    ",privacynoticediv+="
    ");var workflowdescdiv='
    '+langstrings[7]+'

    ',workflowconfigpaneldiv='
    '+langstrings[8]+'
    ',body='
    '+bulkinfodiv+"

    "+langstrings[6]+"

    "+select+workflowdescdiv+privacynoticediv+workflowconfigpaneldiv+"
    ";ModalFactory.create({type:ModalFactory.types.SAVE_CANCEL,title:ismassaction?langstrings[10]:langstrings[5],body:body},void 0).then((function(modal){pauseLiveUpdate(liveupdate),modal.setSaveButtonText(langstrings[5]);var root=modal.getRoot();root.on(ModalEvents.save,(function(e){$("#config-frame").is(":visible")?(document.getElementById("config-frame").contentWindow.postMessage("getdata","*"),e.preventDefault()):$("#startWorkflowForm").submit()})),root.on(ModalEvents.hidden,(function(){resumeLiveUpdate(ocinstanceid,contextid,liveupdate),modal.destroy(),resetVideosTableBulkActions()})),modal.show().then((function(){const workflowselect=$("#workflowselect");let workflowid=workflowselect.val();displayWorkflowDescription(workflows[workflowid]),displayWorkflowConfigPanel(ocinstanceid,courseid,workflowid),displayWorkflowPrivacyNotice(privacyworkflows,workflowid),workflowselect.change((function(){let workflowid=$(this).val();displayWorkflowDescription(workflows[workflowid]),displayWorkflowConfigPanel(ocinstanceid,courseid,workflowid),displayWorkflowPrivacyNotice(privacyworkflows,workflowid)}))})).catch(Notification.exception)})).catch(Notification.exception)}))}}(ocinstanceid,courseid,results,contextid,liveupdate),function(ocinstanceid,courseid,langstrings,contextid,liveupdate){$(".report-problem").on("click",(function(e){e.preventDefault();var clickedVideo=$(e.currentTarget);ModalFactory.create({type:ModalFactory.types.SAVE_CANCEL,title:langstrings[0],body:'
    '+langstrings[3]+"
    "}).then((function(modal){pauseLiveUpdate(liveupdate),modal.setSaveButtonText(langstrings[4]);var root=modal.getRoot();root.on(ModalEvents.save,(function(e){$("#inputMessage").val()?$("#reportProblemForm").submit():($("#inputMessage").addClass("is-invalid"),$("#messageValidation").removeClass("d-none")),e.preventDefault()})),root.on(ModalEvents.hidden,(function(){resumeLiveUpdate(ocinstanceid,contextid,liveupdate),modal.destroy()})),modal.show()})).catch(Notification.exception)}))}(ocinstanceid,courseid,results,contextid,liveupdate)})).catch(Notification.exception),window.addEventListener("message",(function(event){"null"===event.origin&&(event.data===parseInt(event.data)?$("#config-frame").height(event.data):($("#configparams").val(event.data),$("#startWorkflowForm").submit()))})),liveupdate.enabled&&initLiveUpdate(ocinstanceid,contextid,liveupdate.timeout),$(".access-link-copytoclipboard").on("click",(function(e){e.preventDefault();var link=e.currentTarget.getAttribute("href");link?navigator.clipboard?navigator.clipboard.writeText(link).then((()=>{str.get_string("directaccess_copy_success","block_opencast").done((function(result){Toast.add(result)})).fail(Notification.exception)})).catch():str.get_string("directaccess_copytoclipboard_unavialable","block_opencast").done((function(result){Toast.add(result,{type:"danger",autohide:!1,closeButton:!0})})).fail(Notification.exception):str.get_string("directaccess_copy_no_link","block_opencast").done((function(result){Toast.add(result,{type:"warning"})})).fail(Notification.exception)}))}}})); //# sourceMappingURL=block_index.min.js.map \ No newline at end of file diff --git a/amd/build/block_index.min.js.map b/amd/build/block_index.min.js.map index c9713bf5..c5618391 100644 --- a/amd/build/block_index.min.js.map +++ b/amd/build/block_index.min.js.map @@ -1 +1 @@ -{"version":3,"file":"block_index.min.js","sources":["../src/block_index.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript to initialise the opencast block.\n *\n * @module block_opencast\n * @copyright 2021 Tamara Gunkel, University of Münster\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['jquery', 'core/modal_factory', 'core/modal_events',\n 'core/str', 'core/url', 'core/notification', 'core/toast', 'core/ajax'],\n function($, ModalFactory, ModalEvents, str, url, Notification, Toast, Ajax) {\n /**\n * Instantiate the window variable in order to work with Intervals\n *\n */\n window.liveUpdateInterval = null;\n window.liveUpdateItemsWithError = [];\n window.liveUpdateState = null;\n\n var pauseLiveUpdate = function(liveupdate) {\n if (!liveupdate.enabled) {\n return;\n }\n if (window.liveUpdateInterval !== null) {\n clearInterval(window.liveUpdateInterval);\n window.liveUpdateState = 'paused';\n }\n };\n\n var resumeLiveUpdate = function(ocinstanceid, contextid, liveupdate) {\n if (!liveupdate.enabled) {\n return;\n }\n if (window.liveUpdateState == 'paused') {\n initLiveUpdate(ocinstanceid, contextid, liveupdate.timeout);\n window.liveUpdateState = 'resumed';\n }\n };\n\n var initWorkflowModal = function(ocinstanceid, courseid, langstrings, contextid, liveupdate) {\n if (document.getElementById('workflowsjson')) {\n var workflows = JSON.parse($('#workflowsjson').text());\n var privacyinfohtml = null;\n var privacytitle = null;\n var privacyworkflows = null;\n var hasprivacyinfo = false;\n if (document.getElementById('workflowprivacynotice')) {\n hasprivacyinfo = true;\n privacyinfohtml = $('#swprivacynoticeinfotext').html();\n privacytitle = $('#swprivacynoticetitle').text();\n privacyworkflows = JSON.parse($('#swprivacynoticewfds').text());\n }\n\n $('.start-workflow').on('click', function(e) {\n e.preventDefault();\n var clickedVideo = $(e.currentTarget);\n var select = '';\n\n var privacynoticediv = '';\n if (hasprivacyinfo) {\n privacynoticediv = '
    ';\n privacynoticediv += '' + privacytitle + '';\n privacynoticediv += '
    ' + privacyinfohtml + '
    ';\n privacynoticediv += '
    ';\n }\n\n var workflowdescdiv = '
    ' + langstrings[7] +\n '

    ';\n\n var workflowconfigpaneldiv = '
    ' +\n '' + langstrings[8] + '' +\n '
    ';\n\n var body = '
    ' +\n '

    ' + langstrings[6] + '

    ' +\n select +\n workflowdescdiv +\n privacynoticediv +\n workflowconfigpaneldiv +\n '
    ' +\n '
    ';\n\n ModalFactory.create({\n type: ModalFactory.types.SAVE_CANCEL,\n title: langstrings[5],\n body: body\n }, undefined)\n .then(function(modal) {\n // Pause the live update if it is running.\n pauseLiveUpdate(liveupdate);\n modal.setSaveButtonText(langstrings[5]);\n var root = modal.getRoot();\n root.on(ModalEvents.save, function(e) {\n // Handle form submission after receiving data, if the workflow has config panel.\n if ($('#config-frame').is(':visible')) {\n document.getElementById('config-frame').contentWindow.postMessage('getdata', '*');\n e.preventDefault();\n } else {\n // If the workflow has no config panel, we submit it directly.\n $('#startWorkflowForm').submit();\n }\n });\n root.on(ModalEvents.hidden, function() {\n // Resume the live update if it was paused.\n resumeLiveUpdate(ocinstanceid, contextid, liveupdate);\n // Destroy when hidden/closed.\n modal.destroy();\n });\n\n // Show description for initial value.\n modal.show().then(function() {\n const workflowselect = $('#workflowselect');\n let workflowid = workflowselect.val();\n displayWorkflowDescription(workflows[workflowid]);\n displayWorkflowConfigPanel(ocinstanceid, courseid, workflowid);\n // The first time to check if the privacy notice must be displayed.\n displayWorkflowPrivacyNotice(privacyworkflows, workflowid);\n\n // Show workflow description when selected.\n workflowselect.change(function() {\n let workflowid = $(this).val();\n displayWorkflowDescription(workflows[workflowid]);\n displayWorkflowConfigPanel(ocinstanceid, courseid, workflowid);\n // After each change, check if the selected workflow has to be displayed.\n displayWorkflowPrivacyNotice(privacyworkflows, workflowid);\n });\n return;\n }).catch(Notification.exception);\n return;\n }).catch(Notification.exception);\n });\n }\n };\n\n /**\n * Helper function to display the privacy notice in workflow modal dialog.\n * @param {Array} privacyworkflows an array list of workflows to display privacy notice for.\n * @param {string} workflowid workflow def id\n */\n var displayWorkflowPrivacyNotice = function(privacyworkflows, workflowid) {\n if (Array.isArray(privacyworkflows) && (privacyworkflows.length === 0 || privacyworkflows.includes(workflowid))) {\n $('#privacynoticediv').removeClass('d-none');\n } else {\n $('#privacynoticediv').addClass('d-none');\n }\n };\n\n /**\n * Helper function to display the description of the workflow.\n * @param {Object} workflowobj the workflow object\n */\n var displayWorkflowDescription = function(workflowobj) {\n if (workflowobj?.description) {\n $('#workflowdescdiv').removeClass('d-none');\n $('#workflowdesc').html(workflowobj.description);\n } else {\n $('#workflowdescdiv').addClass('d-none');\n }\n };\n\n /**\n * Helper function to display Workflow configurration panel.\n * @param {string} ocinstanceid oc instance id\n * @param {string} courseid course id\n * @param {string} workflowid workflow def id\n */\n var displayWorkflowConfigPanel = function(ocinstanceid, courseid, workflowid) {\n $('#workflowconfigpaneldiv').addClass('d-none');\n $('#workflowconfigpanelloading').removeClass('d-none');\n $('#config-frame').attr('src', '');\n var configpanelsrc = url.relativeUrl('blocks/opencast/serveworkflowconfigpanel.php', {\n 'ocinstanceid': ocinstanceid,\n 'courseid': courseid,\n 'workflowid': workflowid\n });\n $.ajax({\n url: configpanelsrc,\n success: (data) => {\n if (data.trim() !== '') {\n $('#workflowconfigpaneldiv').removeClass('d-none');\n $('#config-frame').attr('src', configpanelsrc);\n }\n },\n async: false\n });\n };\n\n var initReportModal = function(ocinstanceid, courseid, langstrings, contextid, liveupdate) {\n $('.report-problem').on('click', function(e) {\n e.preventDefault();\n var clickedVideo = $(e.currentTarget);\n ModalFactory.create({\n type: ModalFactory.types.SAVE_CANCEL,\n title: langstrings[0],\n body: '
    ' +\n '' +\n '' +\n '
    ' + langstrings[3] + '
    ' +\n '
    '\n })\n .then(function(modal) {\n // Pause the live update if it is running.\n pauseLiveUpdate(liveupdate);\n modal.setSaveButtonText(langstrings[4]);\n var root = modal.getRoot();\n root.on(ModalEvents.save, function(e) {\n if ($('#inputMessage').val()) {\n $('#reportProblemForm').submit();\n } else {\n $('#inputMessage').addClass('is-invalid');\n $('#messageValidation').removeClass('d-none');\n }\n e.preventDefault();\n });\n root.on(ModalEvents.hidden, function() {\n // Resume the live update if it was paused.\n resumeLiveUpdate(ocinstanceid, contextid, liveupdate);\n // Destroy when hidden/closed.\n modal.destroy();\n });\n modal.show();\n return;\n }).catch(Notification.exception);\n });\n };\n\n /*\n * Initialise the status live update in the overview page.\n */\n var initLiveUpdate = function(ocinstanceid, contextid, reloadtimeout) {\n if (window.liveUpdateInterval !== null) {\n clearInterval(window.liveUpdateInterval);\n }\n window.liveUpdateItemsWithError = [];\n var items = getLiveUpdateItems();\n if (items.length) {\n window.liveUpdateInterval = setInterval(function() {\n var processingItems = getLiveUpdateProcessingItems();\n var uploadingItems = getLiveUpdateUploadingItems();\n if (processingItems.length == 0 && uploadingItems.length == 0) {\n clearInterval(window.liveUpdateInterval);\n if (window.liveUpdateItemsWithError.length > 0) {\n var titles = window.liveUpdateItemsWithError.join('
  • ');\n str.get_string('liveupdate_fail_notification_message', 'block_opencast', titles)\n .done(function(result) {\n Notification.addNotification({\n message: result,\n type: 'error'\n });\n })\n .fail(Notification.exception);\n }\n return;\n }\n for (var processingItem of processingItems) {\n liveUpdatePerformAjax('processing', ocinstanceid, contextid, processingItem, reloadtimeout);\n }\n for (var uploadingItem of uploadingItems) {\n liveUpdatePerformAjax('uploading', ocinstanceid, contextid, uploadingItem, reloadtimeout);\n }\n }, 5000, ocinstanceid, contextid, url, reloadtimeout);\n }\n };\n\n /*\n * Gets all status live updates items (flags).\n */\n var getLiveUpdateItems = function() {\n var processingItems = getLiveUpdateProcessingItems();\n var uploadingItems = getLiveUpdateUploadingItems();\n return processingItems.concat(uploadingItems);\n };\n\n /*\n * Gets all status live updates items for Processing states.\n */\n var getLiveUpdateProcessingItems = function() {\n var itemsNodeList = document.getElementsByName('liveupdate_processing_item');\n return Array.from(itemsNodeList);\n };\n\n /*\n * Gets all status live updates items for uploading status.\n */\n var getLiveUpdateUploadingItems = function() {\n var itemsNodeList = document.getElementsByName('liveupdate_uploading_item');\n return Array.from(itemsNodeList);\n };\n\n /*\n * Perform status live update Ajax call to the backend to get the related info.\n */\n var liveUpdatePerformAjax = function(type, ocinstanceid, contextid, item, reloadtimeout) {\n var identifier = item.value;\n var title = item?.dataset?.title ? item.dataset.title : '';\n if (identifier == undefined || title == '') {\n window.liveUpdateItemsWithError.push(title);\n item.remove();\n return;\n }\n Ajax.call([{\n methodname: 'block_opencast_get_liveupdate_info',\n args: {contextid: contextid, ocinstanceid: ocinstanceid, type: type, identifier: identifier},\n done: function(status) {\n if (status == '') {\n window.liveUpdateItemsWithError.push(title);\n item.remove();\n return;\n }\n var statusObject = JSON.parse(status);\n if (statusObject.replace != '') {\n replaceLiveUpdateInfo(item, statusObject.replace);\n }\n if (statusObject.remove == true) {\n item.remove();\n var stringparams = {\n timeout: reloadtimeout,\n title: title\n };\n str.get_string('liveupdate_toast_notification', 'block_opencast', stringparams)\n .done(function(result) {\n Toast.add(result);\n })\n .fail(Notification.exception);\n setTimeout(function() {\n window.location.reload();\n }, reloadtimeout * 1000);\n }\n },\n fail: function() {\n window.liveUpdateItemsWithError.push(title);\n item.remove();\n }\n }]);\n };\n\n /*\n * Replace the new live update status with the current one for both text and DOM element.\n */\n var replaceLiveUpdateInfo = function(item, replace) {\n if (item == undefined || replace == '' || typeof replace != 'string') {\n return;\n }\n var newDiv = document.createElement('div');\n newDiv.innerHTML = replace.trim();\n var replaceElm = newDiv.firstChild;\n if (replaceElm.nodeName == '#text') {\n var prevText = item.parentNode.firstChild;\n prevText.remove();\n var newText = document.createTextNode(replace.trim());\n item.parentNode.insertBefore(newText, item);\n } else if (item.previousElementSibling) {\n var prevElm = item.previousElementSibling;\n newDiv.innerHTML = replace.trim();\n if (!areElementsEqual(replaceElm, prevElm)) {\n prevElm.remove();\n item.parentNode.insertBefore(replaceElm, item);\n }\n }\n };\n\n /*\n * Checks if the liev update DOM elements (new vs old) are equal.\n */\n var areElementsEqual = function(baseElm, checkElm) {\n var isEqual = true;\n var attributes = baseElm.getAttributeNames();\n for (var attributeName of attributes) {\n var baseAttributeValue = baseElm.getAttribute(attributeName).trim();\n var checkAttributeValue = '';\n if (checkElm.hasAttribute(attributeName)) {\n checkAttributeValue = checkElm.getAttribute(attributeName).trim();\n }\n if (checkAttributeValue == '') {\n continue;\n }\n if (checkAttributeValue != baseAttributeValue) {\n isEqual = false;\n }\n }\n return isEqual;\n };\n\n /*\n * Copies the direct access link into the clipboard.\n */\n var initCopyAccessLinkToClipboard = function() {\n $('.access-link-copytoclipboard').on('click', function(e) {\n e.preventDefault();\n var element = e.currentTarget;\n var link = element.getAttribute('href');\n if (!link) {\n str.get_string('directaccess_copy_no_link', 'block_opencast')\n .done(function(result) {\n Toast.add(result, {type: 'warning'});\n })\n .fail(Notification.exception);\n return;\n }\n\n if (navigator.clipboard) {\n navigator.clipboard.writeText(link)\n .then(() => {\n str.get_string('directaccess_copy_success', 'block_opencast')\n .done(function(result) {\n Toast.add(result);\n })\n .fail(Notification.exception);\n return;\n }).catch();\n return;\n } else {\n str.get_string('directaccess_copytoclipboard_unavialable', 'block_opencast')\n .done(function(result) {\n Toast.add(result, {type: 'danger', autohide: false, closeButton: true});\n })\n .fail(Notification.exception);\n }\n });\n };\n\n /*\n * Initialise all of the modules for the opencast block.\n */\n var init = function(courseid, ocinstanceid, contextid, liveupdate) {\n // Load strings\n var strings = [\n {\n key: 'reportproblem_modal_title',\n component: 'block_opencast'\n },\n {\n key: 'reportproblem_modal_body',\n component: 'block_opencast'\n },\n {\n key: 'reportproblem_modal_placeholder',\n component: 'block_opencast'\n },\n {\n key: 'reportproblem_modal_required',\n component: 'block_opencast'\n },\n {\n key: 'reportproblem_modal_submit',\n component: 'block_opencast'\n },\n {\n key: 'startworkflow',\n component: 'block_opencast'\n },\n {\n key: 'startworkflow_modal_body',\n component: 'block_opencast'\n },\n {\n key: 'startworkflow_modal_description_title',\n component: 'block_opencast'\n },\n {\n key: 'startworkflow_modal_configpanel_title',\n component: 'block_opencast'\n }\n ];\n str.get_strings(strings).then(function(results) {\n initWorkflowModal(ocinstanceid, courseid, results, contextid, liveupdate);\n initReportModal(ocinstanceid, courseid, results, contextid, liveupdate);\n return;\n }).catch(Notification.exception);\n window.addEventListener('message', function(event) {\n if (event.origin !== \"null\") {\n return;\n }\n\n if (event.data === parseInt(event.data)) {\n $('#config-frame').height(event.data);\n } else {\n $('#configparams').val(event.data);\n $('#startWorkflowForm').submit();\n }\n });\n if (liveupdate.enabled) {\n initLiveUpdate(ocinstanceid, contextid, liveupdate.timeout);\n }\n initCopyAccessLinkToClipboard();\n };\n\n return {\n init: init\n };\n });\n\n"],"names":["define","$","ModalFactory","ModalEvents","str","url","Notification","Toast","Ajax","window","liveUpdateInterval","liveUpdateItemsWithError","liveUpdateState","pauseLiveUpdate","liveupdate","enabled","clearInterval","resumeLiveUpdate","ocinstanceid","contextid","initLiveUpdate","timeout","displayWorkflowPrivacyNotice","privacyworkflows","workflowid","Array","isArray","length","includes","removeClass","addClass","displayWorkflowDescription","workflowobj","description","html","displayWorkflowConfigPanel","courseid","attr","configpanelsrc","relativeUrl","ajax","success","data","trim","async","reloadtimeout","getLiveUpdateItems","setInterval","processingItems","getLiveUpdateProcessingItems","uploadingItems","getLiveUpdateUploadingItems","processingItem","liveUpdatePerformAjax","uploadingItem","titles","join","get_string","done","result","addNotification","message","type","fail","exception","concat","itemsNodeList","document","getElementsByName","from","item","identifier","value","title","dataset","undefined","push","remove","call","methodname","args","status","statusObject","JSON","parse","replace","replaceLiveUpdateInfo","stringparams","add","setTimeout","location","reload","newDiv","createElement","innerHTML","replaceElm","firstChild","nodeName","parentNode","newText","createTextNode","insertBefore","previousElementSibling","prevElm","areElementsEqual","baseElm","checkElm","isEqual","attributes","getAttributeNames","attributeName","baseAttributeValue","getAttribute","checkAttributeValue","hasAttribute","init","get_strings","key","component","then","results","langstrings","getElementById","workflows","text","privacyinfohtml","privacytitle","hasprivacyinfo","on","e","preventDefault","clickedVideo","currentTarget","select","workflow","privacynoticediv","workflowdescdiv","workflowconfigpaneldiv","body","create","types","SAVE_CANCEL","modal","setSaveButtonText","root","getRoot","save","is","contentWindow","postMessage","submit","hidden","destroy","show","workflowselect","val","change","this","catch","initWorkflowModal","initReportModal","addEventListener","event","origin","parseInt","height","link","navigator","clipboard","writeText","autohide","closeButton"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"block_index.min.js","sources":["../src/block_index.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Javascript to initialise the opencast block.\n *\n * @module block_opencast\n * @copyright 2021 Tamara Gunkel, University of Münster\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\ndefine(['jquery', 'core/modal_factory', 'core/modal_events',\n 'core/str', 'core/url', 'core/notification', 'core/toast', 'core/ajax'],\n function($, ModalFactory, ModalEvents, str, url, Notification, Toast, Ajax) {\n /**\n * Instantiate the window variable in order to work with Intervals\n *\n */\n window.liveUpdateInterval = null;\n window.liveUpdateItemsWithError = [];\n window.liveUpdateState = null;\n\n var pauseLiveUpdate = function(liveupdate) {\n if (!liveupdate.enabled) {\n return;\n }\n if (window.liveUpdateInterval !== null) {\n clearInterval(window.liveUpdateInterval);\n window.liveUpdateState = 'paused';\n }\n };\n\n var resumeLiveUpdate = function(ocinstanceid, contextid, liveupdate) {\n if (!liveupdate.enabled) {\n return;\n }\n if (window.liveUpdateState == 'paused') {\n initLiveUpdate(ocinstanceid, contextid, liveupdate.timeout);\n window.liveUpdateState = 'resumed';\n }\n };\n\n var initWorkflowModal = function(ocinstanceid, courseid, langstrings, contextid, liveupdate) {\n if (document.getElementById('workflowsjson')) {\n var workflows = JSON.parse($('#workflowsjson').text());\n var privacyinfohtml = null;\n var privacytitle = null;\n var privacyworkflows = null;\n var hasprivacyinfo = false;\n if (document.getElementById('workflowprivacynotice')) {\n hasprivacyinfo = true;\n privacyinfohtml = $('#swprivacynoticeinfotext').html();\n privacytitle = $('#swprivacynoticetitle').text();\n privacyworkflows = JSON.parse($('#swprivacynoticewfds').text());\n }\n\n $('.start-workflow').on('click', function(e) {\n e.preventDefault();\n const detail = e?.detail || {};\n\n var clickedVideo = $(e.currentTarget);\n var actionurl = url.relativeUrl('blocks/opencast/startworkflow.php', {\n 'ocinstanceid': ocinstanceid,\n 'courseid': courseid,\n 'videoid': clickedVideo.data('id')\n });\n var ismassaction = false;\n var bulkinfodiv = '';\n if (detail?.type === 'bulk' && detail?.selectedids) {\n ismassaction = true;\n bulkinfodiv = '
    ';\n bulkinfodiv += '

    ' + langstrings[9].replace('{$a}', detail.selectedtitles.join('

  • ')) + '

    ';\n bulkinfodiv += '';\n for (let videoid of detail.selectedids) {\n bulkinfodiv += '';\n }\n bulkinfodiv += '';\n actionurl = url.relativeUrl(detail.url, {\n 'ocinstanceid': ocinstanceid,\n 'courseid': courseid\n });\n }\n\n var select = '';\n\n var privacynoticediv = '';\n if (hasprivacyinfo) {\n privacynoticediv = '
    ';\n privacynoticediv += '' + privacytitle + '';\n privacynoticediv += '
    ' + privacyinfohtml + '
    ';\n privacynoticediv += '
    ';\n }\n\n var workflowdescdiv = '
    ' + langstrings[7] +\n '

    ';\n\n var workflowconfigpaneldiv = '
    ' +\n '' + langstrings[8] + '' +\n '
    ';\n\n var body = '
    ' +\n bulkinfodiv +\n '

    ' + langstrings[6] + '

    ' +\n select +\n workflowdescdiv +\n privacynoticediv +\n workflowconfigpaneldiv +\n '
    ' +\n '
    ';\n\n ModalFactory.create({\n type: ModalFactory.types.SAVE_CANCEL,\n title: ismassaction ? langstrings[10] : langstrings[5],\n body: body\n }, undefined)\n .then(function(modal) {\n // Pause the live update if it is running.\n pauseLiveUpdate(liveupdate);\n modal.setSaveButtonText(langstrings[5]);\n var root = modal.getRoot();\n root.on(ModalEvents.save, function(e) {\n // Handle form submission after receiving data, if the workflow has config panel.\n if ($('#config-frame').is(':visible')) {\n document.getElementById('config-frame').contentWindow.postMessage('getdata', '*');\n e.preventDefault();\n } else {\n // If the workflow has no config panel, we submit it directly.\n $('#startWorkflowForm').submit();\n }\n });\n root.on(ModalEvents.hidden, function() {\n // Resume the live update if it was paused.\n resumeLiveUpdate(ocinstanceid, contextid, liveupdate);\n // Destroy when hidden/closed.\n modal.destroy();\n // Change the bulk action select back to choose...\n resetVideosTableBulkActions();\n });\n\n // Show description for initial value.\n modal.show().then(function() {\n const workflowselect = $('#workflowselect');\n let workflowid = workflowselect.val();\n displayWorkflowDescription(workflows[workflowid]);\n displayWorkflowConfigPanel(ocinstanceid, courseid, workflowid);\n // The first time to check if the privacy notice must be displayed.\n displayWorkflowPrivacyNotice(privacyworkflows, workflowid);\n\n // Show workflow description when selected.\n workflowselect.change(function() {\n let workflowid = $(this).val();\n displayWorkflowDescription(workflows[workflowid]);\n displayWorkflowConfigPanel(ocinstanceid, courseid, workflowid);\n // After each change, check if the selected workflow has to be displayed.\n displayWorkflowPrivacyNotice(privacyworkflows, workflowid);\n });\n return;\n }).catch(Notification.exception);\n return;\n }).catch(Notification.exception);\n });\n }\n };\n\n /**\n * Helper function to display the privacy notice in workflow modal dialog.\n * @param {Array} privacyworkflows an array list of workflows to display privacy notice for.\n * @param {string} workflowid workflow def id\n */\n var displayWorkflowPrivacyNotice = function(privacyworkflows, workflowid) {\n if (Array.isArray(privacyworkflows) && (privacyworkflows.length === 0 || privacyworkflows.includes(workflowid))) {\n $('#privacynoticediv').removeClass('d-none');\n } else {\n $('#privacynoticediv').addClass('d-none');\n }\n };\n\n /**\n * Helper function to display the description of the workflow.\n * @param {Object} workflowobj the workflow object\n */\n var displayWorkflowDescription = function(workflowobj) {\n if (workflowobj?.description) {\n $('#workflowdescdiv').removeClass('d-none');\n $('#workflowdesc').html(workflowobj.description);\n } else {\n $('#workflowdescdiv').addClass('d-none');\n }\n };\n\n /**\n * Helper function to display Workflow configurration panel.\n * @param {string} ocinstanceid oc instance id\n * @param {string} courseid course id\n * @param {string} workflowid workflow def id\n */\n var displayWorkflowConfigPanel = function(ocinstanceid, courseid, workflowid) {\n $('#workflowconfigpaneldiv').addClass('d-none');\n $('#workflowconfigpanelloading').removeClass('d-none');\n $('#config-frame').attr('src', '');\n var configpanelsrc = url.relativeUrl('blocks/opencast/serveworkflowconfigpanel.php', {\n 'ocinstanceid': ocinstanceid,\n 'courseid': courseid,\n 'workflowid': workflowid\n });\n $.ajax({\n url: configpanelsrc,\n success: (data) => {\n if (data.trim() !== '') {\n $('#workflowconfigpaneldiv').removeClass('d-none');\n $('#config-frame').attr('src', configpanelsrc);\n }\n },\n async: false\n });\n };\n\n var initReportModal = function(ocinstanceid, courseid, langstrings, contextid, liveupdate) {\n $('.report-problem').on('click', function(e) {\n e.preventDefault();\n var clickedVideo = $(e.currentTarget);\n ModalFactory.create({\n type: ModalFactory.types.SAVE_CANCEL,\n title: langstrings[0],\n body: '
    ' +\n '' +\n '' +\n '
    ' + langstrings[3] + '
    ' +\n '
    '\n })\n .then(function(modal) {\n // Pause the live update if it is running.\n pauseLiveUpdate(liveupdate);\n modal.setSaveButtonText(langstrings[4]);\n var root = modal.getRoot();\n root.on(ModalEvents.save, function(e) {\n if ($('#inputMessage').val()) {\n $('#reportProblemForm').submit();\n } else {\n $('#inputMessage').addClass('is-invalid');\n $('#messageValidation').removeClass('d-none');\n }\n e.preventDefault();\n });\n root.on(ModalEvents.hidden, function() {\n // Resume the live update if it was paused.\n resumeLiveUpdate(ocinstanceid, contextid, liveupdate);\n // Destroy when hidden/closed.\n modal.destroy();\n });\n modal.show();\n return;\n }).catch(Notification.exception);\n });\n };\n\n /*\n * Initialise the status live update in the overview page.\n */\n var initLiveUpdate = function(ocinstanceid, contextid, reloadtimeout) {\n if (window.liveUpdateInterval !== null) {\n clearInterval(window.liveUpdateInterval);\n }\n window.liveUpdateItemsWithError = [];\n var items = getLiveUpdateItems();\n if (items.length) {\n window.liveUpdateInterval = setInterval(function() {\n // Adding the state checker here, in order to pause the live update from other js modules like block_massaction.\n if (window.liveUpdateState === 'paused') {\n return;\n }\n var processingItems = getLiveUpdateProcessingItems();\n var uploadingItems = getLiveUpdateUploadingItems();\n if (processingItems.length == 0 && uploadingItems.length == 0) {\n clearInterval(window.liveUpdateInterval);\n if (window.liveUpdateItemsWithError.length > 0) {\n var titles = window.liveUpdateItemsWithError.join('
  • ');\n str.get_string('liveupdate_fail_notification_message', 'block_opencast', titles)\n .done(function(result) {\n Notification.addNotification({\n message: result,\n type: 'error'\n });\n })\n .fail(Notification.exception);\n }\n return;\n }\n for (var processingItem of processingItems) {\n liveUpdatePerformAjax('processing', ocinstanceid, contextid, processingItem, reloadtimeout);\n }\n for (var uploadingItem of uploadingItems) {\n liveUpdatePerformAjax('uploading', ocinstanceid, contextid, uploadingItem, reloadtimeout);\n }\n }, 5000, ocinstanceid, contextid, url, reloadtimeout);\n }\n };\n\n /*\n * Gets all status live updates items (flags).\n */\n var getLiveUpdateItems = function() {\n var processingItems = getLiveUpdateProcessingItems();\n var uploadingItems = getLiveUpdateUploadingItems();\n return processingItems.concat(uploadingItems);\n };\n\n /*\n * Gets all status live updates items for Processing states.\n */\n var getLiveUpdateProcessingItems = function() {\n var itemsNodeList = document.getElementsByName('liveupdate_processing_item');\n return Array.from(itemsNodeList);\n };\n\n /*\n * Gets all status live updates items for uploading status.\n */\n var getLiveUpdateUploadingItems = function() {\n var itemsNodeList = document.getElementsByName('liveupdate_uploading_item');\n return Array.from(itemsNodeList);\n };\n\n /*\n * Perform status live update Ajax call to the backend to get the related info.\n */\n var liveUpdatePerformAjax = function(type, ocinstanceid, contextid, item, reloadtimeout) {\n var identifier = item.value;\n var title = item?.dataset?.title ? item.dataset.title : '';\n if (identifier == undefined || title == '') {\n window.liveUpdateItemsWithError.push(title);\n item.remove();\n return;\n }\n Ajax.call([{\n methodname: 'block_opencast_get_liveupdate_info',\n args: {contextid: contextid, ocinstanceid: ocinstanceid, type: type, identifier: identifier},\n done: function(status) {\n if (status == '') {\n window.liveUpdateItemsWithError.push(title);\n item.remove();\n return;\n }\n var statusObject = JSON.parse(status);\n if (statusObject.replace != '') {\n replaceLiveUpdateInfo(item, statusObject.replace);\n }\n if (statusObject.remove == true) {\n item.remove();\n var stringparams = {\n timeout: reloadtimeout,\n title: title\n };\n str.get_string('liveupdate_toast_notification', 'block_opencast', stringparams)\n .done(function(result) {\n Toast.add(result);\n })\n .fail(Notification.exception);\n setTimeout(function() {\n window.location.reload();\n }, reloadtimeout * 1000);\n }\n },\n fail: function() {\n window.liveUpdateItemsWithError.push(title);\n item.remove();\n }\n }]);\n };\n\n /*\n * Replace the new live update status with the current one for both text and DOM element.\n */\n var replaceLiveUpdateInfo = function(item, replace) {\n if (item == undefined || replace == '' || typeof replace != 'string') {\n return;\n }\n var newDiv = document.createElement('div');\n newDiv.innerHTML = replace.trim();\n var replaceElm = newDiv.firstChild;\n if (replaceElm.nodeName == '#text') {\n var prevText = item.parentNode.firstChild;\n prevText.remove();\n var newText = document.createTextNode(replace.trim());\n item.parentNode.insertBefore(newText, item);\n } else if (item.previousElementSibling) {\n var prevElm = item.previousElementSibling;\n newDiv.innerHTML = replace.trim();\n if (!areElementsEqual(replaceElm, prevElm)) {\n prevElm.remove();\n item.parentNode.insertBefore(replaceElm, item);\n }\n }\n };\n\n /*\n * Checks if the liev update DOM elements (new vs old) are equal.\n */\n var areElementsEqual = function(baseElm, checkElm) {\n var isEqual = true;\n var attributes = baseElm.getAttributeNames();\n for (var attributeName of attributes) {\n var baseAttributeValue = baseElm.getAttribute(attributeName).trim();\n var checkAttributeValue = '';\n if (checkElm.hasAttribute(attributeName)) {\n checkAttributeValue = checkElm.getAttribute(attributeName).trim();\n }\n if (checkAttributeValue == '') {\n continue;\n }\n if (checkAttributeValue != baseAttributeValue) {\n isEqual = false;\n }\n }\n return isEqual;\n };\n\n /*\n * Copies the direct access link into the clipboard.\n */\n var initCopyAccessLinkToClipboard = function() {\n $('.access-link-copytoclipboard').on('click', function(e) {\n e.preventDefault();\n var element = e.currentTarget;\n var link = element.getAttribute('href');\n if (!link) {\n str.get_string('directaccess_copy_no_link', 'block_opencast')\n .done(function(result) {\n Toast.add(result, {type: 'warning'});\n })\n .fail(Notification.exception);\n return;\n }\n\n if (navigator.clipboard) {\n navigator.clipboard.writeText(link)\n .then(() => {\n str.get_string('directaccess_copy_success', 'block_opencast')\n .done(function(result) {\n Toast.add(result);\n })\n .fail(Notification.exception);\n return;\n }).catch();\n return;\n } else {\n str.get_string('directaccess_copytoclipboard_unavialable', 'block_opencast')\n .done(function(result) {\n Toast.add(result, {type: 'danger', autohide: false, closeButton: true});\n })\n .fail(Notification.exception);\n }\n });\n };\n\n /*\n * Resets the bulk action select dropdowns and unchecks the select items.\n */\n var resetVideosTableBulkActions = function () {\n $('.opencast-videos-table-massactions').val('');\n $('.opencast-videos-table-massactions').attr('disabled', true);\n $('input.opencast-videos-selectall, input.opencast-video-select').prop('checked', false);\n };\n\n /*\n * Initialise all of the modules for the opencast block.\n */\n var init = function(courseid, ocinstanceid, contextid, liveupdate) {\n // Load strings\n var strings = [\n {\n key: 'reportproblem_modal_title',\n component: 'block_opencast'\n },\n {\n key: 'reportproblem_modal_body',\n component: 'block_opencast'\n },\n {\n key: 'reportproblem_modal_placeholder',\n component: 'block_opencast'\n },\n {\n key: 'reportproblem_modal_required',\n component: 'block_opencast'\n },\n {\n key: 'reportproblem_modal_submit',\n component: 'block_opencast'\n },\n {\n key: 'startworkflow',\n component: 'block_opencast'\n },\n {\n key: 'startworkflow_modal_body',\n component: 'block_opencast'\n },\n {\n key: 'startworkflow_modal_description_title',\n component: 'block_opencast'\n },\n {\n key: 'startworkflow_modal_configpanel_title',\n component: 'block_opencast'\n },\n {\n key: 'videostable_massaction_startworkflow_modal_body',\n component: 'block_opencast'\n },\n {\n key: 'videostable_massaction_startworkflow_modal_title',\n component: 'block_opencast'\n }\n ];\n str.get_strings(strings).then(function(results) {\n initWorkflowModal(ocinstanceid, courseid, results, contextid, liveupdate);\n initReportModal(ocinstanceid, courseid, results, contextid, liveupdate);\n return;\n }).catch(Notification.exception);\n window.addEventListener('message', function(event) {\n if (event.origin !== \"null\") {\n return;\n }\n\n if (event.data === parseInt(event.data)) {\n $('#config-frame').height(event.data);\n } else {\n $('#configparams').val(event.data);\n $('#startWorkflowForm').submit();\n }\n });\n if (liveupdate.enabled) {\n initLiveUpdate(ocinstanceid, contextid, liveupdate.timeout);\n }\n initCopyAccessLinkToClipboard();\n };\n\n return {\n init: init\n };\n });\n\n"],"names":["define","$","ModalFactory","ModalEvents","str","url","Notification","Toast","Ajax","window","liveUpdateInterval","liveUpdateItemsWithError","liveUpdateState","pauseLiveUpdate","liveupdate","enabled","clearInterval","resumeLiveUpdate","ocinstanceid","contextid","initLiveUpdate","timeout","displayWorkflowPrivacyNotice","privacyworkflows","workflowid","Array","isArray","length","includes","removeClass","addClass","displayWorkflowDescription","workflowobj","description","html","displayWorkflowConfigPanel","courseid","attr","configpanelsrc","relativeUrl","ajax","success","data","trim","async","reloadtimeout","getLiveUpdateItems","setInterval","processingItems","getLiveUpdateProcessingItems","uploadingItems","getLiveUpdateUploadingItems","processingItem","liveUpdatePerformAjax","uploadingItem","titles","join","get_string","done","result","addNotification","message","type","fail","exception","concat","itemsNodeList","document","getElementsByName","from","item","identifier","value","title","dataset","undefined","push","remove","call","methodname","args","status","statusObject","JSON","parse","replace","replaceLiveUpdateInfo","stringparams","add","setTimeout","location","reload","newDiv","createElement","innerHTML","replaceElm","firstChild","nodeName","parentNode","newText","createTextNode","insertBefore","previousElementSibling","prevElm","areElementsEqual","baseElm","checkElm","isEqual","attributes","getAttributeNames","attributeName","baseAttributeValue","getAttribute","checkAttributeValue","hasAttribute","resetVideosTableBulkActions","val","prop","init","get_strings","key","component","then","results","langstrings","getElementById","workflows","text","privacyinfohtml","privacytitle","hasprivacyinfo","on","e","preventDefault","detail","clickedVideo","currentTarget","actionurl","ismassaction","bulkinfodiv","selectedids","selectedtitles","videoid","select","workflow","privacynoticediv","workflowdescdiv","workflowconfigpaneldiv","body","create","types","SAVE_CANCEL","modal","setSaveButtonText","root","getRoot","save","is","contentWindow","postMessage","submit","hidden","destroy","show","workflowselect","change","this","catch","initWorkflowModal","initReportModal","addEventListener","event","origin","parseInt","height","link","navigator","clipboard","writeText","autohide","closeButton"],"mappings":"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"} \ No newline at end of file diff --git a/amd/build/block_massaction.min.js b/amd/build/block_massaction.min.js new file mode 100644 index 00000000..7a6107cc --- /dev/null +++ b/amd/build/block_massaction.min.js @@ -0,0 +1,10 @@ +define("block_opencast/block_massaction",["exports","jquery","core/str","core/notification","core/modal_save_cancel","core/modal_events","core/url"],(function(_exports,_jquery,str,_notification,_modal_save_cancel,_modal_events,_url){function _getRequireWildcardCache(nodeInterop){if("function"!=typeof WeakMap)return null;var cacheBabelInterop=new WeakMap,cacheNodeInterop=new WeakMap;return(_getRequireWildcardCache=function(nodeInterop){return nodeInterop?cacheNodeInterop:cacheBabelInterop})(nodeInterop)}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}} +/** + * Javascript module to instantiate the Mass-Action functionality. + * + * @module block_opencast + * @copyright 2024 Farbod Zamani Boroujeni (elan e.V.) (zamani@elan-ev.de) + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_jquery=_interopRequireDefault(_jquery),str=function(obj,nodeInterop){if(!nodeInterop&&obj&&obj.__esModule)return obj;if(null===obj||"object"!=typeof obj&&"function"!=typeof obj)return{default:obj};var cache=_getRequireWildcardCache(nodeInterop);if(cache&&cache.has(obj))return cache.get(obj);var newObj={},hasPropertyDescriptor=Object.defineProperty&&Object.getOwnPropertyDescriptor;for(var key in obj)if("default"!==key&&Object.prototype.hasOwnProperty.call(obj,key)){var desc=hasPropertyDescriptor?Object.getOwnPropertyDescriptor(obj,key):null;desc&&(desc.get||desc.set)?Object.defineProperty(newObj,key,desc):newObj[key]=obj[key]}newObj.default=obj,cache&&cache.set(obj,newObj);return newObj}(str),_notification=_interopRequireDefault(_notification),_modal_save_cancel=_interopRequireDefault(_modal_save_cancel),_modal_events=_interopRequireDefault(_modal_events),_url=_interopRequireDefault(_url);_exports.init=(courseid,ocinstanceid,selectors)=>{[...document.querySelectorAll(selectors.dropdown)].forEach((dropdown=>{dropdown.addEventListener("change",(e=>{var _actionsmapping$actio,_actionsmapping$actio2,_actionsmapping$actio3;const element=e.currentTarget,id=element.getAttribute("id"),action=element.value,populatedselector="".concat(selectors.dropdown,":not(#").concat(id,")"),otherdropdowns=[...document.querySelectorAll(populatedselector)];if(otherdropdowns.length&&otherdropdowns.forEach((otherdropdown=>{otherdropdown.value=action})),""===action)return;const selectedvideos=[...document.querySelectorAll("".concat(selectors.selectitem,":checked"))];if(!selectedvideos.length)return;const selectedids=selectedvideos.map((element=>element.id.substring(7))),selectedtitles=selectedvideos.map((element=>element.name.substring(7))),actionsmappinginput=document.getElementById(selectors.actionmapping),actionsmappingraw=actionsmappinginput?actionsmappinginput.value:null;if(null===actionsmappingraw)return;const actionsmapping=JSON.parse(actionsmappingraw);if(null==actionsmapping||null===(_actionsmapping$actio=actionsmapping[action])||void 0===_actionsmapping$actio||null===(_actionsmapping$actio2=_actionsmapping$actio.path)||void 0===_actionsmapping$actio2||!_actionsmapping$actio2.url)return;if("startworkflow"===action){const data={type:"bulk",selectedids:selectedids,selectedtitles:selectedtitles,url:actionsmapping[action].path.url},event=new CustomEvent("click",{detail:data});return void document.querySelector(".start-workflow").dispatchEvent(event)}const stringskeys=[{key:"videostable_massaction_"+action+"_modal_title",component:"block_opencast"},{key:"videostable_massaction_"+action+"_modal_body",component:"block_opencast",param:selectedtitles.join("
  • ")},{key:"videostable_massaction_"+action,component:"block_opencast"}],strPromise=str.get_strings(stringskeys),modalPromise=_modal_save_cancel.default.create({});var urlParams={ocinstanceid:ocinstanceid,courseid:courseid};null!==(_actionsmapping$actio3=actionsmapping[action].path)&&void 0!==_actionsmapping$actio3&&_actionsmapping$actio3.params&&(urlParams=Object.assign(urlParams,actionsmapping[action].path.params));const actionUrl=_url.default.relativeUrl(actionsmapping[action].path.url,urlParams);_jquery.default.when(strPromise,modalPromise).then((function(strings,modal){window.liveUpdateState="paused",modal.setTitle(strings[0]);var body='
    ';body+="

    "+strings[1]+"

    ";for(let selectedid of selectedids)body+='';return body+='',body+="
    ",modal.setBody(body),modal.setSaveButtonText(strings[2]),modal.getRoot().on(_modal_events.default.save,(function(){window.liveUpdateState="resumed",document.getElementById("mass_action_confirmation_form").submit()})),modal.getRoot().on(_modal_events.default.hidden,(function(){window.liveUpdateState="resumed",modal.destroy(),resetVideosTableBulkActions(selectors)})),modal.show(),modal})).fail(_notification.default.exception)}))}))};const resetVideosTableBulkActions=selectors=>{[...document.querySelectorAll(selectors.dropdown)].forEach((dropdown=>{dropdown.value="",dropdown.setAttribute("disabled",!0)}));[...document.querySelectorAll("".concat(selectors.selectall,", ").concat(selectors.selectitem))].forEach((input=>{input.checked=!1}))}})); + +//# sourceMappingURL=block_massaction.min.js.map \ No newline at end of file diff --git a/amd/build/block_massaction.min.js.map b/amd/build/block_massaction.min.js.map new file mode 100644 index 00000000..5535ebf1 --- /dev/null +++ b/amd/build/block_massaction.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"block_massaction.min.js","sources":["../src/block_massaction.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 to instantiate the Mass-Action functionality.\n *\n * @module block_opencast\n * @copyright 2024 Farbod Zamani Boroujeni (elan e.V.) (zamani@elan-ev.de)\n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport $ from 'jquery';\nimport * as str from 'core/str';\nimport Notification from 'core/notification';\nimport ModalSaveCancel from 'core/modal_save_cancel';\nimport ModalEvents from 'core/modal_events';\nimport url from 'core/url';\n\n/**\n * Initializes the mass action functionality for the Opencast block.\n * This function sets up event listeners for dropdown changes, handles video selection,\n * and manages the modal dialogs for different actions.\n *\n * @param {number} courseid - The ID of the current course.\n * @param {number} ocinstanceid - The ID of the Opencast instance.\n * @param {Object} selectors - An object containing CSS/Id selectors for various elements.\n * @param {string} selectors.dropdown - Selector for the action dropdown elements.\n * @param {string} selectors.selectitem - Selector for the checkbox elements to select individual videos.\n * @param {string} selectors.actionmapping - Selector for the element containing action mapping data.\n * @param {string} selectors.selectall - Selector for the \"select all\" checkbox.\n * @returns {void} This function does not return a value.\n */\nexport const init = (courseid, ocinstanceid, selectors) => {\n\n const dropdowns = [...document.querySelectorAll(selectors.dropdown)];\n dropdowns.forEach(dropdown => {\n dropdown.addEventListener('change', e => {\n const element = e.currentTarget;\n const id = element.getAttribute('id');\n const action = element.value;\n\n // Make sure other bulk select get the same value.\n const populatedselector = `${selectors.dropdown}:not(#${id})`;\n const otherdropdowns = [...document.querySelectorAll(populatedselector)];\n if (otherdropdowns.length) {\n otherdropdowns.forEach(otherdropdown => {\n otherdropdown.value = action;\n });\n }\n\n if (action === '') {\n return;\n }\n\n const selectedvideos = [...document.querySelectorAll(`${selectors.selectitem}:checked`)];\n if (!selectedvideos.length) {\n return;\n }\n const selectedids = selectedvideos.map(element => element.id.substring(7));\n const selectedtitles = selectedvideos.map(element => element.name.substring(7));\n\n const actionsmappinginput = document.getElementById(selectors.actionmapping);\n const actionsmappingraw = actionsmappinginput ? actionsmappinginput.value : null;\n if (actionsmappingraw === null) {\n return;\n }\n const actionsmapping = JSON.parse(actionsmappingraw);\n // Make sure that the action url is there.\n if (!actionsmapping?.[action]?.path?.url) {\n return;\n }\n\n // Because of using Modal for start workflow tasks, we don't provide a confirmation modal beforehand,\n // but instead we provide the confirmation texts in existing startworkflow modal.\n if (action === 'startworkflow') {\n\n const data = {\n type: 'bulk',\n selectedids: selectedids,\n selectedtitles: selectedtitles,\n url: actionsmapping[action].path.url\n };\n\n // Create and dispatch the custom event on start-workflow element with detail data.\n const event = new CustomEvent('click', {detail: data});\n document.querySelector('.start-workflow').dispatchEvent(event);\n return; // We stop the function here!\n }\n\n const stringskeys = [\n {\n key: 'videostable_massaction_' + action + '_modal_title',\n component: 'block_opencast'\n },\n {\n key: 'videostable_massaction_' + action + '_modal_body',\n component: 'block_opencast',\n param: selectedtitles.join('
  • ')\n },\n {\n key: 'videostable_massaction_' + action,\n component: 'block_opencast'\n },\n ];\n const strPromise = str.get_strings(stringskeys);\n\n const modalPromise = ModalSaveCancel.create({});\n\n var urlParams = {\n 'ocinstanceid': ocinstanceid,\n 'courseid': courseid\n };\n\n if (actionsmapping[action].path?.params) {\n urlParams = Object.assign(urlParams, actionsmapping[action].path.params);\n }\n\n const actionUrl = url.relativeUrl(actionsmapping[action].path.url, urlParams);\n\n $.when(strPromise, modalPromise).then(function(strings, modal) {\n // Pause the live update if it is running.\n window.liveUpdateState = 'paused';\n modal.setTitle(strings[0]);\n var body = '
    ';\n body += '

    ' + strings[1] + '

    ';\n for (let selectedid of selectedids) {\n body += '';\n }\n body += '';\n body += '
    ';\n modal.setBody(body);\n modal.setSaveButtonText(strings[2]);\n modal.getRoot().on(ModalEvents.save, function() {\n // Resume the live update if it was paused.\n window.liveUpdateState = 'resumed';\n document.getElementById('mass_action_confirmation_form').submit();\n });\n modal.getRoot().on(ModalEvents.hidden, function() {\n // Resume the live update if it was paused.\n window.liveUpdateState = 'resumed';\n // Destroy when hidden/closed.\n modal.destroy();\n // Change the bulk action select back to choose...\n resetVideosTableBulkActions(selectors);\n });\n modal.show();\n return modal;\n }).fail(Notification.exception);\n });\n });\n};\n\n/**\n * Resets the bulk action select dropdowns and unchecks the select items.\n * This function is called when the modal is hidden/closed.\n *\n * @param {Object} selectors - An object containing CSS/Id selectors for various elements.\n * @param {string} selectors.dropdown - Selector for the action dropdown elements.\n * @param {string} selectors.selectitem - Selector for the checkbox elements to select individual videos.\n * @param {string} selectors.actionmapping - Selector for the element containing action mapping data.\n * @param {string} selectors.selectall - Selector for the \"select all\" checkbox.\n * @returns {void} This function does not return a value.\n */\nconst resetVideosTableBulkActions = (selectors) => {\n const dropdowns = [...document.querySelectorAll(selectors.dropdown)];\n dropdowns.forEach(dropdown => {\n dropdown.value = '';\n dropdown.setAttribute('disabled', true);\n });\n\n const ckinputs = [...document.querySelectorAll(`${selectors.selectall}, ${selectors.selectitem}`)];\n ckinputs.forEach(input => {\n input.checked = false;\n });\n};\n"],"names":["courseid","ocinstanceid","selectors","document","querySelectorAll","dropdown","forEach","addEventListener","e","element","currentTarget","id","getAttribute","action","value","populatedselector","otherdropdowns","length","otherdropdown","selectedvideos","selectitem","selectedids","map","substring","selectedtitles","name","actionsmappinginput","getElementById","actionmapping","actionsmappingraw","actionsmapping","JSON","parse","_actionsmapping$actio","path","_actionsmapping$actio2","url","data","type","event","CustomEvent","detail","querySelector","dispatchEvent","stringskeys","key","component","param","join","strPromise","str","get_strings","modalPromise","ModalSaveCancel","create","urlParams","_actionsmapping$actio3","params","Object","assign","actionUrl","relativeUrl","when","then","strings","modal","window","liveUpdateState","setTitle","body","selectedid","setBody","setSaveButtonText","getRoot","on","ModalEvents","save","submit","hidden","destroy","resetVideosTableBulkActions","show","fail","Notification","exception","setAttribute","selectall","input","checked"],"mappings":";;;;;;;s+BA4CoB,CAACA,SAAUC,aAAcC,aAEvB,IAAIC,SAASC,iBAAiBF,UAAUG,WAChDC,SAAQD,WACdA,SAASE,iBAAiB,UAAUC,kFAC1BC,QAAUD,EAAEE,cACZC,GAAKF,QAAQG,aAAa,MAC1BC,OAASJ,QAAQK,MAGjBC,4BAAuBb,UAAUG,0BAAiBM,QAClDK,eAAiB,IAAIb,SAASC,iBAAiBW,uBACjDC,eAAeC,QACfD,eAAeV,SAAQY,gBACnBA,cAAcJ,MAAQD,UAIf,KAAXA,oBAIEM,eAAiB,IAAIhB,SAASC,2BAAoBF,UAAUkB,6BAC7DD,eAAeF,oBAGdI,YAAcF,eAAeG,KAAIb,SAAWA,QAAQE,GAAGY,UAAU,KACjEC,eAAiBL,eAAeG,KAAIb,SAAWA,QAAQgB,KAAKF,UAAU,KAEtEG,oBAAsBvB,SAASwB,eAAezB,UAAU0B,eACxDC,kBAAoBH,oBAAsBA,oBAAoBZ,MAAQ,QAClD,OAAtBe,+BAGEC,eAAiBC,KAAKC,MAAMH,sBAE7BC,MAAAA,8CAAAA,eAAiBjB,yEAAjBoB,sBAA0BC,yCAA1BC,uBAAgCC,cAMtB,kBAAXvB,OAA4B,OAEtBwB,KAAO,CACTC,KAAM,OACNjB,YAAaA,YACbG,eAAgBA,eAChBY,IAAKN,eAAejB,QAAQqB,KAAKE,KAI/BG,MAAQ,IAAIC,YAAY,QAAS,CAACC,OAAQJ,mBAChDlC,SAASuC,cAAc,mBAAmBC,cAAcJ,aAItDK,YAAc,CAChB,CACIC,IAAK,0BAA4BhC,OAAS,eAC1CiC,UAAW,kBAEf,CACID,IAAK,0BAA4BhC,OAAS,cAC1CiC,UAAW,iBACXC,MAAOvB,eAAewB,KAAK,cAE/B,CACIH,IAAK,0BAA4BhC,OACjCiC,UAAW,mBAGbG,WAAaC,IAAIC,YAAYP,aAE7BQ,aAAeC,2BAAgBC,OAAO,QAExCC,UAAY,cACItD,sBACJD,yCAGZ8B,eAAejB,QAAQqB,wCAAvBsB,uBAA6BC,SAC7BF,UAAYG,OAAOC,OAAOJ,UAAWzB,eAAejB,QAAQqB,KAAKuB,eAG/DG,UAAYxB,aAAIyB,YAAY/B,eAAejB,QAAQqB,KAAKE,IAAKmB,2BAEjEO,KAAKb,WAAYG,cAAcW,MAAK,SAASC,QAASC,OAEpDC,OAAOC,gBAAkB,SACzBF,MAAMG,SAASJ,QAAQ,QACnBK,KAAO,kEAAoET,UAAY,KAC3FS,MAAQ,MAAQL,QAAQ,GAAK,WACxB,IAAIM,cAAcjD,YACnBgD,MAAQ,iDAAmDC,WAAa,YAE5ED,MAAQ,sDACRA,MAAQ,UACRJ,MAAMM,QAAQF,MACdJ,MAAMO,kBAAkBR,QAAQ,IAChCC,MAAMQ,UAAUC,GAAGC,sBAAYC,MAAM,WAEjCV,OAAOC,gBAAkB,UACzBhE,SAASwB,eAAe,iCAAiCkD,YAE7DZ,MAAMQ,UAAUC,GAAGC,sBAAYG,QAAQ,WAEnCZ,OAAOC,gBAAkB,UAEzBF,MAAMc,UAENC,4BAA4B9E,cAEhC+D,MAAMgB,OACChB,SACRiB,KAAKC,sBAAaC,wBAgB3BJ,4BAA+B9E,YACf,IAAIC,SAASC,iBAAiBF,UAAUG,WAChDC,SAAQD,WACdA,SAASS,MAAQ,GACjBT,SAASgF,aAAa,YAAY,MAGrB,IAAIlF,SAASC,2BAAoBF,UAAUoF,uBAAcpF,UAAUkB,cAC3Ed,SAAQiF,QACbA,MAAMC,SAAU"} \ No newline at end of file diff --git a/amd/src/block_index.js b/amd/src/block_index.js index 198de1fc..52a208fc 100644 --- a/amd/src/block_index.js +++ b/amd/src/block_index.js @@ -68,7 +68,31 @@ define(['jquery', 'core/modal_factory', 'core/modal_events', $('.start-workflow').on('click', function(e) { e.preventDefault(); + const detail = e?.detail || {}; + var clickedVideo = $(e.currentTarget); + var actionurl = url.relativeUrl('blocks/opencast/startworkflow.php', { + 'ocinstanceid': ocinstanceid, + 'courseid': courseid, + 'videoid': clickedVideo.data('id') + }); + var ismassaction = false; + var bulkinfodiv = ''; + if (detail?.type === 'bulk' && detail?.selectedids) { + ismassaction = true; + bulkinfodiv = '
    '; + bulkinfodiv += '

    ' + langstrings[9].replace('{$a}', detail.selectedtitles.join('

  • ')) + '

    '; + bulkinfodiv += ''; + for (let videoid of detail.selectedids) { + bulkinfodiv += ''; + } + bulkinfodiv += ''; + actionurl = url.relativeUrl(detail.url, { + 'ocinstanceid': ocinstanceid, + 'courseid': courseid + }); + } + var select = ''; var body = '
    ' + + actionurl + + '">
    ' + + bulkinfodiv + '

    ' + langstrings[6] + '

    ' + select + workflowdescdiv + @@ -110,7 +132,7 @@ define(['jquery', 'core/modal_factory', 'core/modal_events', ModalFactory.create({ type: ModalFactory.types.SAVE_CANCEL, - title: langstrings[5], + title: ismassaction ? langstrings[10] : langstrings[5], body: body }, undefined) .then(function(modal) { @@ -133,6 +155,8 @@ define(['jquery', 'core/modal_factory', 'core/modal_events', resumeLiveUpdate(ocinstanceid, contextid, liveupdate); // Destroy when hidden/closed. modal.destroy(); + // Change the bulk action select back to choose... + resetVideosTableBulkActions(); }); // Show description for initial value. @@ -269,6 +293,10 @@ define(['jquery', 'core/modal_factory', 'core/modal_events', var items = getLiveUpdateItems(); if (items.length) { window.liveUpdateInterval = setInterval(function() { + // Adding the state checker here, in order to pause the live update from other js modules like block_massaction. + if (window.liveUpdateState === 'paused') { + return; + } var processingItems = getLiveUpdateProcessingItems(); var uploadingItems = getLiveUpdateUploadingItems(); if (processingItems.length == 0 && uploadingItems.length == 0) { @@ -453,6 +481,15 @@ define(['jquery', 'core/modal_factory', 'core/modal_events', }); }; + /* + * Resets the bulk action select dropdowns and unchecks the select items. + */ + var resetVideosTableBulkActions = function () { + $('.opencast-videos-table-massactions').val(''); + $('.opencast-videos-table-massactions').attr('disabled', true); + $('input.opencast-videos-selectall, input.opencast-video-select').prop('checked', false); + }; + /* * Initialise all of the modules for the opencast block. */ @@ -494,6 +531,14 @@ define(['jquery', 'core/modal_factory', 'core/modal_events', { key: 'startworkflow_modal_configpanel_title', component: 'block_opencast' + }, + { + key: 'videostable_massaction_startworkflow_modal_body', + component: 'block_opencast' + }, + { + key: 'videostable_massaction_startworkflow_modal_title', + component: 'block_opencast' } ]; str.get_strings(strings).then(function(results) { diff --git a/amd/src/block_massaction.js b/amd/src/block_massaction.js new file mode 100644 index 00000000..4a12d05f --- /dev/null +++ b/amd/src/block_massaction.js @@ -0,0 +1,187 @@ +// 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 to instantiate the Mass-Action functionality. + * + * @module block_opencast + * @copyright 2024 Farbod Zamani Boroujeni (elan e.V.) (zamani@elan-ev.de) + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +import $ from 'jquery'; +import * as str from 'core/str'; +import Notification from 'core/notification'; +import ModalSaveCancel from 'core/modal_save_cancel'; +import ModalEvents from 'core/modal_events'; +import url from 'core/url'; + +/** + * Initializes the mass action functionality for the Opencast block. + * This function sets up event listeners for dropdown changes, handles video selection, + * and manages the modal dialogs for different actions. + * + * @param {number} courseid - The ID of the current course. + * @param {number} ocinstanceid - The ID of the Opencast instance. + * @param {Object} selectors - An object containing CSS/Id selectors for various elements. + * @param {string} selectors.dropdown - Selector for the action dropdown elements. + * @param {string} selectors.selectitem - Selector for the checkbox elements to select individual videos. + * @param {string} selectors.actionmapping - Selector for the element containing action mapping data. + * @param {string} selectors.selectall - Selector for the "select all" checkbox. + * @returns {void} This function does not return a value. + */ +export const init = (courseid, ocinstanceid, selectors) => { + + const dropdowns = [...document.querySelectorAll(selectors.dropdown)]; + dropdowns.forEach(dropdown => { + dropdown.addEventListener('change', e => { + const element = e.currentTarget; + const id = element.getAttribute('id'); + const action = element.value; + + // Make sure other bulk select get the same value. + const populatedselector = `${selectors.dropdown}:not(#${id})`; + const otherdropdowns = [...document.querySelectorAll(populatedselector)]; + if (otherdropdowns.length) { + otherdropdowns.forEach(otherdropdown => { + otherdropdown.value = action; + }); + } + + if (action === '') { + return; + } + + const selectedvideos = [...document.querySelectorAll(`${selectors.selectitem}:checked`)]; + if (!selectedvideos.length) { + return; + } + const selectedids = selectedvideos.map(element => element.id.substring(7)); + const selectedtitles = selectedvideos.map(element => element.name.substring(7)); + + const actionsmappinginput = document.getElementById(selectors.actionmapping); + const actionsmappingraw = actionsmappinginput ? actionsmappinginput.value : null; + if (actionsmappingraw === null) { + return; + } + const actionsmapping = JSON.parse(actionsmappingraw); + // Make sure that the action url is there. + if (!actionsmapping?.[action]?.path?.url) { + return; + } + + // Because of using Modal for start workflow tasks, we don't provide a confirmation modal beforehand, + // but instead we provide the confirmation texts in existing startworkflow modal. + if (action === 'startworkflow') { + + const data = { + type: 'bulk', + selectedids: selectedids, + selectedtitles: selectedtitles, + url: actionsmapping[action].path.url + }; + + // Create and dispatch the custom event on start-workflow element with detail data. + const event = new CustomEvent('click', {detail: data}); + document.querySelector('.start-workflow').dispatchEvent(event); + return; // We stop the function here! + } + + const stringskeys = [ + { + key: 'videostable_massaction_' + action + '_modal_title', + component: 'block_opencast' + }, + { + key: 'videostable_massaction_' + action + '_modal_body', + component: 'block_opencast', + param: selectedtitles.join('
  • ') + }, + { + key: 'videostable_massaction_' + action, + component: 'block_opencast' + }, + ]; + const strPromise = str.get_strings(stringskeys); + + const modalPromise = ModalSaveCancel.create({}); + + var urlParams = { + 'ocinstanceid': ocinstanceid, + 'courseid': courseid + }; + + if (actionsmapping[action].path?.params) { + urlParams = Object.assign(urlParams, actionsmapping[action].path.params); + } + + const actionUrl = url.relativeUrl(actionsmapping[action].path.url, urlParams); + + $.when(strPromise, modalPromise).then(function(strings, modal) { + // Pause the live update if it is running. + window.liveUpdateState = 'paused'; + modal.setTitle(strings[0]); + var body = ''; + body += '

    ' + strings[1] + '

    '; + for (let selectedid of selectedids) { + body += ''; + } + body += ''; + body += ''; + modal.setBody(body); + modal.setSaveButtonText(strings[2]); + modal.getRoot().on(ModalEvents.save, function() { + // Resume the live update if it was paused. + window.liveUpdateState = 'resumed'; + document.getElementById('mass_action_confirmation_form').submit(); + }); + modal.getRoot().on(ModalEvents.hidden, function() { + // Resume the live update if it was paused. + window.liveUpdateState = 'resumed'; + // Destroy when hidden/closed. + modal.destroy(); + // Change the bulk action select back to choose... + resetVideosTableBulkActions(selectors); + }); + modal.show(); + return modal; + }).fail(Notification.exception); + }); + }); +}; + +/** + * Resets the bulk action select dropdowns and unchecks the select items. + * This function is called when the modal is hidden/closed. + * + * @param {Object} selectors - An object containing CSS/Id selectors for various elements. + * @param {string} selectors.dropdown - Selector for the action dropdown elements. + * @param {string} selectors.selectitem - Selector for the checkbox elements to select individual videos. + * @param {string} selectors.actionmapping - Selector for the element containing action mapping data. + * @param {string} selectors.selectall - Selector for the "select all" checkbox. + * @returns {void} This function does not return a value. + */ +const resetVideosTableBulkActions = (selectors) => { + const dropdowns = [...document.querySelectorAll(selectors.dropdown)]; + dropdowns.forEach(dropdown => { + dropdown.value = ''; + dropdown.setAttribute('disabled', true); + }); + + const ckinputs = [...document.querySelectorAll(`${selectors.selectall}, ${selectors.selectitem}`)]; + ckinputs.forEach(input => { + input.checked = false; + }); +}; diff --git a/changevisibility_massaction.php b/changevisibility_massaction.php new file mode 100644 index 00000000..3c0fe210 --- /dev/null +++ b/changevisibility_massaction.php @@ -0,0 +1,334 @@ +. + +/** + * Change Visibility - Mass Action + * + * @package block_opencast + * @copyright 2024 Farbod Zamani Boroujeni, ELAN e.V. + * @author Farbod Zamani Boroujeni + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +require_once('../../config.php'); +require_once('./renderer.php'); + +use block_opencast\groupaccess; +use block_opencast\local\apibridge; +use block_opencast\local\visibility_form_massaction; +use block_opencast\local\visibility_helper; +use core\output\notification; +use tool_opencast\local\settings_api; + +global $PAGE, $OUTPUT, $CFG; + +$ismassaction = required_param('ismassaction', PARAM_INT); +$videoids = required_param_array('videoids', PARAM_RAW); +$courseid = required_param('courseid', PARAM_INT); +$ocinstanceid = optional_param('ocinstanceid', settings_api::get_default_ocinstance()->id, PARAM_INT); + +$baseurl = new moodle_url('/blocks/opencast/changevisibility_massaction.php', + ['ismassaction' => $ismassaction, 'courseid' => $courseid, 'ocinstanceid' => $ocinstanceid]); +$PAGE->set_url($baseurl); + +require_login($courseid, false); + +$PAGE->set_pagelayout('incourse'); +$PAGE->set_title(get_string('pluginname', 'block_opencast')); +$PAGE->set_heading(get_string('pluginname', 'block_opencast')); + +$redirecturl = new moodle_url('/blocks/opencast/index.php', ['courseid' => $courseid, 'ocinstanceid' => $ocinstanceid]); +$PAGE->navbar->add(get_string('pluginname', 'block_opencast'), $redirecturl); +$PAGE->navbar->add(get_string('changevisibility_massaction', 'block_opencast'), $baseurl); + +// Check if the ACL control feature is enabled. +if (get_config('block_opencast', 'aclcontrolafter_' . $ocinstanceid) != true) { + throw new moodle_exception('ACL control feature not enabled', 'block_opencast', $redirecturl); +} + +// Workflow is not set. +if (get_config('block_opencast', 'workflow_roles_' . $ocinstanceid) == "") { + $message = get_string('workflownotdefined', 'block_opencast'); + redirect($redirecturl, $message, null, \core\notification::ERROR); +} + +if (empty($videoids)) { + $message = get_string('changevisibility_massaction_novideos', 'block_opencast'); + redirect($redirecturl, $message, null, \core\notification::ERROR); +} + +// Capability check. +$coursecontext = context_course::instance($courseid); +require_capability('block/opencast:addvideo', $coursecontext); + +$apibridge = apibridge::get_instance($ocinstanceid); + +$videosdatalist = []; + +$courseseries = $apibridge->get_course_series($courseid); +foreach ($videoids as $videoid) { + + // Record the video data for later use. + $videodata = new stdClass(); + $videodata->identifier = $videoid; + $videodata->title = $videoid; + $videodata->detail = null; + $videodata->error = false; + $videodata->visibility = null; + $videodata->scheduledvisibility = null; + + $video = $apibridge->get_opencast_video($videoid); + if (!empty($video->error)) { + $videodata->error = get_string('videonotfound', 'block_opencast'); + $videodata->detail = get_string('changevisibility_massaction_videoerror', 'block_opencast', $videodata); + $videosdatalist[] = $videodata; + continue; + } + + $videodata->title = $video->video->title; + + if (!in_array($video->video->processing_state, ["SUCCEEDED", "FAILED", "STOPPED"])) { + $videodata->error = get_string('massaction_videostatusmismatched', 'block_opencast'); + $videodata->detail = get_string('changevisibility_massaction_videoerror', 'block_opencast', $videodata); + $videosdatalist[] = $videodata; + continue; + } + + $visibility = $apibridge->is_event_visible($videoid, $courseid); + if ($visibility === block_opencast_renderer::MIXED_VISIBILITY) { + $groups = groupaccess::get_record(['opencasteventid' => $videoid, 'ocinstanceid' => $ocinstanceid]); + if ($groups) { + $visibility = block_opencast_renderer::GROUP; + } else { + $visibility = block_opencast_renderer::HIDDEN; + } + } + + $videodata->visibility = $visibility; + list($visibilitystatus, $visibilitystatusdesc) = visibility_helper::get_visibility_status_legend($visibility); + + // To record lang string object and info. + $langstringkey = 'changevisibility_massaction_visibility_status'; + $strobj = new stdClass(); + $strobj->title = $videodata->title; + $strobj->vstatus = $visibilitystatus; + $strobj->vstatusdesc = $visibilitystatusdesc; + + $scheduledvisibility = visibility_helper::get_event_scheduled_visibility($ocinstanceid, $courseid, $videoid); + + $videodata->scheduledvisibility = $scheduledvisibility; + + if (!empty($scheduledvisibility) && intval($scheduledvisibility->status) === visibility_helper::STATUS_PENDING) { + $langstringkey = 'changevisibility_massaction_visibility_status_with_scheduled'; + + list($scheduledvisibilitystatus, $scheduledvisibilitystatusdesc) = + visibility_helper::get_visibility_status_legend($scheduledvisibility->scheduledvisibilitystatus); + $scheduledvisibilitydatetime = userdate( + $scheduledvisibility->scheduledvisibilitytime, + get_string('strftimedatetime', 'langconfig') + ); + + $strobj->svstatus = $scheduledvisibilitystatus; + $strobj->svstatusdesc = $scheduledvisibilitystatusdesc; + $strobj->svdatetime = $scheduledvisibilitydatetime; + } + + $videodata->detail = get_string($langstringkey, 'block_opencast', $strobj); + + $videosdatalist[] = $videodata; +} + +$massactionchangevisibilityform = new visibility_form_massaction(null, ['courseid' => $courseid, + 'videosdatalist' => $videosdatalist, 'ocinstanceid' => $ocinstanceid, ]); + +if ($massactionchangevisibilityform->is_cancelled()) { + redirect($redirecturl); +} + +if ($data = $massactionchangevisibilityform->get_data()) { + if (confirm_sesskey()) { + + $nochanges = []; + + $failed = []; + $succeeded = []; + + $schedulingfailed = []; + $schedulingsucceeded = []; + + $requestscheduling = false; + if (isset($data->enableschedulingchangevisibility) && boolval($data->enableschedulingchangevisibility)) { + $requestscheduling = true; + } + + $groups = null; + if (property_exists($data, 'groups')) { + $groups = $data->groups; + } + + $initialvisibilitygroups = null; + if ($data->visibility == block_opencast_renderer::GROUP + && !empty($groups)) { + $initialvisibilitygroups = json_encode($groups); + } + $scheduledvisibilitygroups = null; + if ($data->scheduledvisibilitystatus == block_opencast_renderer::GROUP + && !empty($data->scheduledvisibilitygroups)) { + $scheduledvisibilitygroups = json_encode($data->scheduledvisibilitygroups); + } + + // All processed video data is included in $videosdatalist variable beforehand! + foreach ($videosdatalist as $videodata) { + + $haschanges = false; + + // Just skip if the video has any error! + if (!empty($videodata->error)) { + $failed[$videodata->identifier] = + get_string('changevisibility_massaction_report_failed', 'block_opencast', $videodata); + continue; + } + + // If only the requested visibility is different. + if ($videodata->visibility != $data->visibility) { + $changeresult = $apibridge->change_visibility($videodata->identifier, $courseid, $data->visibility, $groups); + // If there is any error, we skip the scheduling process and mark this video as failed. + + if ($changeresult === false) { + $langstrkey = 'changevisibility_massaction_aclchangeerror'; + if ($requestscheduling) { + $langstrkey = 'changevisibility_massaction_aclchangeerror_noscheduling'; + } + $videodata->error = get_string($langstrkey, 'block_opencast'); + $failed[] = get_string('changevisibility_massaction_report_failed', 'block_opencast', $videodata); + continue; + } + $succeeded[] = $videodata->title; + $haschanges = true; + } + + $schedulingresult = true; + // Check if the scheduled visibility is set, we update the record. + if ($requestscheduling) { + $scheduledvisibility = $videodata->scheduledvisibility; + // If the record already exists, we update it. + if (!empty($scheduledvisibility)) { + $scheduledvisibility->scheduledvisibilitytime = $data->scheduledvisibilitytime; + $scheduledvisibility->scheduledvisibilitystatus = $data->scheduledvisibilitystatus; + $scheduledvisibility->scheduledvisibilitygroups = $scheduledvisibilitygroups; + $schedulingresult = visibility_helper::update_visibility_job($scheduledvisibility); + if (!$schedulingresult) { + $videodata->error = get_string('scheduledvisibilityupdatefailed', 'block_opencast'); + } + } else { + // Otherwise, we create a new record. + $scheduledvisibility = new stdClass(); + $scheduledvisibility->initialvisibilitystatus = $data->visibility; + $scheduledvisibility->initialvisibilitygroups = $initialvisibilitygroups; + $scheduledvisibility->scheduledvisibilitytime = $data->scheduledvisibilitytime; + $scheduledvisibility->scheduledvisibilitystatus = $data->scheduledvisibilitystatus; + $scheduledvisibility->scheduledvisibilitygroups = $scheduledvisibilitygroups; + $scheduledvisibility->ocinstanceid = $ocinstanceid; + $scheduledvisibility->courseid = $courseid; + $scheduledvisibility->opencasteventid = $videodata->identifier; + $schedulingresult = visibility_helper::save_visibility_job($scheduledvisibility); + if (!$schedulingresult) { + $videodata->error = get_string('scheduledvisibilitycreatefailed', 'block_opencast'); + } + } + + if (!$schedulingresult) { + $schedulingfailed[] = get_string('changevisibility_massaction_report_failed', 'block_opencast', $videodata); + } else { + $schedulingsucceeded[] = $videodata->title; + } + + $haschanges = true; + } + + if (!$haschanges) { + $nochanges[] = $videodata->title; + } + } + + // We notify those have no change first as info. + if (!empty($nochanges)) { + $nochangestext = get_string( + 'changevisibility_massaction_notification_nochanges', + 'block_opencast', + implode('
  • ', $nochanges) + ); + \core\notification::add($nochangestext, \core\notification::INFO); + } + + // We notify the errors of scheduling. + if (!empty($schedulingfailed)) { + $schedulingfailedtext = get_string( + 'changevisibility_massaction_notification_schedulingfailed', + 'block_opencast', + implode('
  • ', $schedulingfailed) + ); + \core\notification::add($schedulingfailedtext, \core\notification::ERROR); + } + + // We notify the succeeded scheduling. + if (!empty($schedulingsucceeded)) { + $schedulingsucceededtext = get_string( + 'changevisibility_massaction_notification_schedulingsucceeded', + 'block_opencast', + implode('
  • ', $schedulingsucceeded) + ); + \core\notification::add($schedulingsucceededtext, \core\notification::SUCCESS); + } + + $failedtext = ''; + if (!empty($failed)) { + $failedtext = get_string( + 'changevisibility_massaction_notification_failed', + 'block_opencast', + implode('
  • ', $failed) + ); + } + $succeededtext = ''; + if (!empty($succeeded)) { + $succeededtext = get_string( + 'changevisibility_massaction_notification_succeeded', + 'block_opencast', + implode('
  • ', $succeeded) + ); + } + + // Redirect with error if no success message is available. + if (empty($succeededtext) && !empty($failedtext)) { + redirect($redirecturl, $failedtext, null, notification::NOTIFY_ERROR); + } + + // Otherwise, notify the error message if exists. + if (!empty($failedtext)) { + \core\notification::add($failedtext, \core\notification::ERROR); + } + + // If hitting here, that means success message exists and we can redirect! + redirect($redirecturl, $succeededtext, null, notification::NOTIFY_SUCCESS); + } +} + +$renderer = $PAGE->get_renderer('block_opencast'); + +echo $OUTPUT->header(); +echo $OUTPUT->heading(get_string('changevisibility_header_massaction', 'block_opencast', $video)); +$massactionchangevisibilityform->display(); +echo $OUTPUT->footer(); diff --git a/classes/external.php b/classes/external.php index bdc157a8..f78fc109 100644 --- a/classes/external.php +++ b/classes/external.php @@ -442,7 +442,7 @@ public static function get_liveupdate_info(int $contextid, int $ocinstanceid, st // Force to have replace and remove params, otherwise empty must be returned. if (!isset($liveupdateinfo['replace']) || !isset($liveupdateinfo['remove'])) { // Returning empty string helps to remove the item in the javascript, that results in cleaning the interval. - $liveupdateinfo = ''; + return ''; } // Finally, we return info as json encoded string. diff --git a/classes/local/apibridge.php b/classes/local/apibridge.php index 06da1fa5..93ea8618 100644 --- a/classes/local/apibridge.php +++ b/classes/local/apibridge.php @@ -3080,4 +3080,56 @@ public function generate_studio_url_path($courseid, $seriesid) { // Finally we return the generate studio url path. return $studiourlpath; } + + /** + * Checks if the user has the capability to update metadata for multiple events in a course. + * We use specific checker method, in case there would be more conditions later on. + * + * @param int $courseid The ID of the course. + * + * @return bool True if the user has the capability, false otherwise. + */ + public function can_update_metadata_massaction($courseid) { + $context = context_course::instance($courseid); + return has_capability('block/opencast:addvideo', $context); + } + + /** + * Checks if the user has the capability to delete multiple events in a course. + * We use specific checker method, in case there would be more conditions later on. + * + * @param int $courseid The ID of the course. + * + * @return bool True if the user has the capability, false otherwise. + */ + public function can_delete_massaction($courseid) { + $context = context_course::instance($courseid); + return has_capability('block/opencast:deleteevent', $context); + } + + /** + * Checks if the user has the capability to change the visibility of multiple events in a course. + * We use specific checker method, in case there would be more conditions later on. + * + * @param int $courseid The ID of the course. + * + * @return bool True if the user has the capability, false otherwise. + */ + public function can_change_visibility_massaction($courseid) { + $context = context_course::instance($courseid); + return has_capability('block/opencast:addvideo', $context); + } + + /** + * Checks if the user has the capability to start workflows for multiple events in a course. + * We use specific checker method, in case there would be more conditions later on. + * + * @param int $courseid The ID of the course. + * + * @return bool True if the user has the capability, false otherwise. + */ + public function can_start_workflow_massaction($courseid) { + $context = context_course::instance($courseid); + return has_capability('block/opencast:startworkflow', $context); + } } diff --git a/classes/local/massaction_helper.php b/classes/local/massaction_helper.php new file mode 100644 index 00000000..7f6e8743 --- /dev/null +++ b/classes/local/massaction_helper.php @@ -0,0 +1,426 @@ +. + +/** + * Mass action helper class. + * + * @package block_opencast + * @copyright 2024 Farbod Zamani Boroujeni, ELAN e.V. + * @author Farbod Zamani Boroujeni + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace block_opencast\local; + +use coding_exception; +use stdClass; +use html_writer; + +/** + * Mass action helper class. + * + * @package block_opencast + * @copyright 2024 Farbod Zamani Boroujeni, ELAN e.V. + * @author Farbod Zamani Boroujeni + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class massaction_helper { + + /** @var string Toggle group name. */ + const TOGGLE_GROUP_NAME = 'opencast-videos-table'; + + /** @var string Checkbox selectall class name. */ + const CHECKBOX_SELECTALL_CLASSNAME = 'opencast-videos-selectall'; + /** @var string Checkbox selectall id. */ + const CHECKBOX_SELECTALL_ID = 'select-all-opencast-videos'; + + /** @var string Checkbox select item class name. */ + const CHECKBOX_SELECTITEM_CLASSNAME = 'opencast-video-select'; + /** @var string Checkbox select item disabled class name. */ + const CHECKBOX_SELECTITEM_DISABLED_CLASSNAME = 'opencast-videos-table-disabled'; + + /** @var string Select dropdown class name. */ + const SELECT_DROPDOWN_CLASSNAME = 'opencast-videos-table-massactions'; + + /** @var string Actions mapping hidden input id. */ + const HIDDEN_INPUT_ACTIONS_MAPPING_ID = 'opencast-videos-table-massactions-actionsmapping'; + + /** @var string Massaction type change visibility. */ + const MASSACTION_CHANGEVISIBILITY = 'changevisibility'; + /** @var string Massaction type delete. */ + const MASSACTION_DELETE = 'delete'; + /** @var string Massaction type update metadata. */ + const MASSACTION_UPDATEMETADATA = 'updatemetadata'; + /** @var string Massaction type start workflow. */ + const MASSACTION_STARTWORKFLOW = 'startworkflow'; + + /** @var array Mass-Action configuration mapping. */ + public $massactions = [ + self::MASSACTION_DELETE => [ + 'path' => [ + 'url' => '/blocks/opencast/deleteevent_massaction.php', + ], + 'enable' => true, + ], + self::MASSACTION_UPDATEMETADATA => [ + 'path' => [ + 'url' => '/blocks/opencast/updatemetadata_massaction.php', + ], + 'enable' => true, + ], + self::MASSACTION_CHANGEVISIBILITY => [ + 'path' => [ + 'url' => '/blocks/opencast/changevisibility_massaction.php', + ], + 'enable' => true, + ], + self::MASSACTION_STARTWORKFLOW => [ + 'path' => [ + 'url' => '/blocks/opencast/startworkflow_massaction.php', + ], + 'enable' => true, + ], + ]; + + /** @var bool Whether the whole feature is activated or not. */ + private $isactivated = true; + + /** @var bool Whether hidden input for actions mapping is rendered. */ + private $mappingisrendered = false; + + /** + * Constructs the mass action helper class. + * + * This function initializes the mass actions based on the provided default actions. + * If default actions are provided, they are validated and set as the mass actions. + * If the default actions are not valid, a coding_exception is thrown. + * + * @param array $defaultactions An optional array of default mass actions. + * Each default action is represented as an associative array with the following structure: + * [ + * 'name' => (string) The unique name of the mass action, + * 'path' => [ + * 'url' => (string) The URL of the mass action handler, + * 'params' => (array) The URL parameters of the mass action handler (optional), + * ], + * 'enable' => (bool) Whether the mass action is enabled or not, + * ] + * + * @throws coding_exception If the default actions are not valid. + */ + public function __construct(array $defaultactions = null) { + if (!empty($defaultactions)) { + $this->massactions = $defaultactions; + if (!$this->validate_massactions()) { + throw new coding_exception('massaction_invaliddefaultactions', 'block_opencast'); + } + } + } + + /** + * Renders the master checkbox for mass actions. + * + * This function generates a checkbox that acts as a master toggle for all the video items in the table. + * The checkbox is only rendered if there are any enabled mass actions available. + * + * @return string The HTML markup for the master checkbox. If no mass actions are available, an empty string is returned. + */ + public function render_master_checkbox() { + global $OUTPUT; + if (!$this->has_massactions()) { + return ''; + } + $mastercheckbox = new \core\output\checkbox_toggleall(self::TOGGLE_GROUP_NAME, true, [ + 'classes' => self::CHECKBOX_SELECTALL_CLASSNAME, + 'id' => self::CHECKBOX_SELECTALL_ID, + 'name' => self::CHECKBOX_SELECTALL_ID, + 'label' => get_string('selectall'), + // Consistent label to prevent unwanted text change when we automatically uncheck. + 'selectall' => get_string('selectall'), + 'deselectall' => get_string('selectall'), + // We need the classes specially for behat test to pickup the checkbox. + 'labelclasses' => 'form-check-label d-block pe-2 sr-only', + ]); + return $OUTPUT->render($mastercheckbox); + } + + /** + * Renders a checkbox for a video item in the mass action table. + * + * This function generates a checkbox for a video item in the mass action table. + * The checkbox is disabled when the video item is not selectable. + * + * @param stdClass $video The video object containing the video's identifier and title. + * @param bool $isselectable Whether the video item is selectable or not. Default is true. + * + * @return string The HTML markup for the checkbox. If mass actions are not available, an empty string is returned. + */ + public function render_item_checkbox(stdClass $video, bool $isselectable = true) { + global $OUTPUT; + + if (!$this->has_massactions()) { + return ''; + } + // Preparing select checkboxes. + $disabledcheckboxattrs = [ + 'title' => get_string('videostablemassaction_disabled_item', 'block_opencast'), + 'disabled' => 'disabled', + ]; + // A default disabled checkbox. + $checkboxhtml = html_writer::checkbox( + self::CHECKBOX_SELECTITEM_DISABLED_CLASSNAME, + $video->identifier, + false, + '', + $disabledcheckboxattrs + ); + // When the row is selectable, then we provide the toggle checkbox. + if ($isselectable) { + $selectableattributes = [ + 'classes' => self::CHECKBOX_SELECTITEM_CLASSNAME, + 'id' => 'ocvideo' . $video->identifier, + 'name' => 'ocvideo' . $video->title, + 'checked' => false, + // For behat tests to pickup the checkbox easier, we provide the title as well. + 'label' => get_string('select') . ' ' . $video->title, + // We need the classes specially for behat test to pickup the checkbox. + 'labelclasses' => 'form-check-label d-block pe-2 sr-only', + ]; + $checkbox = new \core\output\checkbox_toggleall(self::TOGGLE_GROUP_NAME, false, $selectableattributes); + $checkboxhtml = $OUTPUT->render($checkbox); + } + + return $checkboxhtml; + } + + /** + * Renders the mass action select dropdown for the video table. + * + * This function generates a dropdown menu for selecting mass actions to be performed on the video table. + * The dropdown menu is populated with enabled mass actions and is disabled when no mass actions are available. + * + * @param string $id The unique identifier for the dropdown menu. + * + * @return string The HTML markup for the mass action select dropdown. If no mass actions are available, + * an empty string is returned. + */ + public function render_table_mass_actions_select(string $id) { + if (!$this->has_massactions()) { + return ''; + } + // Bulk actions. + $html = html_writer::start_div('py-3 px-2 mt-2 mb-2'); + $html .= html_writer::label( + get_string('videostablemassaction_label', 'block_opencast'), + $id, + false, + ['class' => 'mr-3'], + ); + + $enabledmassactions = array_filter($this->massactions, function ($item) { + return !empty($item['enable']); + }); + + $massactionselectitems = []; + foreach (array_keys($enabledmassactions) as $makey) { + $massactionselectitems[$makey] = get_string('videostable_massaction_' . $makey, 'block_opencast'); + } + + // Actions mapping hidden input. + if (!$this->mappingisrendered) { + $hiddenactionsmappingattrs = [ + 'type' => 'hidden', + 'id' => self::HIDDEN_INPUT_ACTIONS_MAPPING_ID, + 'value' => json_encode($enabledmassactions), + ]; + $html .= html_writer::empty_tag('input', $hiddenactionsmappingattrs); + $this->mappingisrendered = true; + } + + $withselectedparams = [ + 'id' => $id, + 'data-action' => 'toggle', + 'data-togglegroup' => self::TOGGLE_GROUP_NAME, + 'data-toggle' => 'action', + 'disabled' => true, + 'class' => self::SELECT_DROPDOWN_CLASSNAME, + ]; + $html .= html_writer::select($massactionselectitems, $id, '', ['' => 'choosedots'], $withselectedparams); + + $html .= html_writer::end_div(); + return $html; + } + + /** + * Adds a new mass action to the list of available mass actions. + * + * @param string $name The unique name of the mass action. + * @param string $url The URL of the mass action handler. + * @param bool $enable Whether the mass action is enabled or not. Default is true. + * + * @return bool Returns true if the mass action was successfully added, false otherwise. + */ + public function add_massaction($name, $url, $enable = true) { + if (!isset($this->massactions[$name])) { + $item = [ + 'path' => [ + 'url' => $url, + ], + 'enable' => $enable, + ]; + $this->massactions[$name] = $item; + return true; + } + return false; + } + + /** + * Enables or disables a specific mass action. + * + * This function allows you to enable or disable a mass action based on its unique name. + * If the mass action exists in the list of available mass actions, its 'enable' status will be updated. + * + * @param string $item The unique name of the mass action. + * @param bool $enable Whether the mass action should be enabled (true) or disabled (false). Default is true. + * + */ + public function massaction_action_activation($item, $enable = true) { + if (isset($this->massactions[$item])) { + $this->massactions[$item]['enable'] = $enable; + } + } + + + + /** + * Checks if there are any enabled mass actions available and if the feature is activated. + * + * This function filters the mass actions to get only the enabled ones and checks if there are any. + * It also verifies if the feature is still activated. + * + * @return bool Returns true if there are enabled mass actions available and the feature is activated, + * false otherwise. + */ + public function has_massactions() { + // Filter the mass actions to get only the enabled ones. + $enabledmassactions = array_filter($this->massactions, function ($item) { + return !empty($item['enable']); + }); + + // Check if there are enabled mass actions and if the feature is still activated. + return count($enabledmassactions) > 0 && $this->isactivated; + } + + /** + * Validates the mass actions configuration. + * + * This function checks if the mass actions configuration is valid. + * It ensures that each mass action has an 'enable' status, a 'path' array, + * and the 'path' array contains a non-empty 'url'. + * + * @return bool Returns true if the mass actions configuration is valid, false otherwise. + */ + private function validate_massactions() { + $isvalid = true; + foreach ($this->massactions as $action) { + if (!isset($action['enable']) || !isset($action['path']) || + (isset($action['path']) && (!isset($action['path']['url']) || empty($action['path']['url'])))) { + $isvalid = false; + break; + } + } + return $isvalid; + } + + /** + * Activates or deactivates the mass action feature. + * + * This function allows you to enable or disable the mass action feature. + * When the feature is activated, the mass actions will be available for use. + * When the feature is deactivated, the mass actions will not be available. + * + * @param bool $activate Whether to activate (true) or deactivate (false) the mass action feature. + * The default value is true, meaning the feature will be activated if no value is provided. + * + * @return void This function does not return any value. + */ + public function activate_massaction(bool $activate = true) { + $this->isactivated = $activate; + } + + /** + * Sets a parameter for the URL of a specific mass action. + * + * This function allows you to add or update a parameter in the URL of a specific mass action. + * The parameter will be appended to the 'path' array of the mass action. + * + * @param string $actionname The unique name of the mass action. + * @param string $paramkey The key of the parameter to be set. + * @param string $paramvalue The value of the parameter to be set. + * + * @throws coding_exception If the provided actionname, paramkey, or paramvalue is invalid. + * + * @return void This function does not return any value. + */ + public function set_action_path_parameter(string $actionname, string $paramkey, string $paramvalue) { + if (!isset($this->massactions[$actionname]) || empty($paramkey) || empty($paramvalue)) { + throw new coding_exception('massaction_invalidactionparam', 'block_opencast'); + } + $this->massactions[$actionname]['path']['params'][$paramkey] = $paramvalue; + } + + /** + * Removes a parameter from the URL of a specific mass action. + * + * This function allows you to remove a parameter from the URL of a specific mass action. + * The parameter will be removed from the 'path' array of the mass action. + * + * @param string $actionname The unique name of the mass action. + * @param string $paramkey The key of the parameter to be removed. + * + * @return bool Returns true if the parameter was successfully removed, false otherwise. + * If the provided actionname or paramkey is invalid, the function will return false. + */ + public function remove_action_path_parameter(string $actionname, string $paramkey) { + if (isset($this->massactions[$actionname]) && !empty($paramkey) && + isset($this->massactions[$actionname]['path']['params'][$paramkey])) { + unset($this->massactions[$actionname]['path']['params'][$paramkey]); + return true; + } + + return false; + } + + /** + * Retrieves the JavaScript selectors used in the mass action helper class. + * + * @return array An associative array containing the JavaScript selectors. + * The keys represent the selector names, and the values represent the corresponding CSS selectors. + * The selectors include: + * - 'dropdown': The CSS selector for the mass action dropdown menu. + * - 'selectall': The CSS selector for the master checkbox for selecting all video items. + * - 'selectitem': The CSS selector for the checkboxes for selecting individual video items. + * - 'actionmapping': The CSS selector for the hidden input containing the mapping of mass actions. + */ + public static function get_js_selectors() { + return [ + 'dropdown' => '.'. self::SELECT_DROPDOWN_CLASSNAME, + 'selectall' => 'input.'. self::CHECKBOX_SELECTALL_CLASSNAME, + 'selectitem' => 'input.'. self::CHECKBOX_SELECTITEM_CLASSNAME, + 'actionmapping' => self::HIDDEN_INPUT_ACTIONS_MAPPING_ID, + ]; + } +} diff --git a/classes/local/updatemetadata_form_massaction.php b/classes/local/updatemetadata_form_massaction.php new file mode 100644 index 00000000..c7d7e4da --- /dev/null +++ b/classes/local/updatemetadata_form_massaction.php @@ -0,0 +1,226 @@ +. + +/** + * Update metadata form - Mass action. + * + * @package block_opencast + * @copyright 2024 Farbod Zamani Boroujeni, ELAN e.V. + * @author Farbod Zamani Boroujeni + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace block_opencast\local; + +use moodleform; +use html_writer; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; + +require_once($CFG->dirroot . '/lib/formslib.php'); + +/** + * Update metadata form - Mass action. + * + * @package block_opencast + * @copyright 2024 Farbod Zamani Boroujeni, ELAN e.V. + * @author Farbod Zamani Boroujeni + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class updatemetadata_form_massaction extends moodleform { + + /** + * Form definition. + */ + public function definition() { + global $PAGE; + + $mform = $this->_form; + $renderer = $PAGE->get_renderer('block_opencast'); + + $ocinstanceid = $this->_customdata['ocinstanceid']; + + $videosdatalist = $this->_customdata['videosdatalist']; + + $mform->addElement('hidden', 'ismassaction', 1); + $mform->setType('ismassaction', PARAM_INT); + + $videoslisthtmlitem = []; + foreach ($videosdatalist as $videodata) { + $videoslisthtmlitem[] = $videodata->title; + if (empty($videodata->error)) { + $mform->addElement('hidden', 'videoids[]', $videodata->identifier); + } + } + $mform->setType('videoids', PARAM_ALPHANUMEXT); + + if (!empty($videoslisthtmlitem)) { + $line = html_writer::tag('hr', ''); + $explanation = html_writer::tag('p', + get_string('massaction_selectedvideos_list', 'block_opencast', + implode('
  • ', $videoslisthtmlitem)) + ); + $mform->addElement('html', $line . $explanation . $line); + } + + foreach ($this->_customdata['metadata_catalog'] as $field) { + $param = []; + $attributes = []; + if ($field->param_json) { + $param = $field->datatype == 'static' ? $field->param_json : json_decode($field->param_json, true); + } + if ($field->datatype == 'autocomplete') { + $attributes = [ + 'multiple' => true, + 'placeholder' => get_string('metadata_autocomplete_placeholder', 'block_opencast', + $this->try_get_string($field->name, 'block_opencast')), + 'showsuggestions' => true, // If true, admin is able to add suggestion via admin page. Otherwise no suggestions! + 'noselectionstring' => get_string('metadata_autocomplete_noselectionstring', 'block_opencast', + $this->try_get_string($field->name, 'block_opencast')), + 'tags' => true, + ]; + // Check if the metadata_catalog field is creator or contributor, to pass some suggestions. + if ($field->name == 'creator' || $field->name == 'contributor') { + // We merge param values with the suggestions, because param is already initialized. + $param = array_merge($param, + autocomplete_suggestion_helper::get_suggestions_for_creator_and_contributor($ocinstanceid)); + } + } + + // Apply format_string to each value of select option, to use Multi-Language filters (if any). + if ($field->datatype == 'select') { + array_walk($param, function (&$item) { + $item = format_string($item); + }); + } + + $element = $mform->addElement($field->datatype, $field->name, $this->try_get_string($field->name, 'block_opencast'), + $param, $attributes); + + // Because there is no normal way to disable and enable the autocomplete field, + // we render a multiple select field as replacement, + // in order to give the user the impersseion that this field is not yet enabled. + if ($field->datatype == 'autocomplete') { + $selectreplacement = $mform->addElement('select', $field->name . '_replacement', + $this->try_get_string($field->name, 'block_opencast'), + [ + '' => $attributes['noselectionstring'], + ] + ); + $selectreplacement->setMultiple(true); + + $mform->disabledIf($field->name . '_replacement', $field->name . '_enabled', 'notchecked'); + $mform->hideIf($field->name . '_replacement', $field->name . '_enabled', 'checked'); + $mform->hideIf($field->name, $field->name . '_enabled', 'notchecked'); + } + + // For mass action fields, it is important to use a checkbox "enabled/disabled" for each field, + // as to prevent unwanted update on other metadata catalogs. Then only ones that are enabled will get updated. + $mform->addElement('checkbox', $field->name . '_enabled', '', get_string('enable')); + $mform->setType($field->name . '_enabled', PARAM_INT); + $mform->disabledIf($field->name, $field->name . '_enabled', 'notchecked'); + + // Check if the description is set for the field, to display it as help icon. + if (isset($field->description) && !empty($field->description)) { + // Use the renderer to generate a help icon with custom text. + $element->_helpbutton = $renderer->render_help_icon_with_custom_text( + $this->try_get_string($field->name, 'block_opencast'), $field->description); + } + + if ($field->datatype == 'text') { + $mform->setType($field->name, PARAM_TEXT); + } + + if ($field->readonly) { + $mform->freeze($field->name); + } else if ($field->required) { + $mform->addRule($field->name, get_string('required'), 'required'); + } + } + + // Adding Start Date field as well manually. + $mform->addElement('date_time_selector', 'startDate', get_string('date', 'block_opencast')); + $mform->addElement('checkbox', 'startDate_enabled', '', get_string('enable')); + $mform->setType('startDate_enabled', PARAM_INT); + $mform->disabledIf('startDate', 'startDate_enabled', 'notchecked'); + + $mform->addElement('hidden', 'courseid', $this->_customdata['courseid']); + $mform->setType('courseid', PARAM_INT); + $mform->addElement('hidden', 'ocinstanceid', $ocinstanceid); + $mform->setType('ocinstanceid', PARAM_INT); + + if ($this->_customdata['redirectpage']) { + $mform->addElement('hidden', 'redirectpage', $this->_customdata['redirectpage']); + $mform->setType('redirectpage', PARAM_ALPHA); + } + if ($this->_customdata['series']) { + $mform->addElement('hidden', 'series', $this->_customdata['series']); + $mform->setType('series', PARAM_ALPHANUMEXT); + } + + $mform->closeHeaderBefore('buttonar'); + + $this->add_action_buttons(true, get_string('savechanges')); + } + + /** + * Tries to get the string for identifier and component. + * As a fallback it outputs the identifier itself with the first letter being uppercase. + * @param string $identifier The key identifier for the localized string + * @param string $component The module where the key identifier is stored, + * usually expressed as the filename in the language pack without the + * .php on the end but can also be written as mod/forum or grade/export/xls. + * If none is specified then moodle.php is used. + * @param string|object|array $a An object, string or number that can be used + * within translation strings + * @return string + * @throws coding_exception + */ + protected function try_get_string($identifier, $component = '', $a = null) { + if (!get_string_manager()->string_exists($identifier, $component)) { + return ucfirst($identifier); + } else { + return get_string($identifier, $component, $a); + } + } + + /** + * Validation. + * We check if there is any enabled field for the update, if not we return errors. + * + * @param array $data + * @param array $files + * @return array the errors that were found + */ + public function validation($data, $files) { + $errors = parent::validation($data, $files); + $fieldnames = array_column($this->_customdata['metadata_catalog'], 'name'); + $fieldnames[] = 'startDate'; + $enablefieldnames = array_map(function ($fieldname) { + return $fieldname . '_enabled'; + }, $fieldnames); + $enabledfields = array_filter(array_keys($data), function ($fieldname) use ($enablefieldnames) { + return in_array($fieldname, $enablefieldnames); + }); + if (empty($enabledfields)) { + $errors = array_merge($errors, + array_fill_keys($enablefieldnames, get_string('updatemetadata_massaction_emptyformsubmission', 'block_opencast'))); + } + return $errors; + } +} diff --git a/classes/local/upload_helper.php b/classes/local/upload_helper.php index a64eff81..22a973ef 100644 --- a/classes/local/upload_helper.php +++ b/classes/local/upload_helper.php @@ -781,6 +781,21 @@ public static function get_opencast_metadata_catalog_batch($ocinstanceid) { return !empty($batchmetadatacatalog) ? $batchmetadatacatalog : []; } + /** + * Gets the catalog of metadata fields from database for mass action. + * + * @param int $ocinstanceid Opencast instance id. + * @return array the metadata catalog array of stdClasses for mass action or empty array. + */ + public static function get_opencast_metadata_catalog_massaction(int $ocinstanceid): array { + $metadatacatalog = json_decode(get_config('block_opencast', 'metadata_' . $ocinstanceid)); + // As for mass action we don't need the single title catalog. + $massactionmetadatacatalog = array_filter($metadatacatalog, function ($metadata) { + return $metadata->name !== 'title'; + }); + return !empty($massactionmetadatacatalog) ? $massactionmetadatacatalog : []; + } + /** * Ensures that the series exists. * @param stdClass $job diff --git a/classes/local/visibility_form_massaction.php b/classes/local/visibility_form_massaction.php new file mode 100644 index 00000000..a348430e --- /dev/null +++ b/classes/local/visibility_form_massaction.php @@ -0,0 +1,234 @@ +. + +/** + * Change Visibility Mass Action Form + * + * @package block_opencast + * @copyright 2024 Farbod Zamani Boroujeni, ELAN e.V. + * @author Farbod Zamani Boroujeni + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace block_opencast\local; + +use block_opencast\groupaccess; +use block_opencast_renderer; +use moodleform; +use html_writer; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; + +require_once($CFG->dirroot . '/lib/formslib.php'); + +/** + * Change Visibility Mass Action Form + * + * @package block_opencast + * @copyright 2024 Farbod Zamani Boroujeni, ELAN e.V. + * @author Farbod Zamani Boroujeni + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class visibility_form_massaction extends moodleform { + + /** + * Form definition. + */ + public function definition() { + global $PAGE; + $mform = $this->_form; + + // Get the renderer to use its methods. + $renderer = $PAGE->get_renderer('block_opencast'); + $courseid = $this->_customdata['courseid']; + $videosdatalist = $this->_customdata['videosdatalist']; + $ocinstanceid = $this->_customdata['ocinstanceid']; + + $apibridge = apibridge::get_instance($ocinstanceid); + + $mform->addElement('hidden', 'courseid', $courseid); + $mform->setType('courseid', PARAM_INT); + + $mform->addElement('hidden', 'ocinstanceid', $ocinstanceid); + $mform->setType('ocinstanceid', PARAM_INT); + + $mform->addElement('hidden', 'ismassaction', 1); + $mform->setType('ismassaction', PARAM_INT); + + $videoslisthtmlitem = []; + foreach ($videosdatalist as $videodata) { + $videoslisthtmlitem[] = $videodata->detail; + if (empty($videodata->error)) { + $mform->addElement('hidden', 'videoids[]', $videodata->identifier); + } + } + $mform->setType('videoids', PARAM_ALPHANUMEXT); + if (!empty($videoslisthtmlitem)) { + $line = html_writer::tag('hr', ''); + $explanation = html_writer::tag('p', + get_string('massaction_selectedvideos_list', 'block_opencast', + implode('
  • ', $videoslisthtmlitem)) + ); + $mform->addElement('html', $line . $explanation . $line); + } + + // Check if the teacher should be allowed to restrict the episode to course groups. + $groups = []; + $groupvisibilityallowed = false; + $controlgroupsenabled = get_config('block_opencast', 'aclcontrolgroup_' . $ocinstanceid); + + // If group restriction is generally enabled, check if there are roles which allow group visibility. + if ($controlgroupsenabled) { + $roles = $apibridge->getroles(0); + foreach ($roles as $role) { + if (strpos($role->rolename, '[COURSEGROUPID]') >= 0) { + $groupvisibilityallowed = true; + $groups = groups_get_all_groups($courseid); + break; + } + } + } + + $radioarray = []; + $radioarray[] = $mform->addElement('radio', 'visibility', + get_string('visibility_massaction', 'block_opencast'), get_string('visibility_hide_massaction', 'block_opencast'), 0); + $radioarray[] = $mform->addElement('radio', 'visibility', '', + get_string('visibility_show_massaction', 'block_opencast'), 1); + // We need to remove the group visibility radio button, when there is no group in the course. + if ($groupvisibilityallowed && !empty($groups)) { + $radioarray[] = $mform->addElement('radio', 'visibility', '', + get_string('visibility_group_massaction', 'block_opencast'), 2); + } + $mform->setDefault('visibility', block_opencast_renderer::VISIBLE); + $mform->setType('visibility', PARAM_INT); + + // Load existing groups. + if ($groupvisibilityallowed && !empty($groups)) { + + $options = []; + + foreach ($groups as $group) { + $options[$group->id] = $group->name; + } + + $select = $mform->addElement('select', 'groups', get_string('groups'), $options); + $select->setMultiple(true); + + $mform->hideIf('groups', 'visibility', 'neq', 2); + } + + // Provide a checkbox to enable changing the visibility for later. + $mform->addElement('checkbox', 'enableschedulingchangevisibility', + get_string('enableschedulingchangevisibility_massaction', 'block_opencast'), + get_string('enableschedulingchangevisibilitydesc_massaction', 'block_opencast')); + $mform->hideIf('scheduledvisibilitytime', 'enableschedulingchangevisibility', 'notchecked'); + $mform->hideIf('scheduledvisibilitystatus', 'enableschedulingchangevisibility', 'notchecked'); + + $mform->setDefault('enableschedulingchangevisibility', false); + + // Scheduled visibility. + list($waitingtime, $configuredtimespan) = visibility_helper::get_waiting_time($ocinstanceid); + $element = $mform->addElement('date_time_selector', 'scheduledvisibilitytime', + get_string('scheduledvisibilitytime', 'block_opencast')); + $element->_helpbutton = $renderer->render_help_icon_with_custom_text( + get_string('scheduledvisibilitytimehi', 'block_opencast'), + get_string('scheduledvisibilitytimehi_help', 'block_opencast', $configuredtimespan)); + + $mform->setDefault('scheduledvisibilitytime', $waitingtime); + + $scheduleradioarray = []; + $scheduleradioarray[] = $mform->addElement('radio', 'scheduledvisibilitystatus', + get_string('scheduledvisibilitystatus', 'block_opencast'), + get_string('visibility_hide_massaction', 'block_opencast'), + 0 + ); + $scheduleradioarray[] = $mform->addElement('radio', 'scheduledvisibilitystatus', '', + get_string('visibility_show_massaction', 'block_opencast'), 1); + // We need to remove the group visibility radio button, we there is no group in the course. + if ($groupvisibilityallowed && !empty($groups)) { + $scheduleradioarray[] = $mform->addElement('radio', 'scheduledvisibilitystatus', + '', get_string('visibility_group_massaction', 'block_opencast'), 2); + } + + $mform->setDefault('scheduledvisibilitystatus', block_opencast_renderer::HIDDEN); + $mform->setType('scheduledvisibilitystatus', PARAM_INT); + + // Load existing groups. + if ($groupvisibilityallowed && !empty($groups)) { + $options = []; + foreach ($groups as $group) { + $options[$group->id] = $group->name; + } + $select = $mform->addElement('select', 'scheduledvisibilitygroups', get_string('groups'), $options); + $select->setMultiple(true); + $mform->hideIf('scheduledvisibilitygroups', 'scheduledvisibilitystatus', 'neq', 2); + $mform->hideIf('scheduledvisibilitygroups', 'enableschedulingchangevisibility', 'notchecked'); + } + + $this->add_action_buttons(true, get_string('savechanges')); + } + + /** + * Validation. + * + * @param array $data + * @param array $files + * @return array the errors that were found + */ + public function validation($data, $files) { + $errors = parent::validation($data, $files); + if ($data['visibility'] == block_opencast_renderer::GROUP && empty($data['groups'])) { + $errors['visibility'] = get_string('emptyvisibilitygroups', 'block_opencast'); + } + if (isset($data['enableschedulingchangevisibility']) && $data['enableschedulingchangevisibility']) { + // Deducting 2 minutes from the time, to let teachers finish the form. + $customminutes = [ + 'minutes' => 2, + 'action' => 'minus', + ]; + // Get custom allowed scheduled visibility time. + $waitingtimearray = visibility_helper::get_waiting_time( + $this->_customdata['ocinstanceid'], $customminutes); + $allowedscheduledvisibilitytime = $waitingtimearray[0]; + if (intval($data['scheduledvisibilitytime']) < intval($allowedscheduledvisibilitytime)) { + $errors['scheduledvisibilitytime'] = get_string('scheduledvisibilitytimeerror', + 'block_opencast', $waitingtimearray[1]); + } + if ($data['scheduledvisibilitystatus'] == block_opencast_renderer::GROUP && + empty($data['scheduledvisibilitygroups'])) { + $errors['enableschedulingchangevisibility'] = get_string('emptyvisibilitygroups', 'block_opencast'); + } + // Check whether the scheduled visibility is equal to initial visibility. + if (intval($data['scheduledvisibilitystatus']) == intval($data['visibility'])) { + $haserror = true; + if ($data['scheduledvisibilitystatus'] == block_opencast_renderer::GROUP) { + sort($data['scheduledvisibilitygroups']); + sort($data['groups']); + if ($data['scheduledvisibilitygroups'] != $data['groups']) { + $haserror = false; + } + } + if ($haserror) { + $errors['enableschedulingchangevisibility'] = get_string('scheduledvisibilitystatuserror', 'block_opencast'); + } + } + } + + return $errors; + } +} diff --git a/classes/local/visibility_helper.php b/classes/local/visibility_helper.php index 5dd903de..e1ddf57d 100644 --- a/classes/local/visibility_helper.php +++ b/classes/local/visibility_helper.php @@ -548,4 +548,27 @@ public static function get_uploadjob_scheduled_visibility($uploadjobid, $onlysch $visibility = $DB->get_record_sql($sql, $params); return !empty($visibility) ? $visibility : null; } + + /** + * Returns the legend for the visibility status based on the given status code. + * + * @param int $statuscode The status code for the visibility. + * + * @return array An array containing the legend title and description. + */ + public static function get_visibility_status_legend(int $statuscode): array { + $vkey = 'visible'; + if ($statuscode == block_opencast_renderer::HIDDEN) { + $vkey = 'hidden'; + } else if ($statuscode == block_opencast_renderer::GROUP) { + $vkey = 'group'; + } else if ($statuscode == block_opencast_renderer::MIXED_VISIBILITY) { + $vkey = 'mixed'; + } + + return [ + get_string('legendvisibility_' . $vkey, 'block_opencast'), + get_string('legendvisibility_' . $vkey . 'desc', 'block_opencast'), + ]; + } } diff --git a/deleteevent_massaction.php b/deleteevent_massaction.php new file mode 100644 index 00000000..17c13926 --- /dev/null +++ b/deleteevent_massaction.php @@ -0,0 +1,122 @@ +. + +/** + * Delete events - Mass action. + * + * @package block_opencast + * @copyright 2024 Farbod Zamani Boroujeni, ELAN e.V. + * @author Farbod Zamani Boroujeni + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +use block_opencast\local\apibridge; +use tool_opencast\local\settings_api; +use core\output\notification; +require_once('../../config.php'); + +global $PAGE, $OUTPUT, $CFG, $SITE; + +$ismassaction = required_param('ismassaction', PARAM_INT); +$videoids = required_param_array('videoids', PARAM_RAW); +$courseid = required_param('courseid', PARAM_INT); +$ocinstanceid = optional_param('ocinstanceid', settings_api::get_default_ocinstance()->id, PARAM_INT); +$redirectpage = optional_param('redirectpage', null, PARAM_ALPHA); +$series = optional_param('series', null, PARAM_ALPHANUMEXT); + +$baseurl = new moodle_url('/blocks/opencast/deleteevent_massaction.php', + ['courseid' => $courseid, 'ocinstanceid' => $ocinstanceid, + 'redirectpage' => $redirectpage, 'series' => $series, ]); +$PAGE->set_url($baseurl); + +require_login($courseid, false); + +$PAGE->set_pagelayout('incourse'); +$PAGE->set_title(get_string('pluginname', 'block_opencast')); +$PAGE->set_heading(get_string('pluginname', 'block_opencast')); + +if ($redirectpage == 'overviewvideos') { + $redirecturl = new moodle_url('/blocks/opencast/overview_videos.php', ['ocinstanceid' => $ocinstanceid, + 'series' => $series, ]); +} else if ($redirectpage == 'overview') { + $redirecturl = new moodle_url('/blocks/opencast/overview.php', ['ocinstanceid' => $ocinstanceid]); +} else { + $redirecturl = new moodle_url('/blocks/opencast/index.php', ['courseid' => $courseid, 'ocinstanceid' => $ocinstanceid]); +} + +$PAGE->navbar->add(get_string('pluginname', 'block_opencast'), $redirecturl); +$PAGE->navbar->add(get_string('deleteevent_massaction', 'block_opencast'), $baseurl); + +// Capability check. +$coursecontext = context_course::instance($courseid); +require_capability('block/opencast:deleteevent', $coursecontext); + +$opencast = apibridge::get_instance($ocinstanceid); + +$failed = []; +$succeeded = []; + +foreach ($videoids as $videoid) { + $video = $opencast->get_opencast_video($videoid); + $stringobj = new stdClass(); + $stringobj->name = $video->video->title; + if ($video->video) { + if ($opencast->trigger_delete_event($video->video->identifier)) { + $succeeded[] = $video->video->title; + } else { + $failed[] = $video->video->title; + } + continue; + } + $stringobj->reason = get_string('videonotfound', 'block_opencast'); + $failed[] = get_string('videostablemassaction_notification_reasoning', 'block_opencast', $stringobj); +} + +$failedtext = ''; +if (!empty($failed)) { + $failedtext = get_string( + 'deleteevent_massaction_notification_failed', + 'block_opencast', + implode('
  • ', $failed) + ); +} +$succeededtext = ''; +if (!empty($succeeded)) { + $succeededtext = get_string( + 'deleteevent_massaction_notification_success', + 'block_opencast', + implode('
  • ', $succeeded) + ); +} + +// If there is no changes, we redirect with warning. +if (empty($succeededtext) && empty($failedtext)) { + $nochangetext = get_string('deleteevent_massaction_notification_nochange', 'block_opencast'); + redirect($redirecturl, $nochangetext, null, notification::NOTIFY_ERROR); +} + +// Redirect with error if no success message is available. +if (empty($succeededtext) && !empty($failedtext)) { + redirect($redirecturl, $failedtext, null, notification::NOTIFY_ERROR); +} + +// Otherwise, notify the error message if exists. +if (!empty($failedtext)) { + \core\notification::add($failedtext, \core\notification::ERROR); +} + +// If hitting here, that means success message exists and we can redirect! +redirect($redirecturl, $succeededtext, null, notification::NOTIFY_SUCCESS); diff --git a/index.php b/index.php index 61b3f611..275563f4 100644 --- a/index.php +++ b/index.php @@ -27,6 +27,7 @@ use block_opencast\local\importvideosmanager; use block_opencast\local\liveupdate_helper; use block_opencast\local\ltimodulemanager; +use block_opencast\local\massaction_helper; use block_opencast\local\upload_helper; use block_opencast\opencast_connection_exception; use core\notification; @@ -82,6 +83,14 @@ 'timeout' => $liveupdatereloadtimeout, ]; $PAGE->requires->js_call_amd('block_opencast/block_index', 'init', [$courseid, $ocinstanceid, $coursecontext->id, $liveupdate]); + +$PAGE->requires->js_call_amd('block_opencast/block_massaction', 'init', + [ + $courseid, + $ocinstanceid, + massaction_helper::get_js_selectors(), + ] +); $PAGE->set_pagelayout('incourse'); $PAGE->set_title(get_string('pluginname', 'block_opencast')); @@ -162,7 +171,41 @@ /** @var block_opencast_renderer $renderer */ $renderer = $PAGE->get_renderer('block_opencast'); +$massaction = new massaction_helper(); + +// Mass-Action configuration for update metadata. +if (!$apibridge->can_update_metadata_massaction($courseid)) { + $massaction->massaction_action_activation(massaction_helper::MASSACTION_UPDATEMETADATA, false); +} + +// Mass-Action configuration for delete. +if (!$apibridge->can_delete_massaction($courseid)) { + $massaction->massaction_action_activation(massaction_helper::MASSACTION_DELETE, false); +} + +// Mass-Action configuration for change visibility. +if (!$apibridge->can_change_visibility_massaction($courseid) || !$toggleaclroles) { + $massaction->massaction_action_activation(massaction_helper::MASSACTION_CHANGEVISIBILITY, false); +} + +// Mass-Action configuration for start workflow. +if (!$apibridge->can_start_workflow_massaction($courseid) || !$workflowsavailable) { + $massaction->massaction_action_activation(massaction_helper::MASSACTION_STARTWORKFLOW, false); +} + +// We add the select columns and headers into the beginning of the headers and columns arrays, when mass actions are there! +if ($massaction->has_massactions()) { + array_unshift($headers, 'selectall'); + array_unshift($columns, 'select'); +} + foreach ($headers as $i => $header) { + // Take care of selectall at first. + if ($header == 'selectall') { + $headers[$i] = $massaction->render_master_checkbox(); + continue; + } + if (!empty($header)) { $headers[$i] = get_string('h' . $header, 'block_opencast'); } else { @@ -378,13 +421,13 @@ } if ($videodata->error == 0) { - echo html_writer::start_div('position-relative'); $table = $renderer->create_videos_tables('opencast-videos-table-' . $series, $headers, $columns, $baseurl); $deletedvideos = $DB->get_records("block_opencast_deletejob", [], "", "opencasteventid"); $engageurl = get_config('block_opencast', 'engageurl_' . $ocinstanceid); - foreach ($videodata->videos as $video) { + $isselectable = true; + $row = []; // Start date column. @@ -421,6 +464,7 @@ // Workflow state and actions column, depending if the video is currently deleted or not. if (array_key_exists($video->identifier, $deletedvideos)) { + $isselectable = false; // Workflow state column. $row[] = $renderer->render_processing_state_icon("DELETING"); @@ -541,9 +585,28 @@ } } + + $selectcheckbox = $massaction->render_item_checkbox($video, $isselectable); + if (!empty($selectcheckbox)) { + array_unshift($row, $selectcheckbox); + } + $table->add_data($row); } + + // Last check to deactivate mass action, if there is nothing to display in the table. + if (!$table->started_output) { + $massaction->activate_massaction(false); + } + // Rendering the table containter div. + echo html_writer::start_div('position-relative'); + // Rendering mass action on top. + echo $massaction->render_table_mass_actions_select('bulkselect-top'); + // Rendering table. $table->finish_html(); + // Rendering mass action on bottom. + echo $massaction->render_table_mass_actions_select('bulkselect-bottom'); + // Rendering closing table container div. echo html_writer::end_div(); } else { echo html_writer::div(get_string('errorgetblockvideos', 'block_opencast', $videodata->error), 'opencast-bc-wrap'); diff --git a/lang/en/block_opencast.php b/lang/en/block_opencast.php index 7c7e0da7..df169aa7 100644 --- a/lang/en/block_opencast.php +++ b/lang/en/block_opencast.php @@ -214,7 +214,21 @@ $string['changevisibility'] = 'Alter visibility'; $string['changevisibility_group'] = 'The video is visible to all student belonging to selected groups. Click to alter visibility.'; $string['changevisibility_header'] = 'Change visibility for {$a->title}'; +$string['changevisibility_header_massaction'] = 'Change visibility for selected videos'; $string['changevisibility_hidden'] = 'The video is visible to no student. Click to alter visibility.'; +$string['changevisibility_massaction'] = 'Alter visibility of selected videos.'; +$string['changevisibility_massaction_aclchangeerror'] = 'The visibility change has been failed.'; +$string['changevisibility_massaction_aclchangeerror_noscheduling'] = 'The visibility change could not be completed, and the scheduled visibility change was skipped.'; +$string['changevisibility_massaction_notification_failed'] = 'Unable to change visibility for the following video(s):
    '; +$string['changevisibility_massaction_notification_nochanges'] = 'No visibility changes were applied to the selected video(s) as the current visibility already matches the chosen setting:
    '; +$string['changevisibility_massaction_notification_schedulingfailed'] = 'Unable to schedule visibility change for the following video(s):
    '; +$string['changevisibility_massaction_notification_schedulingsucceeded'] = 'The visibility change for the selected video(s) has been successfully scheduled:
    '; +$string['changevisibility_massaction_notification_succeeded'] = 'The visibility of the selected video(s) has been successfully updated:
    '; +$string['changevisibility_massaction_novideos'] = 'Unable to find any video to alter the visibility.'; +$string['changevisibility_massaction_report_failed'] = '{$a->title}: {$a->error}'; +$string['changevisibility_massaction_videoerror'] = 'Change visibility for the video ({$a->title}) is not possible at the moment due to: {$a->error}'; +$string['changevisibility_massaction_visibility_status'] = '{$a->title}: {$a->vstatusdesc}'; +$string['changevisibility_massaction_visibility_status_with_scheduled'] = '{$a->title}: {$a->vstatusdesc} scheduled for "{$a->svstatus}" status on {$a->svdatetime}'; $string['changevisibility_mixed'] = 'The visibility of the video is in an invalid status. Click to choose the correct visibility.'; $string['changevisibility_visible'] = 'The video is visible to all students of the course. Click to alter visibility.'; $string['changingownerfailed'] = 'An error occured. The ownership could not be transferred.'; @@ -249,6 +263,10 @@ $string['deletedraft'] = 'Delete a video before transfer to Opencast'; $string['deletedraftdesc'] = 'You are about to delete this video before the transfer to Opencast.
    It will be removed from the transfer queue and will not be processed. Please do not continue unless you are absolutely sure.'; $string['deleteevent'] = 'Delete an event in Opencast'; +$string['deleteevent_massaction'] = 'Delete selected video(s) in Opencast'; +$string['deleteevent_massaction_notification_failed'] = 'Failed to delete the following videos:
    '; +$string['deleteevent_massaction_notification_nochange'] = 'Unable to delete any video due to missing video identifier.'; +$string['deleteevent_massaction_notification_success'] = 'The following selected video will be deleted shortly:
    '; $string['deleteeventdesc'] = 'You are about to delete this video permanently and irreversibly from Opencast.
    All embedded links to it will become invalid. Please do not continue unless you are absolutely sure.'; $string['deletegroupacldesc'] = 'You are about to delete the access to this video from this course.
    If the access is deleted, the video is not displayed in filepicker and in the list of available videos. This does not affect videos, which are already embedded.
    The video will not be deleted in Opencast.'; $string['deletetranscription'] = 'Delete transcription'; @@ -309,7 +327,9 @@ $string['enableopencaststudioreturnbtn'] = 'Show a redirect back button in Studio'; $string['enableopencaststudioreturnbtn_desc'] = 'When enabled, Studio then renders an additional button "Exit and go back" after up- or downloading the recording.'; $string['enableschedulingchangevisibility'] = 'Schedule a visibility change'; +$string['enableschedulingchangevisibility_massaction'] = 'Schedule a visibility change for the selected video(s)'; $string['enableschedulingchangevisibilitydesc'] = 'Set a date and a visibility status for the event in future, which will be performed using a scheduled task.'; +$string['enableschedulingchangevisibilitydesc_massaction'] = 'Set a date and a visibility status for the selected video(s) in future, which will be performed using a scheduled task.'; $string['engageplayerintegration'] = 'Engage player integration'; $string['engageredirect'] = 'Redirect to engage player'; $string['engageurl'] = 'URL of the Opencast Engage server'; @@ -534,6 +554,10 @@ $string['manageseriesforcourse'] = 'Manage series'; $string['managetranscriptions'] = 'Manage Transcriptions'; $string['managetranscriptions_header'] = 'Manage Event\'s Transcriptions'; +$string['massaction_invalidactionparam'] = 'Invalid action url path parameter'; +$string['massaction_invaliddefaultactions'] = 'Invalid actions detected!'; +$string['massaction_selectedvideos_list'] = 'You have selected the following video(s):
    '; +$string['massaction_videostatusmismatched'] = 'Inappropriate video processing state!'; $string['maxseries'] = 'Maximum number of series'; $string['maxseriesdesc'] = 'Specifies how many series can be assigned to a course. Teachers won\'t be able to add/import more series if the maximum number is reached.'; $string['maxseriesreached'] = 'You cannot add another series to this course because the course contains already the maximum number of series.'; @@ -793,6 +817,12 @@ $string['unabletomanagetranscriptions'] = 'Due to an active processing state or an ongoing workflow, managing the event\'s transcriptions is not possible at the moment.'; $string['unexpected_api_response'] = 'Unexpected API response.'; $string['updatemetadata'] = 'Update metadata for this event'; +$string['updatemetadata_massaction'] = 'Update metadata for selected video(s)'; +$string['updatemetadata_massaction_emptyformsubmission'] = 'At least one field must be enabled.'; +$string['updatemetadata_massaction_notification_failed'] = 'Unable to update metadata for the following video(s):
    '; +$string['updatemetadata_massaction_notification_nochange'] = 'No changes were made regarding metadata update for the selected video(s).'; +$string['updatemetadata_massaction_notification_succeeded'] = 'The metadata of the selected video(s) has been successfully updated:
    '; +$string['updatemetadata_massaction_videoerror'] = 'Update metadata for the video ({$a->title}) is not possible at the moment due to: {$a->error}'; $string['updatemetadata_short'] = 'Update metadata'; $string['updatemetadatafailed'] = 'The metadata could not be saved.'; $string['updatemetadatasaved'] = 'Metadata is saved.'; @@ -833,12 +863,32 @@ $string['videonotfound'] = 'Video not found'; $string['videosavailable'] = 'Videos available in this course'; $string['videosoverviewexplanation'] = 'Here you can see in which courses the videos are provided as activity to the students.
    The second column shows the activities of the courses where the series is managed by the Opencast block. The third column shows courses where the video is embedded without the series being managed by the block.'; +$string['videostable_massaction_changevisibility'] = 'Change Visibility'; +$string['videostable_massaction_changevisibility_modal_body'] = 'Are you sure you want to perform visibility change on the following selected videos:
    '; +$string['videostable_massaction_changevisibility_modal_title'] = 'Change Visibility for selected videos'; +$string['videostable_massaction_delete'] = 'Delete'; +$string['videostable_massaction_delete_modal_body'] = 'Are you sure you want to delete the following selected videos:
    '; +$string['videostable_massaction_delete_modal_title'] = 'Delete selected videos'; +$string['videostable_massaction_startworkflow'] = 'Start workflow'; +$string['videostable_massaction_startworkflow_info'] = 'You have selected the following videos to start the workflow on:'; +$string['videostable_massaction_startworkflow_modal_body'] = 'You have selected the following videos to start workflow for:
    Make sure the selected videos are correct and proceed.'; +$string['videostable_massaction_startworkflow_modal_title'] = 'Start workflow for selected videos'; +$string['videostable_massaction_updatemetadata'] = 'Update metadata'; +$string['videostable_massaction_updatemetadata_modal_body'] = 'Are you sure you want to update the metadata of the following selected videos:
    '; +$string['videostable_massaction_updatemetadata_modal_title'] = 'Update metadata of selected videos'; +$string['videostablemassaction_disabled_item'] = 'Not selectable'; +$string['videostablemassaction_label'] = 'With Selected Videos...'; +$string['videostablemassaction_notification_reasoning'] = '{$a->name}: {$a->reason}'; $string['videostoupload'] = 'Videos to upload to opencast'; $string['viewviedeosnotallowed'] = 'You are not allowed to view the videos in this series.'; $string['visibility'] = 'Visibility of the video'; $string['visibility_group'] = 'Allow all students belonging to selected groups to access the video'; +$string['visibility_group_massaction'] = 'Allow all students belonging to selected groups to access the selected video(s)'; $string['visibility_hide'] = 'Prevent any student from accessing the video'; +$string['visibility_hide_massaction'] = 'Prevent any student from accessing the selected video(s)'; +$string['visibility_massaction'] = 'Visibility of the selected video(s)'; $string['visibility_show'] = 'Allow all students of the course to access the video'; +$string['visibility_show_massaction'] = 'Allow all students of the course to access the selected video(s)'; $string['visibilityheader'] = 'Event Visibility'; $string['visibilityheaderexplanation'] = 'You are able to set the initial visibility status of the video before upload, as well as scheduling a visibility change when it is configured to do so.'; $string['workflow_invalid'] = 'This workflow does not exist or is not enabled.'; @@ -846,6 +896,9 @@ $string['workflow_opencast_invalid'] = 'This workflow does not exist in Opencast or is restricted. Please contact the administrator.'; $string['workflow_settings_opencast'] = 'Workflow Settings'; $string['workflow_started_failure'] = 'Starting workflow failed.'; +$string['workflow_started_massaction_nochange'] = 'Unable to start workflow due to missing video(s).'; +$string['workflow_started_massaction_notification_failed'] = 'Unable to start workflow for the following selected videos:
    '; +$string['workflow_started_massaction_notification_success'] = 'Workflow has been successfully started for the selected videos:
    '; $string['workflow_started_success'] = 'Workflow successfully started.'; $string['workflownotdefined'] = 'The workflow for updating metadata is not defined.'; $string['workflowrolesdesc'] = 'This workflow is triggered when updating event metadata or deleting/adding nonpermanent ACL rules. If not set, it will not be possible to change the visibility of uploaded videos through the block.'; diff --git a/overview_videos.php b/overview_videos.php index 0112ef57..6c72ea81 100644 --- a/overview_videos.php +++ b/overview_videos.php @@ -23,6 +23,7 @@ */ use block_opencast\local\apibridge; +use block_opencast\local\massaction_helper; use block_opencast\local\upload_helper; use core\notification; use mod_opencast\local\opencasttype; @@ -110,6 +111,13 @@ $PAGE->navbar->add(get_string('opencastseries', 'block_opencast'), new moodle_url('/blocks/opencast/overview.php', ['ocinstanceid' => $ocinstanceid])); $PAGE->navbar->add(get_string('pluginname', 'block_opencast'), $baseurl); +$PAGE->requires->js_call_amd('block_opencast/block_massaction', 'init', + [ + $SITE->id, + $ocinstanceid, + massaction_helper::get_js_selectors(), + ] +); echo $OUTPUT->header(); if ($ocseries) { @@ -153,12 +161,44 @@ // Build table. $columns = ['owner', 'videos', 'linked', 'activities', 'action']; -$headers = [ - get_string('owner', 'block_opencast'), - get_string('video', 'block_opencast'), - get_string('embeddedasactivity', 'block_opencast'), - get_string('embeddedasactivitywolink', 'block_opencast'), - get_string('heading_actions', 'block_opencast'), ]; +$headers = ['owner', 'video', 'embeddedasactivity', 'embeddedasactivitywolink', 'heading_actions']; + +$massaction = new massaction_helper(); +// Mass-Action configuration for update metadata. +if (!$hasaddvideopermission) { + $massaction->massaction_action_activation(massaction_helper::MASSACTION_UPDATEMETADATA, false); +} else { + // When it is offered, we add extra parameters. + $massaction->set_action_path_parameter(massaction_helper::MASSACTION_UPDATEMETADATA, 'redirectpage', 'overviewvideos'); + $massaction->set_action_path_parameter(massaction_helper::MASSACTION_UPDATEMETADATA, 'series', $series); +} + +// Mass-Action configuration for delete. +if (!$hasdeletepermission) { + $massaction->massaction_action_activation(massaction_helper::MASSACTION_DELETE, false); +} else { + // When it is offered, we add extra parameters. + $massaction->set_action_path_parameter(massaction_helper::MASSACTION_DELETE, 'redirectpage', 'overviewvideos'); + $massaction->set_action_path_parameter(massaction_helper::MASSACTION_DELETE, 'series', $series); +} + +// No visiblity change and no strat workflow is allowed from overview page! +$massaction->massaction_action_activation(massaction_helper::MASSACTION_CHANGEVISIBILITY, false); +$massaction->massaction_action_activation(massaction_helper::MASSACTION_STARTWORKFLOW, false); + +// We add the select columns and headers into the beginning of the headers and columns arrays, when mass actions are there! +if ($massaction->has_massactions()) { + array_unshift($headers, 'selectall'); + array_unshift($columns, 'select'); +} + +$headers = array_map(function ($header) use ($massaction) { + if ($header == 'selectall') { + return $massaction->render_master_checkbox(); + } + return get_string($header, 'block_opencast'); +}, $headers); + $table = $renderer->create_overview_videos_table('ignore', $headers, $columns, $baseurl); $videos = $apibridge->get_series_videos($series)->videos; @@ -168,11 +208,21 @@ foreach ($renderer->create_overview_videos_rows($videos, $apibridge, $ocinstanceid, $activityinstalled, $showchangeownerlink, false, $isseriesowner, $hasaddvideopermission, - $hasdownloadpermission, $hasdeletepermission, '', $hasaccesspermission) as $row) { + $hasdownloadpermission, $hasdeletepermission, '', $hasaccesspermission, $massaction) as $row) { $table->add_data($row); } +// Last check to deactivate mass action, if there is nothing to display in the table. +if (!$table->started_output) { + $massaction->activate_massaction(false); +} + +// Rendering mass action on top. +echo $massaction->render_table_mass_actions_select('bulkselect-top'); +// Rendering table. $table->finish_html(); +// Rendering mass action on bottom. +echo $massaction->render_table_mass_actions_select('bulkselect-bottom'); if ($opencasterror) { notification::error($opencasterror); diff --git a/renderer.php b/renderer.php index 465911c0..17aa33c2 100644 --- a/renderer.php +++ b/renderer.php @@ -28,6 +28,7 @@ use block_opencast\local\attachment_helper; use block_opencast\local\ingest_uploader; use block_opencast\local\ltimodulemanager; +use block_opencast\local\massaction_helper; use block_opencast\local\upload_helper; use block_opencast\local\visibility_helper; use mod_opencast\local\opencasttype; @@ -250,7 +251,7 @@ public function create_videos_tables($id, $headers, $columns, $baseurl) { $table = new block_opencast\local\flexible_table($id); $table->set_attribute('cellspacing', '0'); $table->set_attribute('cellpadding', '3'); - $table->set_attribute('class', 'generaltable'); + $table->set_attribute('class', 'generaltable opencast-videos-table'); $table->set_attribute('id', $id); $table->headers = $headers; $table->define_columns($columns); @@ -260,8 +261,32 @@ public function create_videos_tables($id, $headers, $columns, $baseurl) { $table->no_sorting('provide'); $table->no_sorting('provide-activity'); $table->no_sorting('published'); + $table->no_sorting('visibility'); // This column cannot be sortable because it does not mean anything to Opencast! + $table->no_sorting('select'); $table->sortable(true, 'start_date', SORT_DESC); + $table->column_style('selectall', 'max-width', '40px'); + $table->column_style('start_date', 'min-width', '125px'); + $table->column_style('visibility', 'min-width', '120px'); + $table->column_style('workflow_state', 'min-width', '120px'); + $table->column_style('action', 'min-width', '100px'); + + $columnclasses = [ + 'selectall' => ['oc-col-select'], + 'workflow_state' => ['oc-col-wfstatus'], + 'visibility' => ['oc-col-visibility'], + ]; + + foreach ($columns as $column) { + $classes = isset($columnclasses[$column]) ? $columnclasses[$column] : []; + if ($table->is_sortable($column)) { + $classes[] = 'oc-sortable-alignment'; + } + if (!empty($classes)) { + $table->column_class($column, implode(' ', $classes)); + } + } + $table->pageable(true); $table->is_downloadable(false); @@ -336,6 +361,8 @@ public function create_overview_videos_table($id, $headers, $columns, $baseurl) $table->no_sorting('owner'); $table->no_sorting('linked'); $table->no_sorting('activities'); + $table->no_sorting('select'); + $table->no_sorting('action'); $table->sortable(true, 'videos', SORT_DESC); $table->pageable(true); @@ -367,6 +394,7 @@ public function create_overview_videos_table($id, $headers, $columns, $baseurl) * @param bool $hasdeletepermission * @param string $redirectpage * @param bool $hasaccesspermission + * @param ?massaction_helper $massaction * @return array * @throws coding_exception * @throws dml_exception @@ -376,11 +404,17 @@ public function create_overview_videos_rows($videos, $apibridge, $ocinstanceid, $showchangeownerlink, $isownerverified = false, $isseriesowner = false, $hasaddvideopermissions = false, $hasdownloadpermission = false, $hasdeletepermission = false, - $redirectpage = 'overviewvideos', $hasaccesspermission = false) { + $redirectpage = 'overviewvideos', $hasaccesspermission = false, + $massaction = null) { global $USER, $SITE, $DB; $rows = []; foreach ($videos as $video) { + + // This flag here works in false case, because here in overview page we only offer update and delete mass action. + // We only offer them if the single action has been offered too. + $isselectable = false; + $activitylinks = []; if ($activityinstalled) { $activitylinks = $DB->get_records('opencast', ['ocinstanceid' => $ocinstanceid, @@ -434,6 +468,9 @@ public function create_overview_videos_rows($videos, $apibridge, $ocinstanceid, $actions = ''; if ($hasaddvideopermissions) { $updatemetadata = $apibridge->can_update_event_metadata($video, $SITE->id, false); + if ($updatemetadata) { + $isselectable = true; + } $actions .= $this->render_edit_functions($ocinstanceid, $SITE->id, $video->identifier, $updatemetadata, false, null, false, false, false, 'overview', $video->is_part_of); } @@ -448,6 +485,7 @@ public function create_overview_videos_rows($videos, $apibridge, $ocinstanceid, if ($hasdeletepermission && isset($video->processing_state) && ($video->processing_state !== 'RUNNING' && $video->processing_state !== 'PAUSED')) { + $isselectable = true; $url = new moodle_url('/blocks/opencast/deleteevent.php', ['identifier' => $video->identifier, 'courseid' => $SITE->id, 'ocinstanceid' => $ocinstanceid, 'series' => $video->is_part_of, 'redirectpage' => $redirectpage, ]); @@ -458,6 +496,13 @@ public function create_overview_videos_rows($videos, $apibridge, $ocinstanceid, $row[] = $actions; + if (!is_null($massaction)) { + $selectcheckbox = $massaction->render_item_checkbox($video, $isselectable); + if (!empty($selectcheckbox)) { + array_unshift($row, $selectcheckbox); + } + } + $rows[] = $row; } diff --git a/startworkflow_massaction.php b/startworkflow_massaction.php new file mode 100644 index 00000000..16a2aafe --- /dev/null +++ b/startworkflow_massaction.php @@ -0,0 +1,126 @@ +. + +/** + * Start workflow - Mass action. + * + * @package block_opencast + * @copyright 2024 Farbod Zamani Boroujeni, ELAN e.V. + * @author Farbod Zamani Boroujeni + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +use block_opencast\local\apibridge; +use core\output\notification; +use tool_opencast\local\settings_api; +require_once('../../config.php'); + +global $PAGE, $OUTPUT, $CFG, $USER, $COURSE, $DB; + +$ismassaction = required_param('ismassaction', PARAM_INT); +$videoids = required_param_array('videoids', PARAM_RAW); +$courseid = required_param('courseid', PARAM_INT); +$workflow = required_param('workflow', PARAM_ALPHANUMEXT); +$configparams = required_param('configparams', PARAM_RAW); +$ocinstanceid = optional_param('ocinstanceid', settings_api::get_default_ocinstance()->id, PARAM_INT); + +$redirecturl = new moodle_url('/blocks/opencast/index.php', ['courseid' => $courseid, 'ocinstanceid' => $ocinstanceid]); + +require_login($courseid, false); + +// Capability check. +$coursecontext = context_course::instance($courseid); +require_capability('block/opencast:startworkflow', $coursecontext); + +$apibridge = apibridge::get_instance($ocinstanceid); + +$seriesid = $apibridge->get_default_course_series($courseid); +$apiworkflow = $apibridge->get_workflow_definition($workflow); + +// Apply multiple tags. +$workflowtags = []; +$workflowtagsconfig = get_config('block_opencast', 'workflow_tags_' . $ocinstanceid); +if (!empty($workflowtagsconfig)) { + $workflowtags = explode(',', $workflowtagsconfig); + $workflowtags = array_map('trim', $workflowtags); +} +if (!$apiworkflow || empty(array_intersect($apiworkflow->tags, $workflowtags))) { + redirect($redirecturl, + get_string('workflow_opencast_invalid', 'block_opencast'), + null, + notification::NOTIFY_ERROR); +} + +$seriesid = $apibridge->get_default_course_series($courseid); + +$failed = []; +$succeeded = []; + +foreach ($videoids as $videoid) { + $video = $apibridge->get_opencast_video($videoid); + $stringobj = new stdClass(); + $stringobj->name = $video->video->title; + if ($seriesid->identifier != $video->video->is_part_of) { + $stringobj->reason = get_string('video_notallowed', 'block_opencast'); + $failed[] = get_string('videostablemassaction_notification_reasoning', 'block_opencast', $stringobj); + continue; + } + + $result = $apibridge->start_workflow($videoid, $workflow, ['configuration' => $configparams]); + + if ($result) { + $succeeded[] = $video->video->title; + } else { + $stringobj->reason = get_string('workflow_started_failure', 'block_opencast'); + $failed[] = get_string('videostablemassaction_notification_reasoning', 'block_opencast', $stringobj); + } +} + +$failedtext = ''; +if (!empty($failed)) { + $failedtext = get_string( + 'workflow_started_massaction_notification_failed', + 'block_opencast', + implode('
  • ', $failed) + ); +} +$succeededtext = ''; +if (!empty($succeeded)) { + $succeededtext = get_string( + 'workflow_started_massaction_notification_success', + 'block_opencast', + implode('
  • ', $succeeded) + ); +} + +// If there is no changes, we redirect with warning. +if (empty($succeededtext) && empty($failedtext)) { + $nochangetext = get_string('workflow_started_massaction_nochange', 'block_opencast'); + redirect($redirecturl, $nochangetext, null, notification::NOTIFY_ERROR); +} + +// Redirect with error if no success message is available. +if (empty($succeededtext) && !empty($failedtext)) { + redirect($redirecturl, $failedtext, null, notification::NOTIFY_ERROR); +} + +// Otherwise, notify the error message if exists. +if (!empty($failedtext)) { + \core\notification::add($failedtext, \core\notification::ERROR); +} + +// If hitting here, that means success message exists and we can redirect! +redirect($redirecturl, $succeededtext, null, notification::NOTIFY_SUCCESS); diff --git a/styles.css b/styles.css index 68af970e..0610bceb 100644 --- a/styles.css +++ b/styles.css @@ -99,6 +99,26 @@ display: none; } +/* Video Table Styles */ +table.opencast-videos-table.generaltable thead th { + vertical-align: middle !important; /* stylelint-disable-line */ +} + +table.opencast-videos-table.generaltable thead th.oc-sortable-alignment { + overflow: hidden; +} + +table.opencast-videos-table.generaltable thead th.oc-sortable-alignment a { + float: left; + white-space: nowrap; + margin-bottom: 1px; +} + +table.opencast-videos-table.generaltable thead th.oc-sortable-alignment > i { + float: left; + margin: 4px 0 1px 3px; +} + /* Styles for Series Table */ #seriestable .tabulator-row .tabulator-cell { cursor: default !important; /* stylelint-disable-line */ diff --git a/tests/behat/behat_block_opencast.php b/tests/behat/behat_block_opencast.php index 9daca684..aca43bf1 100644 --- a/tests/behat/behat_block_opencast.php +++ b/tests/behat/behat_block_opencast.php @@ -225,4 +225,40 @@ public function the_lti_tool_in_the_course_should_have_the_custom_parameter($lti " instead of expected \"$customparameter\"", $this->getSession()); } } + + /** + * Checks whether the checkbox is checked, throws exception if it is not checked. + * + * @Then /^the "(?P(?:[^"]|\\")*)" checkbox should be checked$/ + * @throws ExpectationException Thrown by behat_base::find and isChecked + * @param string $element Element we look on + */ + public function the_element_checkbox_should_be_checked($element) { + $element = $this->find("checkbox", $element); + if (!$element) { + throw new ExpectationException('Checkbox not found with the provided XPath.', $this->getSession()); + } + + if (!$element->isChecked()) { + throw new ExpectationException('The checkbox is not checked.', $this->getSession()); + } + } + + /** + * Checks whether the checkbox is not checked, throws error if it is checked. + * + * @Then /^the "(?P(?:[^"]|\\")*)" checkbox should not be checked$/ + * @throws ExpectationException Thrown by behat_base::find and isChecked + * @param string $element Element we look on + */ + public function the_element_checkbox_should_not_be_checked($element) { + $element = $this->find("checkbox", $element); + if (!$element) { + throw new ExpectationException('Checkbox not found with the provided XPath.', $this->getSession()); + } + + if ($element->isChecked()) { + throw new ExpectationException('The checkbox is checked.', $this->getSession()); + } + } } diff --git a/tests/behat/block_opencast_autocomplete_metadata.feature b/tests/behat/block_opencast_autocomplete_metadata.feature index 8571ef4f..40fb224b 100644 --- a/tests/behat/block_opencast_autocomplete_metadata.feature +++ b/tests/behat/block_opencast_autocomplete_metadata.feature @@ -44,7 +44,7 @@ Feature: Check and set autocompletion suggestions @javascript Scenario: Autocomplete suggestions for Presentors must be extracted from the access capabilities and only show teacher and editingteacher enroled users. When I click on "Go to overview..." "link" - And I click on "#opencast-videos-table-1234-1234-1234-1234-1234_r0 .c3 .action-menu a" "css_element" + And I click on "#opencast-videos-table-1234-1234-1234-1234-1234_r0 .cell .action-menu a" "css_element" And I click on "Update metadata" "link" Then I should see "Update metadata" When I expand the "Presenter(s)" autocomplete diff --git a/tests/behat/block_opencast_massactions.feature b/tests/behat/block_opencast_massactions.feature new file mode 100644 index 00000000..29a9d1db --- /dev/null +++ b/tests/behat/block_opencast_massactions.feature @@ -0,0 +1,146 @@ +@block @block_opencast @block_opencast_massactions +Feature: Select all videos and perform mass actions in the Opencast Block Overview page + As a teacher, I want to be able to select all videos from the video list table + and perform mass actions so that I can manage multiple videos efficiently. + Background: + Given the following "users" exist: + | username | firstname | lastname | email | idnumber | + | teacher1 | Teacher | 1 | teacher1@example.com | T1 | + And the following "courses" exist: + | fullname | shortname | category | + | Course 1 | C1 | 0 | + And the following "course enrolments" exist: + | user | course | role | + | teacher1 | C1 | editingteacher | + And I setup the default settigns for opencast plugins + And the following config values are set as admin: + | config | value | plugin | + | apiurl_1 | http://testapi:8080 | tool_opencast | + | apipassword_1 | opencast | tool_opencast | + | apiusername_1 | admin | tool_opencast | + | ocinstances | [{"id":1,"name":"Default","isvisible":true,"isdefault":true}] | tool_opencast | + | limituploadjobs_1 | 0 | block_opencast | + | group_creation_1 | 0 | block_opencast | + | group_name_1 | Moodle_course_[COURSEID] | block_opencast | + | series_name_1 | Course_Series_[COURSEID] | block_opencast | + | enablechunkupload_1 | 0 | block_opencast | + | workflow_roles_1 | republish-metadata | block_opencast | + | aclcontrolafter_1 | 1 | block_opencast | + | metadata_1 | [{"name":"rightsHolder","datatype":"text","required":0,"readonly":0,"param_json":null}] | block_opencast | + | workflow_tags_1 | archive | block_opencast | + And I setup the opencast test api + And I upload a testvideo + And I log in as "teacher1" + And I am on "Course 1" course homepage with editing mode on + And I add the "Opencast Videos" block + + @javascript + Scenario: Teachers should see the mass action elements and be able to select and deselect all or one video, select actions from dropdown only when videos are selected, see the confrimation dialog when selecting a mass action and the video selection should be reset when the confirmation dialogue is canceled. + Given I click on "Go to overview..." "link" + Then the "With Selected Videos..." "select" should be disabled + And the "Select all" checkbox should not be checked + # Testing basic video selection via select all. + When I click on "Select all" "checkbox" + Then the "With Selected Videos..." "select" should be enabled + And the "Select Test video" checkbox should be checked + When I click on "Select all" "checkbox" + Then the "With Selected Videos..." "select" should be disabled + And the "Select Test video" checkbox should not be checked + # Testing basic video selection via a video. + When I click on "Select Test video" "checkbox" + Then the "With Selected Videos..." "select" should be enabled + And the "Select all" checkbox should be checked + When I click on "Select Test video" "checkbox" + Then the "With Selected Videos..." "select" should be disabled + And the "Select all" checkbox should not be checked + # Testing actions dropdowns selection. + When I click on "Select all" "checkbox" + Then the "With Selected Videos..." "select" should be enabled + And the "Select Test video" checkbox should be checked + When I click on "With Selected Videos..." "select" + Then I should see "Delete" + And I should see "Update metadata" + And I should see "Change Visibility" + And I should see "Start workflow" + # Testing confirmation dialogue and reset video selection when confirmation is cancelled. + When I select "Update metadata" from the "With Selected Videos..." singleselect + Then I should see "Are you sure you want to update the metadata of the following selected videos:" + When I click on "Cancel" "button" in the "Update metadata of selected videos" "dialogue" + Then the "Select all" checkbox should not be checked + And the "Select Test video" checkbox should not be checked + And the "With Selected Videos..." "select" should be disabled + + @javascript + Scenario: The mass actions should not be provided when conditions are not met, such as permissions and configurations. + Given the following "permission overrides" exist: + | capability | permission | role | contextlevel | reference | + | block/opencast:deleteevent | Prevent | editingteacher | Course | C1 | + | block/opencast:startworkflow | Prevent | editingteacher | Course | C1 | + | block/opencast:addvideo | Prevent | editingteacher | Course | C1 | + When I click on "Go to overview..." "link" + Then I should not see "With Selected Videos..." + When the following "permission overrides" exist: + | capability | permission | role | contextlevel | reference | + | block/opencast:deleteevent | Allow | editingteacher | Course | C1 | + | block/opencast:startworkflow | Allow | editingteacher | Course | C1 | + | block/opencast:addvideo | Allow | editingteacher | Course | C1 | + And I reload the page + Then I should see "With Selected Videos..." + When the following config values are set as admin: + | workflow_tags_1 | | block_opencast | + | aclcontrolafter_1 | 0 | block_opencast | + And I reload the page + And I click on "With Selected Videos..." "select" + Then "Start workflow" "option" should not exist in the "With Selected Videos..." "select" + And "Change Visibility" "option" should not exist in the "With Selected Videos..." "select" + When the following config values are set as admin: + | workflow_tags_1 | archive | block_opencast | + | aclcontrolafter_1 | 1 | block_opencast | + And I reload the page + And I click on "With Selected Videos..." "select" + Then I should see "Delete" + And I should see "Update metadata" + And I should see "Change Visibility" + And I should see "Start workflow" + + @javascript + Scenario: Teachers should be able to perform the mass action after the confirmation + Given I click on "Go to overview..." "link" + # Testing start workflow. + When I click on "Select all" "checkbox" + And I select "Start workflow" from the "With Selected Videos..." singleselect + Then I should see "You have selected the following videos to start workflow for:" + And I wait "1" seconds + And I set the field "workflow" to "duplicate-event" + When I click on "Start workflow" "button" + And I wait "2" seconds + Then I should see "Workflow has been successfully started for the selected videos:" + # Testing Change Visibility. + When I click on "Select all" "checkbox" + And I select "Change Visibility" from the "With Selected Videos..." singleselect + Then I should see "Are you sure you want to perform visibility change on the following selected videos:" + When I click on "Change Visibility" "button" + And I wait "1" seconds + Then I should see "You have selected the following video(s):" + And I click on "#id_visibility_1" "css_element" + And I click on "Save changes" "button" + And I should see "The visibility of the selected video(s) has been successfully updated:" + # Testing Update metadata. + When I click on "Select all" "checkbox" + And I select "Update metadata" from the "With Selected Videos..." singleselect + Then I should see "Are you sure you want to update the metadata of the following selected videos:" + When I click on "Update metadata" "button" + And I wait "1" seconds + Then I should see "You have selected the following video(s):" + And I click on "#id_rightsHolder_enabled" "css_element" + And I set the field "Rights" to "TEST TEXT FOR RIGHTS" + When I click on "Save changes" "button" + And I wait "1" seconds + Then I should see "The metadata of the selected video(s) has been successfully updated:" + # Testing Delete. + When I click on "Select all" "checkbox" + And I select "Delete" from the "With Selected Videos..." singleselect + Then I should see "Are you sure you want to delete the following selected videos:" + When I click on "Delete" "button" in the ".modal" "css_element" + And I wait "1" seconds + Then I should see "The following selected video will be deleted shortly:" diff --git a/updatemetadata_massaction.php b/updatemetadata_massaction.php new file mode 100644 index 00000000..cc807113 --- /dev/null +++ b/updatemetadata_massaction.php @@ -0,0 +1,221 @@ +. + +/** + * Update the metadata of selected videos - Mass action. + * + * @package block_opencast + * @copyright 2024 Farbod Zamani Boroujeni, ELAN e.V. + * @author Farbod Zamani Boroujeni + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +use block_opencast\local\apibridge; +use block_opencast\local\updatemetadata_form_massaction; +use block_opencast\local\upload_helper; +use core\output\notification; +use tool_opencast\local\settings_api; +require_once('../../config.php'); + +global $PAGE, $OUTPUT, $CFG, $SITE; + +require_once($CFG->dirroot . '/repository/lib.php'); + +$ismassaction = required_param('ismassaction', PARAM_INT); +$videoids = required_param_array('videoids', PARAM_RAW); +$courseid = required_param('courseid', PARAM_INT); +$ocinstanceid = optional_param('ocinstanceid', settings_api::get_default_ocinstance()->id, PARAM_INT); +$redirectpage = optional_param('redirectpage', null, PARAM_ALPHA); +$series = optional_param('series', null, PARAM_ALPHANUMEXT); + +$baseurl = new moodle_url('/blocks/opencast/updatemetadata_massaction.php', + ['courseid' => $courseid, 'ocinstanceid' => $ocinstanceid, + 'redirectpage' => $redirectpage, 'series' => $series, ]); +$PAGE->set_url($baseurl); + +if ($redirectpage == 'overviewvideos') { + $redirecturl = new moodle_url('/blocks/opencast/overview_videos.php', ['ocinstanceid' => $ocinstanceid, + 'series' => $series, ]); +} else if ($redirectpage == 'overview') { + $redirecturl = new moodle_url('/blocks/opencast/overview.php', ['ocinstanceid' => $ocinstanceid]); +} else { + $redirecturl = new moodle_url('/blocks/opencast/index.php', ['courseid' => $courseid, 'ocinstanceid' => $ocinstanceid]); +} + +require_login($courseid, false); + +$PAGE->set_pagelayout('incourse'); +$PAGE->set_title(get_string('pluginname', 'block_opencast')); +$PAGE->set_heading(get_string('pluginname', 'block_opencast')); +$PAGE->navbar->add(get_string('pluginname', 'block_opencast'), $redirecturl); +$PAGE->navbar->add(get_string('updatemetadata_massaction', 'block_opencast'), $baseurl); + +// Capability check. +$coursecontext = context_course::instance($courseid); +require_capability('block/opencast:addvideo', $coursecontext); + +$opencast = apibridge::get_instance($ocinstanceid); +$massactionmetadatacatalog = upload_helper::get_opencast_metadata_catalog_massaction($ocinstanceid); + +$videosdatalist = []; + +foreach ($videoids as $videoid) { + + // Record the video data for later use. + $videodata = new stdClass(); + $videodata->identifier = $videoid; + $videodata->title = $videoid; + $videodata->detail = null; + $videodata->error = false; + + $video = $opencast->get_opencast_video($videoid); + + if (!empty($video->error)) { + $videodata->error = get_string('videonotfound', 'block_opencast'); + $videodata->detail = get_string('updatemetadata_massaction_videoerror', 'block_opencast', $videodata); + $videosdatalist[] = $videodata; + continue; + } + + $videodata->title = $video->video->title; + + if (!$opencast->can_update_event_metadata($video->video, $courseid, false)) { + $videodata->error = get_string('massaction_videostatusmismatched', 'block_opencast'); + $videodata->detail = get_string('updatemetadata_massaction_videoerror', 'block_opencast', $videodata); + $videosdatalist[] = $videodata; + continue; + } + + // Bring the video data to the top. + array_unshift($videosdatalist, $videodata); +} + +$massactionupdatemetadataform = new updatemetadata_form_massaction(null, + ['metadata_catalog' => $massactionmetadatacatalog, 'courseid' => $courseid, + 'ocinstanceid' => $ocinstanceid, 'redirectpage' => $redirectpage, + 'videosdatalist' => $videosdatalist, 'series' => $series, ]); + +if ($massactionupdatemetadataform->is_cancelled()) { + redirect($redirecturl); +} + +if ($data = $massactionupdatemetadataform->get_data()) { + if (confirm_sesskey()) { + $selectedmetadata = []; + $metadataids = array_column($massactionmetadatacatalog, 'name'); + + // Adding Start Date manually. + if (!in_array('startDate', $metadataids)) { + $metadataids[] = 'startDate'; + } + + foreach ($metadataids as $key) { + // We check if the metadata was allowed/enabled for the change and update. + if (isset($data->$key) && isset($data->{$key . "_enabled"})) { + $sd = null; + if ($key == 'startDate') { + $sd = new DateTime("now", new DateTimeZone("UTC")); + $sd->setTimestamp($data->startDate); + $starttime = [ + 'id' => 'startTime', + 'value' => $sd->format('H:i:s') . 'Z', + ]; + $selectedmetadata[] = $starttime; + } + $contentobj = [ + 'id' => $key, + 'value' => ($key == 'startDate' && !empty($sd)) ? $sd->format('Y-m-d') : $data->$key, + ]; + $selectedmetadata[] = $contentobj; + } + } + + $failed = []; + $succeeded = []; + if (!empty($selectedmetadata)) { + // All processed video data is included in $videosdatalist variable beforehand! + $selectedmetadataids = array_column($selectedmetadata, 'id'); + foreach ($videosdatalist as $videodata) { + // Now we need to get and replace the metadata of each video to make sure it only changes allowed catalogs. + $metadata = $opencast->get_event_metadata($videodata->identifier, 'dublincore/episode'); + $metadataids = array_column($metadata, 'id'); + $squashedmetadata = []; + foreach ($metadataids as $index => $id) { + $currentcatalog = $metadata[$index]; + $value = $currentcatalog->value; + // If the metadata id exists in the selected ids (is allowed to be updated), we replace the value. + if (in_array($id, $selectedmetadataids)) { + $value = $selectedmetadata[array_search($id, $selectedmetadataids)]['value']; + } + $newobj = [ + 'id' => $id, + 'value' => $value, + ]; + $squashedmetadata[] = $newobj; + } + $res = $opencast->update_event_metadata($videodata->identifier, $squashedmetadata); + if ($res) { + $succeeded[] = $videodata->title; + } else { + $failed[] = $videodata->title; + } + } + } + + $failedtext = ''; + if (!empty($failed)) { + $failedtext = get_string( + 'updatemetadata_massaction_notification_failed', + 'block_opencast', + implode('
  • ', $failed) + ); + } + $succeededtext = ''; + if (!empty($succeeded)) { + $succeededtext = get_string( + 'updatemetadata_massaction_notification_succeeded', + 'block_opencast', + implode('
  • ', $succeeded) + ); + } + + // If there is no changes, we redirect with warning. + if (empty($succeededtext) && empty($failedtext)) { + $nochangetext = get_string('updatemetadata_massaction_notification_nochange', 'block_opencast'); + redirect($redirecturl, $nochangetext, null, notification::NOTIFY_WARNING); + } + + // Redirect with error if no success message is available. + if (empty($succeededtext) && !empty($failedtext)) { + redirect($redirecturl, $failedtext, null, notification::NOTIFY_ERROR); + } + + // Otherwise, notify the error message if exists. + if (!empty($failedtext)) { + \core\notification::add($failedtext, \core\notification::ERROR); + } + + // If hitting here, that means success message exists and we can redirect! + redirect($redirecturl, $succeededtext, null, notification::NOTIFY_SUCCESS); + } +} +$PAGE->requires->js_call_amd('block_opencast/block_form_handler', 'init'); +$renderer = $PAGE->get_renderer('block_opencast'); + +echo $OUTPUT->header(); +echo $OUTPUT->heading(get_string('updatemetadata_massaction', 'block_opencast')); +$massactionupdatemetadataform->display(); +echo $OUTPUT->footer();