From bb93bcdb2d0f0b2d1ea48e37952e65365282addf Mon Sep 17 00:00:00 2001 From: Amaia Anabitarte Date: Tue, 30 Apr 2024 17:34:28 +0200 Subject: [PATCH] MDL-81601 core_course: Fix course index completion status --- .../tests/behat/enable_completion_on_pass_grade.feature | 2 ++ course/format/amd/build/local/courseeditor/exporter.min.js | 2 +- .../format/amd/build/local/courseeditor/exporter.min.js.map | 2 +- course/format/amd/src/local/courseeditor/exporter.js | 5 ++++- course/format/classes/output/local/state/cm.php | 5 +++-- course/format/tests/behat/courseindex_completion.feature | 4 ++-- 6 files changed, 13 insertions(+), 7 deletions(-) diff --git a/completion/tests/behat/enable_completion_on_pass_grade.feature b/completion/tests/behat/enable_completion_on_pass_grade.feature index cb8b1b182ae..65dbced9ce1 100644 --- a/completion/tests/behat/enable_completion_on_pass_grade.feature +++ b/completion/tests/behat/enable_completion_on_pass_grade.feature @@ -39,6 +39,8 @@ Feature: Students will be marked as completed if they have achieved a passing gr When I am on the "Course 1" course page logged in as student1 Then the "Receive a grade" completion condition of "Test assignment name" is displayed as "done" And the "Receive a passing grade" completion condition of "Test assignment name" is displayed as "failed" + And "Failed" "icon" should exist in the "courseindex-content" "region" And I am on the "Course 1" course page logged in as student2 And the "Receive a grade" completion condition of "Test assignment name" is displayed as "done" And the "Receive a passing grade" completion condition of "Test assignment name" is displayed as "done" + And "Done" "icon" should exist in the "courseindex-content" "region" diff --git a/course/format/amd/build/local/courseeditor/exporter.min.js b/course/format/amd/build/local/courseeditor/exporter.min.js index 3ed55d17121..7ccd06eede5 100644 --- a/course/format/amd/build/local/courseeditor/exporter.min.js +++ b/course/format/amd/build/local/courseeditor/exporter.min.js @@ -8,6 +8,6 @@ define("core_courseformat/local/courseeditor/exporter",["exports"],(function(_ex * @copyright 2021 Ferran Recio * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class{constructor(reactive){this.reactive=reactive,this.COMPLETIONS=["incomplete","complete","complete","fail"]}course(state){var _state$course$highlig;const data={sections:[],editmode:this.reactive.isEditing,highlighted:null!==(_state$course$highlig=state.course.highlighted)&&void 0!==_state$course$highlig?_state$course$highlig:""};return this.listedSectionIds(state).forEach((sectionid=>{var _state$section$get;const sectioninfo=null!==(_state$section$get=state.section.get(sectionid))&&void 0!==_state$section$get?_state$section$get:{},section=this.section(state,sectioninfo);data.sections.push(section)})),data.hassections=0!=data.sections.length,data}listedSectionIds(state){var _state$course$section;return(null!==(_state$course$section=state.course.sectionlist)&&void 0!==_state$course$section?_state$course$section:[]).filter((sectionid=>{var _state$section$get2;return null===(null!==(_state$section$get2=state.section.get(sectionid))&&void 0!==_state$section$get2?_state$section$get2:{}).component}))}section(state,sectioninfo){var _state$course$highlig2,_sectioninfo$cmlist;const section={...sectioninfo,highlighted:null!==(_state$course$highlig2=state.course.highlighted)&&void 0!==_state$course$highlig2?_state$course$highlig2:"",cms:[]};return(null!==(_sectioninfo$cmlist=sectioninfo.cmlist)&&void 0!==_sectioninfo$cmlist?_sectioninfo$cmlist:[]).forEach((cmid=>{const cminfo=state.cm.get(cmid),cm=this.cm(state,cminfo);section.cms.push(cm)})),section.hascms=0!=section.cms.length,section}cm(state,cminfo){return{...cminfo,isactive:!1}}cmDraggableData(state,cmid){const cminfo=state.cm.get(cmid);if(!cminfo)return null;let nextcmid;const section=state.section.get(cminfo.sectionid),currentindex=null==section?void 0:section.cmlist.indexOf(cminfo.id);return void 0!==currentindex&&(nextcmid=null==section?void 0:section.cmlist[currentindex+1]),{type:"cm",id:cminfo.id,name:cminfo.name,sectionid:cminfo.sectionid,delegatesection:cminfo.delegatesection,nextcmid:nextcmid}}sectionDraggableData(state,sectionid){const sectioninfo=state.section.get(sectionid);return sectioninfo?{type:"section",id:sectioninfo.id,name:sectioninfo.name,number:sectioninfo.number}:null}fileDraggableData(state,dataTransfer){var _dataTransfer$files;const files=[];return(null===(_dataTransfer$files=dataTransfer.files)||void 0===_dataTransfer$files?void 0:_dataTransfer$files.length)>0&&dataTransfer.files.forEach((file=>{files.push(file)})),{type:"files",files:files}}cmCompletion(state,cminfo){const data={statename:"",state:"NaN"};if(void 0!==cminfo.completionstate){var _this$COMPLETIONS$cmi;data.state=cminfo.completionstate,data.hasstate=!0;const statename=null!==(_this$COMPLETIONS$cmi=this.COMPLETIONS[cminfo.completionstate])&&void 0!==_this$COMPLETIONS$cmi?_this$COMPLETIONS$cmi:"NaN";data["is".concat(statename)]=!0}return data}allItemsArray(state){var _state$course$section2;const items=[];return(null!==(_state$course$section2=state.course.sectionlist)&&void 0!==_state$course$section2?_state$course$section2:[]).forEach((sectionid=>{var _sectioninfo$cmlist2;const sectioninfo=state.section.get(sectionid);items.push({type:"section",id:sectioninfo.id,url:sectioninfo.sectionurl});(null!==(_sectioninfo$cmlist2=sectioninfo.cmlist)&&void 0!==_sectioninfo$cmlist2?_sectioninfo$cmlist2:[]).forEach((cmid=>{const cminfo=state.cm.get(cmid);items.push({type:"cm",id:cminfo.id,url:cminfo.url})}))})),items}canUseStealth(state,cmIds){return cmIds.some((cmId=>{var _cminfo$allowstealth;const cminfo=state.cm.get(cmId);return null!==(_cminfo$allowstealth=null==cminfo?void 0:cminfo.allowstealth)&&void 0!==_cminfo$allowstealth&&_cminfo$allowstealth}))}},_exports.default})); +class{constructor(reactive){this.reactive=reactive,this.COMPLETIONS=["incomplete","complete","complete","fail"]}course(state){var _state$course$highlig;const data={sections:[],editmode:this.reactive.isEditing,highlighted:null!==(_state$course$highlig=state.course.highlighted)&&void 0!==_state$course$highlig?_state$course$highlig:""};return this.listedSectionIds(state).forEach((sectionid=>{var _state$section$get;const sectioninfo=null!==(_state$section$get=state.section.get(sectionid))&&void 0!==_state$section$get?_state$section$get:{},section=this.section(state,sectioninfo);data.sections.push(section)})),data.hassections=0!=data.sections.length,data}listedSectionIds(state){var _state$course$section;return(null!==(_state$course$section=state.course.sectionlist)&&void 0!==_state$course$section?_state$course$section:[]).filter((sectionid=>{var _state$section$get2;return null===(null!==(_state$section$get2=state.section.get(sectionid))&&void 0!==_state$section$get2?_state$section$get2:{}).component}))}section(state,sectioninfo){var _state$course$highlig2,_sectioninfo$cmlist;const section={...sectioninfo,highlighted:null!==(_state$course$highlig2=state.course.highlighted)&&void 0!==_state$course$highlig2?_state$course$highlig2:"",cms:[]};return(null!==(_sectioninfo$cmlist=sectioninfo.cmlist)&&void 0!==_sectioninfo$cmlist?_sectioninfo$cmlist:[]).forEach((cmid=>{const cminfo=state.cm.get(cmid),cm=this.cm(state,cminfo);section.cms.push(cm)})),section.hascms=0!=section.cms.length,section}cm(state,cminfo){return{...cminfo,isactive:!1}}cmDraggableData(state,cmid){const cminfo=state.cm.get(cmid);if(!cminfo)return null;let nextcmid;const section=state.section.get(cminfo.sectionid),currentindex=null==section?void 0:section.cmlist.indexOf(cminfo.id);return void 0!==currentindex&&(nextcmid=null==section?void 0:section.cmlist[currentindex+1]),{type:"cm",id:cminfo.id,name:cminfo.name,sectionid:cminfo.sectionid,delegatesection:cminfo.delegatesection,nextcmid:nextcmid}}sectionDraggableData(state,sectionid){const sectioninfo=state.section.get(sectionid);return sectioninfo?{type:"section",id:sectioninfo.id,name:sectioninfo.name,number:sectioninfo.number}:null}fileDraggableData(state,dataTransfer){var _dataTransfer$files;const files=[];return(null===(_dataTransfer$files=dataTransfer.files)||void 0===_dataTransfer$files?void 0:_dataTransfer$files.length)>0&&dataTransfer.files.forEach((file=>{files.push(file)})),{type:"files",files:files}}cmCompletion(state,cminfo){const data={statename:"",state:"NaN"};if(void 0!==cminfo.completionstate){var _this$COMPLETIONS$cmi;data.state=cminfo.completionstate,data.hasstate=!0;let statename=null!==(_this$COMPLETIONS$cmi=this.COMPLETIONS[cminfo.completionstate])&&void 0!==_this$COMPLETIONS$cmi?_this$COMPLETIONS$cmi:"NaN";void 0!==cminfo.isoverallcomplete&&!0===cminfo.isoverallcomplete&&(statename="complete"),data["is".concat(statename)]=!0}return data}allItemsArray(state){var _state$course$section2;const items=[];return(null!==(_state$course$section2=state.course.sectionlist)&&void 0!==_state$course$section2?_state$course$section2:[]).forEach((sectionid=>{var _sectioninfo$cmlist2;const sectioninfo=state.section.get(sectionid);items.push({type:"section",id:sectioninfo.id,url:sectioninfo.sectionurl});(null!==(_sectioninfo$cmlist2=sectioninfo.cmlist)&&void 0!==_sectioninfo$cmlist2?_sectioninfo$cmlist2:[]).forEach((cmid=>{const cminfo=state.cm.get(cmid);items.push({type:"cm",id:cminfo.id,url:cminfo.url})}))})),items}canUseStealth(state,cmIds){return cmIds.some((cmId=>{var _cminfo$allowstealth;const cminfo=state.cm.get(cmId);return null!==(_cminfo$allowstealth=null==cminfo?void 0:cminfo.allowstealth)&&void 0!==_cminfo$allowstealth&&_cminfo$allowstealth}))}},_exports.default})); //# sourceMappingURL=exporter.min.js.map \ No newline at end of file diff --git a/course/format/amd/build/local/courseeditor/exporter.min.js.map b/course/format/amd/build/local/courseeditor/exporter.min.js.map index 56cfcc968ec..336e22c8dfe 100644 --- a/course/format/amd/build/local/courseeditor/exporter.min.js.map +++ b/course/format/amd/build/local/courseeditor/exporter.min.js.map @@ -1 +1 @@ -{"version":3,"file":"exporter.min.js","sources":["../../../src/local/courseeditor/exporter.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 * Module to export parts of the state and transform them to be used in templates\n * and as draggable data.\n *\n * @module core_courseformat/local/courseeditor/exporter\n * @class core_courseformat/local/courseeditor/exporter\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class {\n\n /**\n * Class constructor.\n *\n * @param {CourseEditor} reactive the course editor object\n */\n constructor(reactive) {\n this.reactive = reactive;\n\n // Completions states are defined in lib/completionlib.php. There are 4 different completion\n // state values, however, the course index uses the same state for complete and complete_pass.\n // This is the reason why completed appears twice in the array.\n this.COMPLETIONS = ['incomplete', 'complete', 'complete', 'fail'];\n }\n\n /**\n * Generate the course export data from the state.\n *\n * @param {Object} state the current state.\n * @returns {Object}\n */\n course(state) {\n // Collect section information from the state.\n const data = {\n sections: [],\n editmode: this.reactive.isEditing,\n highlighted: state.course.highlighted ?? '',\n };\n const sectionlist = this.listedSectionIds(state);\n sectionlist.forEach(sectionid => {\n const sectioninfo = state.section.get(sectionid) ?? {};\n const section = this.section(state, sectioninfo);\n data.sections.push(section);\n });\n data.hassections = (data.sections.length != 0);\n\n return data;\n }\n\n /**\n * Get the IDs of the sections that are listed as regular sections.\n * @param {Object} state the current state.\n * @returns {Number[]} the list of section ids that are listed.\n */\n listedSectionIds(state) {\n const fullSectionList = state.course.sectionlist ?? [];\n return fullSectionList.filter(sectionid => {\n const sectioninfo = state.section.get(sectionid) ?? {};\n // Delegated sections (controlled by a component) are not listed in course.\n return sectioninfo.component === null;\n });\n }\n\n /**\n * Generate a section export data from the state.\n *\n * @param {Object} state the current state.\n * @param {Object} sectioninfo the section state data.\n * @returns {Object}\n */\n section(state, sectioninfo) {\n const section = {\n ...sectioninfo,\n highlighted: state.course.highlighted ?? '',\n cms: [],\n };\n const cmlist = sectioninfo.cmlist ?? [];\n cmlist.forEach(cmid => {\n const cminfo = state.cm.get(cmid);\n const cm = this.cm(state, cminfo);\n section.cms.push(cm);\n });\n section.hascms = (section.cms.length != 0);\n\n return section;\n }\n\n /**\n * Generate a cm export data from the state.\n *\n * @param {Object} state the current state.\n * @param {Object} cminfo the course module state data.\n * @returns {Object}\n */\n cm(state, cminfo) {\n const cm = {\n ...cminfo,\n isactive: false,\n };\n return cm;\n }\n\n /**\n * Generate a dragable cm data structure.\n *\n * This method is used by any draggable course module element to generate drop data\n * for its reactive/dragdrop instance.\n *\n * @param {*} state the state object\n * @param {*} cmid the cours emodule id\n * @returns {Object|null}\n */\n cmDraggableData(state, cmid) {\n const cminfo = state.cm.get(cmid);\n if (!cminfo) {\n return null;\n }\n\n // Drop an activity over the next activity is the same as doing anything.\n let nextcmid;\n const section = state.section.get(cminfo.sectionid);\n const currentindex = section?.cmlist.indexOf(cminfo.id);\n if (currentindex !== undefined) {\n nextcmid = section?.cmlist[currentindex + 1];\n }\n\n return {\n type: 'cm',\n id: cminfo.id,\n name: cminfo.name,\n sectionid: cminfo.sectionid,\n delegatesection: cminfo.delegatesection,\n nextcmid,\n };\n }\n\n /**\n * Generate a dragable cm data structure.\n *\n * This method is used by any draggable section element to generate drop data\n * for its reactive/dragdrop instance.\n *\n * @param {*} state the state object\n * @param {*} sectionid the cours section id\n * @returns {Object|null}\n */\n sectionDraggableData(state, sectionid) {\n const sectioninfo = state.section.get(sectionid);\n if (!sectioninfo) {\n return null;\n }\n return {\n type: 'section',\n id: sectioninfo.id,\n name: sectioninfo.name,\n number: sectioninfo.number,\n };\n }\n\n /**\n * Generate a file draggable structure.\n *\n * This method is used when files are dragged on the browser.\n *\n * @param {*} state the state object\n * @param {*} dataTransfer the current data tranfer data\n * @returns {Object|null}\n */\n fileDraggableData(state, dataTransfer) {\n const files = [];\n // Browsers do not provide the file list until the drop event.\n if (dataTransfer.files?.length > 0) {\n dataTransfer.files.forEach(file => {\n files.push(file);\n });\n }\n return {\n type: 'files',\n files,\n };\n }\n\n /**\n * Generate a completion export data from the cm element.\n *\n * @param {Object} state the current state.\n * @param {Object} cminfo the course module state data.\n * @returns {Object}\n */\n cmCompletion(state, cminfo) {\n const data = {\n statename: '',\n state: 'NaN',\n };\n if (cminfo.completionstate !== undefined) {\n data.state = cminfo.completionstate;\n data.hasstate = true;\n const statename = this.COMPLETIONS[cminfo.completionstate] ?? 'NaN';\n data[`is${statename}`] = true;\n }\n return data;\n }\n\n /**\n * Return a sorted list of all sections and cms items in the state.\n *\n * @param {Object} state the current state.\n * @returns {Array} all sections and cms items in the state.\n */\n allItemsArray(state) {\n const items = [];\n const sectionlist = state.course.sectionlist ?? [];\n // Add sections.\n sectionlist.forEach(sectionid => {\n const sectioninfo = state.section.get(sectionid);\n items.push({type: 'section', id: sectioninfo.id, url: sectioninfo.sectionurl});\n // Add cms.\n const cmlist = sectioninfo.cmlist ?? [];\n cmlist.forEach(cmid => {\n const cminfo = state.cm.get(cmid);\n items.push({type: 'cm', id: cminfo.id, url: cminfo.url});\n });\n });\n return items;\n }\n\n /**\n * Check is some activities of a list can be stealth.\n *\n * @param {Object} state the current state.\n * @param {Number[]} cmIds the module ids to check\n * @returns {Boolean} if any of the activities can be stealth.\n */\n canUseStealth(state, cmIds) {\n return cmIds.some(cmId => {\n const cminfo = state.cm.get(cmId);\n return cminfo?.allowstealth ?? false;\n });\n }\n}\n"],"names":["constructor","reactive","COMPLETIONS","course","state","data","sections","editmode","this","isEditing","highlighted","listedSectionIds","forEach","sectionid","sectioninfo","section","get","push","hassections","length","sectionlist","filter","component","cms","cmlist","cmid","cminfo","cm","hascms","isactive","cmDraggableData","nextcmid","currentindex","indexOf","id","undefined","type","name","delegatesection","sectionDraggableData","number","fileDraggableData","dataTransfer","files","file","cmCompletion","statename","completionstate","hasstate","allItemsArray","items","url","sectionurl","canUseStealth","cmIds","some","cmId","allowstealth"],"mappings":";;;;;;;;;;MA+BIA,YAAYC,eACHA,SAAWA,cAKXC,YAAc,CAAC,aAAc,WAAY,WAAY,QAS9DC,OAAOC,uCAEGC,KAAO,CACTC,SAAU,GACVC,SAAUC,KAAKP,SAASQ,UACxBC,0CAAaN,MAAMD,OAAOO,mEAAe,WAEzBF,KAAKG,iBAAiBP,OAC9BQ,SAAQC,yCACVC,uCAAcV,MAAMW,QAAQC,IAAIH,4DAAc,GAC9CE,QAAUP,KAAKO,QAAQX,MAAOU,aACpCT,KAAKC,SAASW,KAAKF,YAEvBV,KAAKa,YAAuC,GAAxBb,KAAKC,SAASa,OAE3Bd,KAQXM,iBAAiBP,sEACWA,MAAMD,OAAOiB,mEAAe,IAC7BC,QAAOR,2CAGO,oCAFbT,MAAMW,QAAQC,IAAIH,8DAAc,IAEjCS,aAW3BP,QAAQX,MAAOU,kEACLC,QAAU,IACTD,YACHJ,2CAAaN,MAAMD,OAAOO,qEAAe,GACzCa,IAAK,uCAEMT,YAAYU,0DAAU,IAC9BZ,SAAQa,aACLC,OAAStB,MAAMuB,GAAGX,IAAIS,MACtBE,GAAKnB,KAAKmB,GAAGvB,MAAOsB,QAC1BX,QAAQQ,IAAIN,KAAKU,OAErBZ,QAAQa,OAAgC,GAAtBb,QAAQQ,IAAIJ,OAEvBJ,QAUXY,GAAGvB,MAAOsB,cACK,IACJA,OACHG,UAAU,GAelBC,gBAAgB1B,MAAOqB,YACbC,OAAStB,MAAMuB,GAAGX,IAAIS,UACvBC,cACM,SAIPK,eACEhB,QAAUX,MAAMW,QAAQC,IAAIU,OAAOb,WACnCmB,aAAejB,MAAAA,eAAAA,QAASS,OAAOS,QAAQP,OAAOQ,gBAC/BC,IAAjBH,eACAD,SAAWhB,MAAAA,eAAAA,QAASS,OAAOQ,aAAe,IAGvC,CACHI,KAAM,KACNF,GAAIR,OAAOQ,GACXG,KAAMX,OAAOW,KACbxB,UAAWa,OAAOb,UAClByB,gBAAiBZ,OAAOY,gBACxBP,SAAAA,UAcRQ,qBAAqBnC,MAAOS,iBAClBC,YAAcV,MAAMW,QAAQC,IAAIH,kBACjCC,YAGE,CACHsB,KAAM,UACNF,GAAIpB,YAAYoB,GAChBG,KAAMvB,YAAYuB,KAClBG,OAAQ1B,YAAY0B,QANb,KAmBfC,kBAAkBrC,MAAOsC,4CACfC,MAAQ,sCAEVD,aAAaC,gEAAOxB,QAAS,GAC7BuB,aAAaC,MAAM/B,SAAQgC,OACvBD,MAAM1B,KAAK2B,SAGZ,CACHR,KAAM,QACNO,MAAAA,OAWRE,aAAazC,MAAOsB,cACVrB,KAAO,CACTyC,UAAW,GACX1C,MAAO,eAEoB+B,IAA3BT,OAAOqB,gBAA+B,2BACtC1C,KAAKD,MAAQsB,OAAOqB,gBACpB1C,KAAK2C,UAAW,QACVF,wCAAYtC,KAAKN,YAAYwB,OAAOqB,wEAAoB,MAC9D1C,iBAAUyC,aAAe,SAEtBzC,KASX4C,cAAc7C,wCACJ8C,MAAQ,yCACM9C,MAAMD,OAAOiB,qEAAe,IAEpCR,SAAQC,2CACVC,YAAcV,MAAMW,QAAQC,IAAIH,WACtCqC,MAAMjC,KAAK,CAACmB,KAAM,UAAWF,GAAIpB,YAAYoB,GAAIiB,IAAKrC,YAAYsC,2CAEnDtC,YAAYU,4DAAU,IAC9BZ,SAAQa,aACLC,OAAStB,MAAMuB,GAAGX,IAAIS,MAC5ByB,MAAMjC,KAAK,CAACmB,KAAM,KAAMF,GAAIR,OAAOQ,GAAIiB,IAAKzB,OAAOyB,YAGpDD,MAUXG,cAAcjD,MAAOkD,cACVA,MAAMC,MAAKC,sCACR9B,OAAStB,MAAMuB,GAAGX,IAAIwC,0CACrB9B,MAAAA,cAAAA,OAAQ+B"} \ No newline at end of file +{"version":3,"file":"exporter.min.js","sources":["../../../src/local/courseeditor/exporter.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 * Module to export parts of the state and transform them to be used in templates\n * and as draggable data.\n *\n * @module core_courseformat/local/courseeditor/exporter\n * @class core_courseformat/local/courseeditor/exporter\n * @copyright 2021 Ferran Recio \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\nexport default class {\n\n /**\n * Class constructor.\n *\n * @param {CourseEditor} reactive the course editor object\n */\n constructor(reactive) {\n this.reactive = reactive;\n\n // Completions states are defined in lib/completionlib.php. There are 4 different completion\n // state values, however, the course index uses the same state for complete and complete_pass.\n // This is the reason why completed appears twice in the array.\n this.COMPLETIONS = ['incomplete', 'complete', 'complete', 'fail'];\n }\n\n /**\n * Generate the course export data from the state.\n *\n * @param {Object} state the current state.\n * @returns {Object}\n */\n course(state) {\n // Collect section information from the state.\n const data = {\n sections: [],\n editmode: this.reactive.isEditing,\n highlighted: state.course.highlighted ?? '',\n };\n const sectionlist = this.listedSectionIds(state);\n sectionlist.forEach(sectionid => {\n const sectioninfo = state.section.get(sectionid) ?? {};\n const section = this.section(state, sectioninfo);\n data.sections.push(section);\n });\n data.hassections = (data.sections.length != 0);\n\n return data;\n }\n\n /**\n * Get the IDs of the sections that are listed as regular sections.\n * @param {Object} state the current state.\n * @returns {Number[]} the list of section ids that are listed.\n */\n listedSectionIds(state) {\n const fullSectionList = state.course.sectionlist ?? [];\n return fullSectionList.filter(sectionid => {\n const sectioninfo = state.section.get(sectionid) ?? {};\n // Delegated sections (controlled by a component) are not listed in course.\n return sectioninfo.component === null;\n });\n }\n\n /**\n * Generate a section export data from the state.\n *\n * @param {Object} state the current state.\n * @param {Object} sectioninfo the section state data.\n * @returns {Object}\n */\n section(state, sectioninfo) {\n const section = {\n ...sectioninfo,\n highlighted: state.course.highlighted ?? '',\n cms: [],\n };\n const cmlist = sectioninfo.cmlist ?? [];\n cmlist.forEach(cmid => {\n const cminfo = state.cm.get(cmid);\n const cm = this.cm(state, cminfo);\n section.cms.push(cm);\n });\n section.hascms = (section.cms.length != 0);\n\n return section;\n }\n\n /**\n * Generate a cm export data from the state.\n *\n * @param {Object} state the current state.\n * @param {Object} cminfo the course module state data.\n * @returns {Object}\n */\n cm(state, cminfo) {\n const cm = {\n ...cminfo,\n isactive: false,\n };\n return cm;\n }\n\n /**\n * Generate a dragable cm data structure.\n *\n * This method is used by any draggable course module element to generate drop data\n * for its reactive/dragdrop instance.\n *\n * @param {*} state the state object\n * @param {*} cmid the cours emodule id\n * @returns {Object|null}\n */\n cmDraggableData(state, cmid) {\n const cminfo = state.cm.get(cmid);\n if (!cminfo) {\n return null;\n }\n\n // Drop an activity over the next activity is the same as doing anything.\n let nextcmid;\n const section = state.section.get(cminfo.sectionid);\n const currentindex = section?.cmlist.indexOf(cminfo.id);\n if (currentindex !== undefined) {\n nextcmid = section?.cmlist[currentindex + 1];\n }\n\n return {\n type: 'cm',\n id: cminfo.id,\n name: cminfo.name,\n sectionid: cminfo.sectionid,\n delegatesection: cminfo.delegatesection,\n nextcmid,\n };\n }\n\n /**\n * Generate a dragable cm data structure.\n *\n * This method is used by any draggable section element to generate drop data\n * for its reactive/dragdrop instance.\n *\n * @param {*} state the state object\n * @param {*} sectionid the cours section id\n * @returns {Object|null}\n */\n sectionDraggableData(state, sectionid) {\n const sectioninfo = state.section.get(sectionid);\n if (!sectioninfo) {\n return null;\n }\n return {\n type: 'section',\n id: sectioninfo.id,\n name: sectioninfo.name,\n number: sectioninfo.number,\n };\n }\n\n /**\n * Generate a file draggable structure.\n *\n * This method is used when files are dragged on the browser.\n *\n * @param {*} state the state object\n * @param {*} dataTransfer the current data tranfer data\n * @returns {Object|null}\n */\n fileDraggableData(state, dataTransfer) {\n const files = [];\n // Browsers do not provide the file list until the drop event.\n if (dataTransfer.files?.length > 0) {\n dataTransfer.files.forEach(file => {\n files.push(file);\n });\n }\n return {\n type: 'files',\n files,\n };\n }\n\n /**\n * Generate a completion export data from the cm element.\n *\n * @param {Object} state the current state.\n * @param {Object} cminfo the course module state data.\n * @returns {Object}\n */\n cmCompletion(state, cminfo) {\n const data = {\n statename: '',\n state: 'NaN',\n };\n if (cminfo.completionstate !== undefined) {\n data.state = cminfo.completionstate;\n data.hasstate = true;\n let statename = this.COMPLETIONS[cminfo.completionstate] ?? 'NaN';\n if (cminfo.isoverallcomplete !== undefined && cminfo.isoverallcomplete === true) {\n statename = 'complete';\n }\n data[`is${statename}`] = true;\n }\n return data;\n }\n\n /**\n * Return a sorted list of all sections and cms items in the state.\n *\n * @param {Object} state the current state.\n * @returns {Array} all sections and cms items in the state.\n */\n allItemsArray(state) {\n const items = [];\n const sectionlist = state.course.sectionlist ?? [];\n // Add sections.\n sectionlist.forEach(sectionid => {\n const sectioninfo = state.section.get(sectionid);\n items.push({type: 'section', id: sectioninfo.id, url: sectioninfo.sectionurl});\n // Add cms.\n const cmlist = sectioninfo.cmlist ?? [];\n cmlist.forEach(cmid => {\n const cminfo = state.cm.get(cmid);\n items.push({type: 'cm', id: cminfo.id, url: cminfo.url});\n });\n });\n return items;\n }\n\n /**\n * Check is some activities of a list can be stealth.\n *\n * @param {Object} state the current state.\n * @param {Number[]} cmIds the module ids to check\n * @returns {Boolean} if any of the activities can be stealth.\n */\n canUseStealth(state, cmIds) {\n return cmIds.some(cmId => {\n const cminfo = state.cm.get(cmId);\n return cminfo?.allowstealth ?? false;\n });\n }\n}\n"],"names":["constructor","reactive","COMPLETIONS","course","state","data","sections","editmode","this","isEditing","highlighted","listedSectionIds","forEach","sectionid","sectioninfo","section","get","push","hassections","length","sectionlist","filter","component","cms","cmlist","cmid","cminfo","cm","hascms","isactive","cmDraggableData","nextcmid","currentindex","indexOf","id","undefined","type","name","delegatesection","sectionDraggableData","number","fileDraggableData","dataTransfer","files","file","cmCompletion","statename","completionstate","hasstate","isoverallcomplete","allItemsArray","items","url","sectionurl","canUseStealth","cmIds","some","cmId","allowstealth"],"mappings":"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"} \ No newline at end of file diff --git a/course/format/amd/src/local/courseeditor/exporter.js b/course/format/amd/src/local/courseeditor/exporter.js index 474ac7e58e8..3b8230b19de 100644 --- a/course/format/amd/src/local/courseeditor/exporter.js +++ b/course/format/amd/src/local/courseeditor/exporter.js @@ -210,7 +210,10 @@ export default class { if (cminfo.completionstate !== undefined) { data.state = cminfo.completionstate; data.hasstate = true; - const statename = this.COMPLETIONS[cminfo.completionstate] ?? 'NaN'; + let statename = this.COMPLETIONS[cminfo.completionstate] ?? 'NaN'; + if (cminfo.isoverallcomplete !== undefined && cminfo.isoverallcomplete === true) { + statename = 'complete'; + } data[`is${statename}`] = true; } return data; diff --git a/course/format/classes/output/local/state/cm.php b/course/format/classes/output/local/state/cm.php index 84853d4f2ad..65e72d9da08 100644 --- a/course/format/classes/output/local/state/cm.php +++ b/course/format/classes/output/local/state/cm.php @@ -106,8 +106,9 @@ class cm implements renderable { $completioninfo = new completion_info($course); $data->istrackeduser = $this->istrackeduser ?? $completioninfo->is_tracked_user($USER->id); if ($data->istrackeduser && $completioninfo->is_enabled($cm)) { - $completiondata = $completioninfo->get_data($cm); - $data->completionstate = $completiondata->completionstate; + $completiondata = new \core_completion\cm_completion_details($completioninfo, $cm, $USER->id, false); + $data->completionstate = $completiondata->get_overall_completion(); + $data->isoverallcomplete = $completiondata->is_overall_complete(); } $data->allowstealth = !empty($CFG->allowstealth) && $format->allow_stealth_module_visibility($cm, $section); diff --git a/course/format/tests/behat/courseindex_completion.feature b/course/format/tests/behat/courseindex_completion.feature index 72ed6fd0d23..fdaa206d84c 100644 --- a/course/format/tests/behat/courseindex_completion.feature +++ b/course/format/tests/behat/courseindex_completion.feature @@ -88,8 +88,8 @@ Feature: Course index completion icons | questioncategory | qtype | name | questiontext | | Test questions | truefalse | First question | Answer the first question | And the following "activities" exist: - | activity | name | course | idnumber | attempts | gradepass | completion | completionusegrade | completionpass | completionattemptsexhausted | - | quiz | Test quiz name | C1 | quiz1 | 1 | 5.00 | 2 | 1 | 1 | 1 | + | activity | name | course | idnumber | attempts | gradepass | completion | completionusegrade | completionpassgrade | completionattemptsexhausted | + | quiz | Test quiz name | C1 | quiz1 | 1 | 5.00 | 2 | 1 | 1 | 1 | And quiz "Test quiz name" contains the following questions: | question | page | | First question | 1 |