From e16ecf2caec1459a80c34123af4d29c39dbfd2c1 Mon Sep 17 00:00:00 2001 From: Alex Yeung Date: Sat, 16 Nov 2024 17:55:36 +0000 Subject: [PATCH] CTP-1685 add unfreeze function in lifecycle block --- amd/build/lifecycle.min.js | 2 +- amd/build/lifecycle.min.js.map | 2 +- amd/src/lifecycle.js | 59 +------------------ classes/external/unfreeze_course.php | 86 ---------------------------- classes/manager.php | 5 +- db/services.php | 10 ---- renderer.php | 18 +++--- templates/unfreeze_button.mustache | 47 +++++++++++++++ unfreeze.php | 60 +++++++++++++++++++ version.php | 2 +- 10 files changed, 126 insertions(+), 165 deletions(-) delete mode 100644 classes/external/unfreeze_course.php create mode 100644 templates/unfreeze_button.mustache create mode 100644 unfreeze.php diff --git a/amd/build/lifecycle.min.js b/amd/build/lifecycle.min.js index 24c262f..d5691ff 100644 --- a/amd/build/lifecycle.min.js +++ b/amd/build/lifecycle.min.js @@ -1,3 +1,3 @@ -define("block_lifecycle/lifecycle",["exports","core/ajax","core/notification","core/str"],(function(_exports,_ajax,_notification,_str){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_ajax=_interopRequireDefault(_ajax),_notification=_interopRequireDefault(_notification);let defaultfreezedate="",originalfreezedatevalue="";function togglefreezebutton(checked){let readonlydateinput=document.getElementById("delayfreezedate");checked?(readonlydateinput.value="",readonlydateinput.disabled=!0):readonlydateinput.disabled=!1}function togglesettings(){let content=document.getElementById("automatic-read-only-settings");document.getElementById("override-freeze-date-button").classList.toggle("active"),content.style.maxHeight?content.style.maxHeight=null:content.style.maxHeight=content.scrollHeight+"px"}function initscheduledfreezedateblock(courseid){let scheduledfreezedatecontainer=document.getElementById("scheduled-freeze-date-container");originalfreezedatevalue=document.getElementById("delayfreezedate").value,scheduledfreezedatecontainer.style.display="none",_ajax.default.call([{methodname:"block_lifecycle_get_scheduled_freeze_date",args:{courseid:courseid}}])[0].done((function(response){document.getElementById("togglefreezebutton").checked?togglefreezebutton(!0):(document.getElementById("scheduled-freeze-date").innerHTML=response.scheduledfreezedate,scheduledfreezedatecontainer.style.display="block"),"true"===response.success&&(defaultfreezedate=response.defaultfreezedate)})).fail((function(err){window.console.log(err)}))}_exports.init=courseid=>{document.getElementById("lifecycle-settings-container")?(initscheduledfreezedateblock(courseid),document.getElementById("togglefreezebutton").addEventListener("click",(event=>{togglefreezebutton(event.target.checked)})),document.getElementById("update_auto_freezing_preferences_button").addEventListener("click",(()=>{!function(courseid){let preferences={togglefreeze:document.getElementById("togglefreezebutton").checked,delayfreezedate:document.getElementById("delayfreezedate").value};(function(){let freezedateelement=document.getElementById("delayfreezedate");if(freezedateelement.value.length>0){if(""===defaultfreezedate)return _notification.default.alert("Error","Could not get the automatically suggested date, please try again later.","OK"),freezedateelement.value=originalfreezedatevalue,!1;{let defaultfreezedateobj=new Date(defaultfreezedate),freezedateobj=new Date(freezedateelement.value);if(freezedateobj{event.preventDefault();const requiredStrings=[{key:"confirmcontextunlock",component:"admin",param:{contextname:contextname}}];(0,_str.getStrings)(requiredStrings).then((_ref=>{let[unlockBody]=_ref;return _notification.default.confirm("Enable editing",unlockBody,"Confirm",null,(()=>{_ajax.default.call([{methodname:"block_lifecycle_unfreeze_course",args:{courseid:courseid}}])[0].done((function(response){response.success?window.location.reload():(_notification.default.addNotification({message:response.message||"An error occurred while enabling editing.",type:"error"}),window.scrollTo({top:0,behavior:"instant"}))})).fail((function(err){window.console.log(err)}))}))})).catch((error=>(window.console.log(error),error)))}))}(courseid)}})); +define("block_lifecycle/lifecycle",["exports","core/ajax","core/notification"],(function(_exports,_ajax,_notification){function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=void 0,_ajax=_interopRequireDefault(_ajax),_notification=_interopRequireDefault(_notification);let defaultfreezedate="",originalfreezedatevalue="";function togglefreezebutton(checked){let readonlydateinput=document.getElementById("delayfreezedate");checked?(readonlydateinput.value="",readonlydateinput.disabled=!0):readonlydateinput.disabled=!1}function togglesettings(){let content=document.getElementById("automatic-read-only-settings");document.getElementById("override-freeze-date-button").classList.toggle("active"),content.style.maxHeight?content.style.maxHeight=null:content.style.maxHeight=content.scrollHeight+"px"}function initscheduledfreezedateblock(courseid){let scheduledfreezedatecontainer=document.getElementById("scheduled-freeze-date-container");originalfreezedatevalue=document.getElementById("delayfreezedate").value,scheduledfreezedatecontainer.style.display="none",_ajax.default.call([{methodname:"block_lifecycle_get_scheduled_freeze_date",args:{courseid:courseid}}])[0].done((function(response){document.getElementById("togglefreezebutton").checked?togglefreezebutton(!0):(document.getElementById("scheduled-freeze-date").innerHTML=response.scheduledfreezedate,scheduledfreezedatecontainer.style.display="block"),"true"===response.success&&(defaultfreezedate=response.defaultfreezedate)})).fail((function(err){window.console.log(err)}))}_exports.init=courseid=>{document.getElementById("lifecycle-settings-container")&&(initscheduledfreezedateblock(courseid),document.getElementById("togglefreezebutton").addEventListener("click",(event=>{togglefreezebutton(event.target.checked)})),document.getElementById("update_auto_freezing_preferences_button").addEventListener("click",(()=>{!function(courseid){let preferences={togglefreeze:document.getElementById("togglefreezebutton").checked,delayfreezedate:document.getElementById("delayfreezedate").value};(function(){let freezedateelement=document.getElementById("delayfreezedate");if(freezedateelement.value.length>0){if(""===defaultfreezedate)return _notification.default.alert("Error","Could not get the automatically suggested date, please try again later.","OK"),freezedateelement.value=originalfreezedatevalue,!1;{let defaultfreezedateobj=new Date(defaultfreezedate),freezedateobj=new Date(freezedateelement.value);if(freezedateobj {\n // The course is read-only. Initialize the unfreeze button and return.\n if (!document.getElementById('lifecycle-settings-container')) {\n // Initialize the unfreeze button.\n initUnfreezeButton(courseid);\n return;\n }\n\n initscheduledfreezedateblock(courseid);\n // On click listener for \"Disable Automatic Read-Only\" toggle.\n document.getElementById('togglefreezebutton').addEventListener('click', (event) => {\n togglefreezebutton(event.target.checked);\n });\n\n // Save button.\n document.getElementById('update_auto_freezing_preferences_button').addEventListener('click', () => {\n updatepreferences(courseid);\n });\n\n // Drop down settings button.\n document.getElementById('override-freeze-date-button').addEventListener(\"click\", function(e) {\n e.preventDefault();\n togglesettings();\n });\n};\n\n/**\n * Disable read-only date input when \"Disable Automatic Read-Only button\" is on.\n *\n * @param {boolean} checked\n */\nfunction togglefreezebutton(checked) {\n let readonlydateinput = document.getElementById('delayfreezedate');\n if (checked) {\n readonlydateinput.value = '';\n readonlydateinput.disabled = true;\n } else {\n readonlydateinput.disabled = false;\n }\n}\n\n/**\n * Toggle the automatic read only settings container.\n */\nfunction togglesettings() {\n let content = document.getElementById('automatic-read-only-settings');\n document.getElementById('override-freeze-date-button').classList.toggle('active');\n if (content.style.maxHeight) {\n content.style.maxHeight = null;\n } else {\n content.style.maxHeight = content.scrollHeight + \"px\";\n }\n}\n\n/**\n * Validate the preferences.\n * @return {boolean}\n */\nfunction validate() {\n let freezedateelement = document.getElementById('delayfreezedate');\n\n if (freezedateelement.value.length > 0) {\n // The default suggested date is not initialized, so cannot continue the checking.\n if (defaultfreezedate === '') {\n notification.alert(\n 'Error',\n 'Could not get the automatically suggested date, please try again later.',\n 'OK'\n );\n freezedateelement.value = originalfreezedatevalue;\n return false;\n } else {\n let defaultfreezedateobj = new Date(defaultfreezedate);\n let freezedateobj = new Date(freezedateelement.value);\n\n // The override freeze date should not be saved when it is earlier than the default suggested date.\n if (freezedateobj < defaultfreezedateobj || freezedateobj < new Date()) {\n notification.alert(\n 'Invalid Selection',\n 'The date for a Read-Only override must be post the automatically suggested date (' +\n defaultfreezedateobj.toLocaleDateString() +\n '), earlier dates may not be used.',\n 'OK'\n );\n freezedateelement.value = originalfreezedatevalue;\n return false;\n }\n }\n }\n\n return true;\n}\n\n/**\n * Initialize the scheduled freeze date container.\n *\n * @param {int} courseid\n */\nfunction initscheduledfreezedateblock(courseid) {\n let scheduledfreezedatecontainer = document.getElementById('scheduled-freeze-date-container');\n originalfreezedatevalue = document.getElementById('delayfreezedate').value;\n\n // Hide scheduled read-only date text at the beginning.\n scheduledfreezedatecontainer.style.display = 'none';\n\n // Get scheduled read-only dates.\n Ajax.call([{\n methodname: 'block_lifecycle_get_scheduled_freeze_date',\n args: {\n 'courseid': courseid\n },\n }])[0].done(function(response) {\n // Show scheduled date.\n if (!document.getElementById('togglefreezebutton').checked) {\n document.getElementById('scheduled-freeze-date').innerHTML = response.scheduledfreezedate;\n scheduledfreezedatecontainer.style.display = 'block';\n } else {\n // Disable read-only date input depends on freeze button status.\n togglefreezebutton(true);\n }\n // Set the default suggested date.\n if (response.success === 'true') {\n defaultfreezedate = response.defaultfreezedate;\n }\n }).fail(function(err) {\n window.console.log(err);\n });\n}\n\n/**\n * Update the auto context freezing preferences.\n * @param {int} courseid\n */\nfunction updatepreferences(courseid) {\n let preferences = {\n togglefreeze: document.getElementById('togglefreezebutton').checked,\n delayfreezedate: document.getElementById('delayfreezedate').value\n };\n\n if (validate()) {\n Ajax.call([{\n methodname: 'block_lifecycle_update_auto_freezing_preferences',\n args: {\n 'courseid': courseid,\n 'preferences': JSON.stringify(preferences)\n },\n }])[0].done(function(response) {\n notification.addNotification({\n message: response.message,\n type: response.success ? 'success' : 'error'\n });\n initscheduledfreezedateblock(courseid);\n togglesettings();\n }).fail(function(err) {\n window.console.log(err);\n });\n }\n}\n\n/**\n * Initialize the unfreeze button.\n *\n * @param {int} courseid\n */\nfunction initUnfreezeButton(courseid) {\n // Get the unfreeze button.\n let unfreezeButton = document.getElementById('unfreeze-button');\n\n // The course is not frozen. Do nothing.\n if (!unfreezeButton) {\n return;\n }\n\n let contextname = unfreezeButton.getAttribute('data-contextname');\n\n unfreezeButton.addEventListener('click', event => {\n event.preventDefault();\n\n const requiredStrings = [\n {key: 'confirmcontextunlock', component: 'admin', param: {'contextname': contextname}},\n ];\n\n getStrings(requiredStrings).then(([unlockBody]) => {\n return notification.confirm('Enable editing', unlockBody, 'Confirm', null, () => {\n Ajax.call([{\n methodname: 'block_lifecycle_unfreeze_course',\n args: {\n 'courseid': courseid\n },\n }])[0].done(function(response) {\n if (response.success) {\n window.location.reload();\n } else {\n notification.addNotification({\n message: response.message || 'An error occurred while enabling editing.',\n type: 'error'\n });\n // Scroll to the top of the page to show the error message.\n window.scrollTo({top: 0, behavior: \"instant\"});\n }\n }).fail(function(err) {\n window.console.log(err);\n });\n });\n }).catch(\n (error) => {\n window.console.log(error);\n return error;\n }\n );\n });\n}\n"],"names":["defaultfreezedate","originalfreezedatevalue","togglefreezebutton","checked","readonlydateinput","document","getElementById","value","disabled","togglesettings","content","classList","toggle","style","maxHeight","scrollHeight","initscheduledfreezedateblock","courseid","scheduledfreezedatecontainer","display","call","methodname","args","done","response","innerHTML","scheduledfreezedate","success","fail","err","window","console","log","addEventListener","event","target","preferences","togglefreeze","delayfreezedate","freezedateelement","length","alert","defaultfreezedateobj","Date","freezedateobj","toLocaleDateString","validate","JSON","stringify","addNotification","message","type","updatepreferences","e","preventDefault","unfreezeButton","contextname","getAttribute","requiredStrings","key","component","param","then","_ref","unlockBody","notification","confirm","location","reload","scrollTo","top","behavior","catch","error","initUnfreezeButton"],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"lifecycle.min.js","sources":["../src/lifecycle.js"],"sourcesContent":["import Ajax from 'core/ajax';\nimport notification from 'core/notification';\n\n// Default auto suggested read-only date.\nlet defaultfreezedate = '';\n// The datepicker original value before user make any changes.\nlet originalfreezedatevalue = '';\n\nexport const init = (courseid) => {\n // The course is read-only. Do nothing.\n if (!document.getElementById('lifecycle-settings-container')) {\n return;\n }\n\n initscheduledfreezedateblock(courseid);\n // On click listener for \"Disable Automatic Read-Only\" toggle.\n document.getElementById('togglefreezebutton').addEventListener('click', (event) => {\n togglefreezebutton(event.target.checked);\n });\n\n // Save button.\n document.getElementById('update_auto_freezing_preferences_button').addEventListener('click', () => {\n updatepreferences(courseid);\n });\n\n // Drop down settings button.\n document.getElementById('override-freeze-date-button').addEventListener(\"click\", function(e) {\n e.preventDefault();\n togglesettings();\n });\n};\n\n/**\n * Disable read-only date input when \"Disable Automatic Read-Only button\" is on.\n *\n * @param {boolean} checked\n */\nfunction togglefreezebutton(checked) {\n let readonlydateinput = document.getElementById('delayfreezedate');\n if (checked) {\n readonlydateinput.value = '';\n readonlydateinput.disabled = true;\n } else {\n readonlydateinput.disabled = false;\n }\n}\n\n/**\n * Toggle the automatic read only settings container.\n */\nfunction togglesettings() {\n let content = document.getElementById('automatic-read-only-settings');\n document.getElementById('override-freeze-date-button').classList.toggle('active');\n if (content.style.maxHeight) {\n content.style.maxHeight = null;\n } else {\n content.style.maxHeight = content.scrollHeight + \"px\";\n }\n}\n\n/**\n * Validate the preferences.\n * @return {boolean}\n */\nfunction validate() {\n let freezedateelement = document.getElementById('delayfreezedate');\n\n if (freezedateelement.value.length > 0) {\n // The default suggested date is not initialized, so cannot continue the checking.\n if (defaultfreezedate === '') {\n notification.alert(\n 'Error',\n 'Could not get the automatically suggested date, please try again later.',\n 'OK'\n );\n freezedateelement.value = originalfreezedatevalue;\n return false;\n } else {\n let defaultfreezedateobj = new Date(defaultfreezedate);\n let freezedateobj = new Date(freezedateelement.value);\n\n // The override freeze date should not be saved when it is earlier than the default suggested date.\n if (freezedateobj < defaultfreezedateobj || freezedateobj < new Date()) {\n notification.alert(\n 'Invalid Selection',\n 'The date for a Read-Only override must be post the automatically suggested date (' +\n defaultfreezedateobj.toLocaleDateString() +\n '), earlier dates may not be used.',\n 'OK'\n );\n freezedateelement.value = originalfreezedatevalue;\n return false;\n }\n }\n }\n\n return true;\n}\n\n/**\n * Initialize the scheduled freeze date container.\n *\n * @param {int} courseid\n */\nfunction initscheduledfreezedateblock(courseid) {\n let scheduledfreezedatecontainer = document.getElementById('scheduled-freeze-date-container');\n originalfreezedatevalue = document.getElementById('delayfreezedate').value;\n\n // Hide scheduled read-only date text at the beginning.\n scheduledfreezedatecontainer.style.display = 'none';\n\n // Get scheduled read-only dates.\n Ajax.call([{\n methodname: 'block_lifecycle_get_scheduled_freeze_date',\n args: {\n 'courseid': courseid\n },\n }])[0].done(function(response) {\n // Show scheduled date.\n if (!document.getElementById('togglefreezebutton').checked) {\n document.getElementById('scheduled-freeze-date').innerHTML = response.scheduledfreezedate;\n scheduledfreezedatecontainer.style.display = 'block';\n } else {\n // Disable read-only date input depends on freeze button status.\n togglefreezebutton(true);\n }\n // Set the default suggested date.\n if (response.success === 'true') {\n defaultfreezedate = response.defaultfreezedate;\n }\n }).fail(function(err) {\n window.console.log(err);\n });\n}\n\n/**\n * Update the auto context freezing preferences.\n * @param {int} courseid\n */\nfunction updatepreferences(courseid) {\n let preferences = {\n togglefreeze: document.getElementById('togglefreezebutton').checked,\n delayfreezedate: document.getElementById('delayfreezedate').value\n };\n\n if (validate()) {\n Ajax.call([{\n methodname: 'block_lifecycle_update_auto_freezing_preferences',\n args: {\n 'courseid': courseid,\n 'preferences': JSON.stringify(preferences)\n },\n }])[0].done(function(response) {\n notification.addNotification({\n message: response.message,\n type: response.success ? 'success' : 'error'\n });\n initscheduledfreezedateblock(courseid);\n togglesettings();\n }).fail(function(err) {\n window.console.log(err);\n });\n }\n}\n"],"names":["defaultfreezedate","originalfreezedatevalue","togglefreezebutton","checked","readonlydateinput","document","getElementById","value","disabled","togglesettings","content","classList","toggle","style","maxHeight","scrollHeight","initscheduledfreezedateblock","courseid","scheduledfreezedatecontainer","display","call","methodname","args","done","response","innerHTML","scheduledfreezedate","success","fail","err","window","console","log","addEventListener","event","target","preferences","togglefreeze","delayfreezedate","freezedateelement","length","alert","defaultfreezedateobj","Date","freezedateobj","toLocaleDateString","validate","JSON","stringify","addNotification","message","type","updatepreferences","e","preventDefault"],"mappings":"kXAIIA,kBAAoB,GAEpBC,wBAA0B,YA+BrBC,mBAAmBC,aACpBC,kBAAoBC,SAASC,eAAe,mBAC5CH,SACAC,kBAAkBG,MAAQ,GAC1BH,kBAAkBI,UAAW,GAE7BJ,kBAAkBI,UAAW,WAO5BC,qBACDC,QAAUL,SAASC,eAAe,gCACtCD,SAASC,eAAe,+BAA+BK,UAAUC,OAAO,UACpEF,QAAQG,MAAMC,UACdJ,QAAQG,MAAMC,UAAY,KAE1BJ,QAAQG,MAAMC,UAAYJ,QAAQK,aAAe,cAgDhDC,6BAA6BC,cAC9BC,6BAA+Bb,SAASC,eAAe,mCAC3DL,wBAA0BI,SAASC,eAAe,mBAAmBC,MAGrEW,6BAA6BL,MAAMM,QAAU,qBAGxCC,KAAK,CAAC,CACPC,WAAY,4CACZC,KAAM,UACUL,aAEhB,GAAGM,MAAK,SAASC,UAEZnB,SAASC,eAAe,sBAAsBH,QAK/CD,oBAAmB,IAJnBG,SAASC,eAAe,yBAAyBmB,UAAYD,SAASE,oBACtER,6BAA6BL,MAAMM,QAAU,SAMxB,SAArBK,SAASG,UACT3B,kBAAoBwB,SAASxB,sBAElC4B,MAAK,SAASC,KACbC,OAAOC,QAAQC,IAAIH,sBA3HNZ,WAEZZ,SAASC,eAAe,kCAI7BU,6BAA6BC,UAE7BZ,SAASC,eAAe,sBAAsB2B,iBAAiB,SAAUC,QACrEhC,mBAAmBgC,MAAMC,OAAOhC,YAIpCE,SAASC,eAAe,2CAA2C2B,iBAAiB,SAAS,eAsHtEhB,cACnBmB,YAAc,CACdC,aAAchC,SAASC,eAAe,sBAAsBH,QAC5DmC,gBAAiBjC,SAASC,eAAe,mBAAmBC,uBA7E5DgC,kBAAoBlC,SAASC,eAAe,sBAE5CiC,kBAAkBhC,MAAMiC,OAAS,EAAG,IAEV,KAAtBxC,+CACiByC,MACT,QACA,0EACA,MAERF,kBAAkBhC,MAAQN,yBACnB,EACJ,KACCyC,qBAAuB,IAAIC,KAAK3C,mBAChC4C,cAAgB,IAAID,KAAKJ,kBAAkBhC,UAG3CqC,cAAgBF,sBAAwBE,cAAgB,IAAID,kCAC/CF,MACT,oBACA,oFACAC,qBAAqBG,qBACrB,oCACA,MAEJN,kBAAkBhC,MAAQN,yBACnB,UAKZ,GAiDH6C,kBACK1B,KAAK,CAAC,CACPC,WAAY,mDACZC,KAAM,UACUL,qBACG8B,KAAKC,UAAUZ,iBAElC,GAAGb,MAAK,SAASC,gCACJyB,gBAAgB,CACzBC,QAAS1B,SAAS0B,QAClBC,KAAM3B,SAASG,QAAU,UAAY,UAEzCX,6BAA6BC,UAC7BR,oBACDmB,MAAK,SAASC,KACbC,OAAOC,QAAQC,IAAIH,QA1IvBuB,CAAkBnC,aAItBZ,SAASC,eAAe,+BAA+B2B,iBAAiB,SAAS,SAASoB,GACtFA,EAAEC,iBACF7C"} \ No newline at end of file diff --git a/amd/src/lifecycle.js b/amd/src/lifecycle.js index 62c2f98..caff124 100644 --- a/amd/src/lifecycle.js +++ b/amd/src/lifecycle.js @@ -1,6 +1,5 @@ import Ajax from 'core/ajax'; import notification from 'core/notification'; -import {getStrings} from 'core/str'; // Default auto suggested read-only date. let defaultfreezedate = ''; @@ -8,10 +7,8 @@ let defaultfreezedate = ''; let originalfreezedatevalue = ''; export const init = (courseid) => { - // The course is read-only. Initialize the unfreeze button and return. + // The course is read-only. Do nothing. if (!document.getElementById('lifecycle-settings-container')) { - // Initialize the unfreeze button. - initUnfreezeButton(courseid); return; } @@ -165,57 +162,3 @@ function updatepreferences(courseid) { }); } } - -/** - * Initialize the unfreeze button. - * - * @param {int} courseid - */ -function initUnfreezeButton(courseid) { - // Get the unfreeze button. - let unfreezeButton = document.getElementById('unfreeze-button'); - - // The course is not frozen. Do nothing. - if (!unfreezeButton) { - return; - } - - let contextname = unfreezeButton.getAttribute('data-contextname'); - - unfreezeButton.addEventListener('click', event => { - event.preventDefault(); - - const requiredStrings = [ - {key: 'confirmcontextunlock', component: 'admin', param: {'contextname': contextname}}, - ]; - - getStrings(requiredStrings).then(([unlockBody]) => { - return notification.confirm('Enable editing', unlockBody, 'Confirm', null, () => { - Ajax.call([{ - methodname: 'block_lifecycle_unfreeze_course', - args: { - 'courseid': courseid - }, - }])[0].done(function(response) { - if (response.success) { - window.location.reload(); - } else { - notification.addNotification({ - message: response.message || 'An error occurred while enabling editing.', - type: 'error' - }); - // Scroll to the top of the page to show the error message. - window.scrollTo({top: 0, behavior: "instant"}); - } - }).fail(function(err) { - window.console.log(err); - }); - }); - }).catch( - (error) => { - window.console.log(error); - return error; - } - ); - }); -} diff --git a/classes/external/unfreeze_course.php b/classes/external/unfreeze_course.php deleted file mode 100644 index b519fca..0000000 --- a/classes/external/unfreeze_course.php +++ /dev/null @@ -1,86 +0,0 @@ -. - -namespace block_lifecycle\external; - -use block_lifecycle\manager; -use core_external\external_api; -use core_external\external_function_parameters; -use core_external\external_single_structure; -use core_external\external_value; - -/** - * External API for unfreeze a course - * - * @package block_lifecycle - * @copyright 2024 onwards University College London {@link https://www.ucl.ac.uk/} - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - * @author Alex Yeung - */ -class unfreeze_course extends external_api { - /** - * Returns description of method parameters. - * - * @return external_function_parameters - */ - public static function execute_parameters() { - return new external_function_parameters([ - 'courseid' => new external_value(PARAM_INT, 'Coruse ID', VALUE_REQUIRED), - ]); - } - - /** - * Returns description of method result value. - * - * @return \external_single_structure - */ - public static function execute_returns() { - return new external_single_structure([ - 'success' => new external_value(PARAM_BOOL, 'Result of request', VALUE_REQUIRED), - 'message' => new external_value(PARAM_TEXT, 'Error message', VALUE_OPTIONAL), - ]); - } - - /** - * Unfreeze a course - * - * @param int $courseid - * @return array - */ - public static function execute(int $courseid) { - try { - $params = self::validate_parameters( - self::execute_parameters(), - [ - 'courseid' => $courseid, - ] - ); - - // Unfreeze the course. - manager::unfreeze_course($params['courseid']); - - return [ - 'success' => true, - 'message' => 'Enabled course editing successfully.', - ]; - } catch (\Exception $e) { - return [ - 'success' => false, - 'message' => $e->getMessage(), - ]; - } - } -} diff --git a/classes/manager.php b/classes/manager.php index 9274da7..9474b8e 100644 --- a/classes/manager.php +++ b/classes/manager.php @@ -360,7 +360,10 @@ public static function unfreeze_course(int $courseid): void { throw new \moodle_exception('error:unfreeze_course', 'block_lifecycle'); } $context = context_course::instance($courseid); - $context->set_locked(false); + if ($context->is_locked()) { + // Unlock the course context. + $context->set_locked(false); + } } /** diff --git a/db/services.php b/db/services.php index 172e741..033caa3 100644 --- a/db/services.php +++ b/db/services.php @@ -34,7 +34,6 @@ 'description' => 'Update auto context freezing preferences', 'ajax' => true, 'type' => 'write', - 'readonlysession' => true, 'loginrequired' => true, ], 'block_lifecycle_get_scheduled_freeze_date' => [ @@ -44,15 +43,6 @@ 'description' => 'Get scheduled freeze date', 'ajax' => true, 'type' => 'read', - 'readonlysession' => true, - 'loginrequired' => true, - ], - 'block_lifecycle_unfreeze_course' => [ - 'classname' => 'block_lifecycle\external\unfreeze_course', - 'description' => 'Unfreeze a course', - 'ajax' => true, - 'type' => 'write', - 'readonlysession' => true, 'loginrequired' => true, ], ]; diff --git a/renderer.php b/renderer.php index 0748482..ec86c6f 100644 --- a/renderer.php +++ b/renderer.php @@ -156,14 +156,18 @@ public function fetch_course_read_only_notification(): string { /** * Return the html for the unfreeze button. * - * @param course $context + * @param context_course $context * @return string - * @throws coding_exception + * @throws coding_exception|moodle_exception */ - public function fetch_unfreeze_button_html(core\context\course $context): string { - return - '' . - '' . get_string('label:unfreezebutton', 'block_lifecycle') . - ''; + public function fetch_unfreeze_button_html(context_course $context): string { + $url = new moodle_url('/blocks/lifecycle/unfreeze.php', ['id' => $context->instanceid]); + return $this->output->render_from_template( + 'block_lifecycle/unfreeze_button_form', + [ + 'url' => $url->out(false), + 'coursename' => $context->get_context_name(), + ] + ); } } diff --git a/templates/unfreeze_button.mustache b/templates/unfreeze_button.mustache new file mode 100644 index 0000000..5e26add --- /dev/null +++ b/templates/unfreeze_button.mustache @@ -0,0 +1,47 @@ +{{! + This file is part the Local Analytics plugin for Moodle + Moodle is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Moodle is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Moodle. If not, see . +}} +{{! + @template block_lifecycle/unfreeze_button + + Template for displaying the unfreeze button to unfreeze a frozen course. + + Classes required for JS: + * none + + Data attributes required for JS: + * none + + Context variables required for this template: + * url - string The URL to the unfreeze page. + * coursefullname - string The full name of the course. + + Example context (json): + { + "url": "http://test.m44.local/blocks/lifecycle/unfreeze.php?id=5", + "coursefullname": 'Test Course 1' + } +}} + diff --git a/unfreeze.php b/unfreeze.php new file mode 100644 index 0000000..c2bd907 --- /dev/null +++ b/unfreeze.php @@ -0,0 +1,60 @@ +. + +/** + * unfreeze page for block_lifecycle to unfreeze a frozen course. + * + * @package block_lifecycle + * @copyright 2024 onwards University College London {@link https://www.ucl.ac.uk/} + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @author Alex Yeung + */ + +namespace block_lifecycle; + +use context_course; +use core\output\notification; +use moodle_exception; +use moodle_url; + +require_once('../../config.php'); + +// Course ID. +$courseid = required_param('id', PARAM_INT); + +// Get course context. +$context = context_course::instance($courseid); + +// Get course instance. +if (!$course = get_course($courseid)) { + throw new moodle_exception('course not found.', 'block_lifecycle'); +} + +// Make sure user is authenticated. +require_login($course); + +// Check user's capability. +require_capability('block/lifecycle:unfreezecourse', $context); + +// Unfreeze the course. +try { + manager::unfreeze_course($courseid); + // Unfreeze done. Redirect back to the course page. + redirect(new moodle_url('/course/view.php', ['id' => $courseid])); +} catch (moodle_exception $e) { + // Unfreeze failed. Redirect back to the course page with error message. + redirect(new moodle_url('/course/view.php', ['id' => $courseid]), $e->getMessage(), null, notification::NOTIFY_ERROR); +} diff --git a/version.php b/version.php index 0081d74..04a9726 100644 --- a/version.php +++ b/version.php @@ -25,7 +25,7 @@ defined('MOODLE_INTERNAL') || die(); -$plugin->version = 2022120803; +$plugin->version = 2022120801; $plugin->release = '0.1'; $plugin->maturity = MATURITY_ALPHA; $plugin->requires = 2020061512;