diff --git a/.github/workflows/moodle-ci.yml b/.github/workflows/moodle-ci.yml index 2789d36..b9a5294 100644 --- a/.github/workflows/moodle-ci.yml +++ b/.github/workflows/moodle-ci.yml @@ -85,6 +85,36 @@ jobs: - php: '8.1' moodle-branch: 'MOODLE_403_STABLE' database: mariadb + - php: '8.1' + moodle-branch: 'MOODLE_404_STABLE' + database: pgsql + - php: '8.1' + moodle-branch: 'MOODLE_404_STABLE' + database: mariadb + - php: '8.2' + moodle-branch: 'MOODLE_402_STABLE' + database: pgsql + - php: '8.2' + moodle-branch: 'MOODLE_402_STABLE' + database: mariadb + - php: '8.2' + moodle-branch: 'MOODLE_403_STABLE' + database: pgsql + - php: '8.2' + moodle-branch: 'MOODLE_403_STABLE' + database: mariadb + - php: '8.2' + moodle-branch: 'MOODLE_404_STABLE' + database: pgsql + - php: '8.2' + moodle-branch: 'MOODLE_404_STABLE' + database: mariadb + - php: '8.3' + moodle-branch: 'MOODLE_404_STABLE' + database: pgsql + - php: '8.3' + moodle-branch: 'MOODLE_404_STABLE' + database: mariadb steps: - name: Check out repository code diff --git a/.stylelintrc b/.stylelintrc new file mode 100644 index 0000000..6410a5a --- /dev/null +++ b/.stylelintrc @@ -0,0 +1,5 @@ +{ + "rules": { + "selector-type-no-unknown": [ true, { ignoreTypes: ["/^ion-/"] } ] + } +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index bf2702b..2a8853f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 1.0.9 - 2024-05-03 +### Changed +- Change "Save" for "Update" when the user has already voted. +- Make the cards stand out more from the background. +- Change behaviour so once the student has voted, they are taken automatically to the results screen. +### Added +- CI tests for Moodle 4.4. + ## 1.0.8 - 2024-01-30 ### Changed - Instructions removed when the user can't vote. diff --git a/amd/build/sortvoting.min.js b/amd/build/sortvoting.min.js index f45ccd8..2f30dd6 100644 --- a/amd/build/sortvoting.min.js +++ b/amd/build/sortvoting.min.js @@ -5,6 +5,6 @@ define("mod_sortvoting/sortvoting",["exports","core/notification","core/ajax","c * @module mod_sortvoting/sortvoting * @copyright 2023 Odei Alba * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=function(){new _sortable_list.default("#sortvotinglist",{moveHandlerSelector:".optionitem"}),(0,_jquery.default)("ul#sortvotinglist > *").on(_sortable_list.default.EVENTS.DROP,(function(evt,info){if(info.positionChanged)for(var lis=info.sourceList[0].getElementsByTagName("li"),i=0;i{const saveSortVoteElement=event.target.closest(SELECTORS_SAVEVOTE);saveSortVoteElement&&(event.preventDefault(),function(saveSortVoteElement){saveSortVoteElement.setAttribute("disabled",!0);var sortvotingid=document.getElementsByName("sortvotingid")[0].value,options=document.getElementsByName("option[]"),votes=[],positions=[];if(options.forEach((function(option){positions.push(option.value),votes.push({position:option.value,optionid:option.getAttribute("data-optionid")})})),new Set(positions).size!==positions.length)return(0,_toast.add)((0,_str.get_string)("errorduplicatedposition","mod_sortvoting"),{type:"danger"}),void saveSortVoteElement.removeAttribute("disabled");_ajax.default.call([{methodname:"mod_sortvoting_save_vote",args:{sortvotingid:sortvotingid,votes:votes}}])[0].done((function(result){result.success?(0,_toast.add)((0,_str.get_string)("votesuccess","mod_sortvoting"),{type:"success"}):(0,_toast.add)((0,_str.get_string)("voteerror","mod_sortvoting"),{type:"danger"}),result.seeresultsupdated?window.location.reload():result.allowupdate?saveSortVoteElement.removeAttribute("disabled"):(saveSortVoteElement.style.display="none",(0,_jquery.default)("ul#sortvotinglist > li.optionitem").each((function(index,element){element.classList.remove("optionitem","draggable"),element.classList.add("bg-gray-100"),element.removeAttribute("draggable"),element.removeAttribute("data-drag-type")})))})).fail(_notification.default.exception)}(saveSortVoteElement))}))},_notification=_interopRequireDefault(_notification),_ajax=_interopRequireDefault(_ajax),_sortable_list=_interopRequireDefault(_sortable_list),_jquery=_interopRequireDefault(_jquery);const SELECTORS_SAVEVOTE="[data-action='savevote']"})); + */Object.defineProperty(_exports,"__esModule",{value:!0}),_exports.init=function(){new _sortable_list.default("#sortvotinglist",{moveHandlerSelector:".optionitem"}),(0,_jquery.default)("ul#sortvotinglist > *").on(_sortable_list.default.EVENTS.DROP,(function(evt,info){if(info.positionChanged)for(var lis=info.sourceList[0].getElementsByTagName("li"),i=0;i{const saveSortVoteElement=event.target.closest(SELECTORS_SAVEVOTE);saveSortVoteElement&&(event.preventDefault(),function(saveSortVoteElement){saveSortVoteElement.setAttribute("disabled",!0);var sortvotingid=document.getElementsByName("sortvotingid")[0].value,options=document.getElementsByName("option[]"),votes=[],positions=[];if(options.forEach((function(option){positions.push(option.value),votes.push({position:option.value,optionid:option.getAttribute("data-optionid")})})),new Set(positions).size!==positions.length)return(0,_toast.add)((0,_str.get_string)("errorduplicatedposition","mod_sortvoting"),{type:"danger"}),void saveSortVoteElement.removeAttribute("disabled");_ajax.default.call([{methodname:"mod_sortvoting_save_vote",args:{sortvotingid:sortvotingid,votes:votes}}])[0].done((function(result){result.success?(0,_toast.add)((0,_str.get_string)("votesuccess","mod_sortvoting"),{type:"success"}):(0,_toast.add)((0,_str.get_string)("voteerror","mod_sortvoting"),{type:"danger"}),result.canseeresults?window.location.href=result.redirecturl:result.allowupdate?saveSortVoteElement.removeAttribute("disabled"):(saveSortVoteElement.style.display="none",(0,_jquery.default)("ul#sortvotinglist > li.optionitem").each((function(index,element){element.classList.remove("optionitem","draggable"),element.classList.add("bg-gray-100"),element.removeAttribute("draggable"),element.removeAttribute("data-drag-type")})))})).fail(_notification.default.exception)}(saveSortVoteElement))}))},_notification=_interopRequireDefault(_notification),_ajax=_interopRequireDefault(_ajax),_sortable_list=_interopRequireDefault(_sortable_list),_jquery=_interopRequireDefault(_jquery);const SELECTORS_SAVEVOTE="[data-action='savevote']"})); //# sourceMappingURL=sortvoting.min.js.map \ No newline at end of file diff --git a/amd/build/sortvoting.min.js.map b/amd/build/sortvoting.min.js.map index 549464a..125e196 100644 --- a/amd/build/sortvoting.min.js.map +++ b/amd/build/sortvoting.min.js.map @@ -1 +1 @@ -{"version":3,"file":"sortvoting.min.js","sources":["../src/sortvoting.js"],"sourcesContent":["// This file is part of the mod_sortvoting plugin for 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 * AMD module used when saving a new sort voting.\n *\n * @module mod_sortvoting/sortvoting\n * @copyright 2023 Odei Alba \n * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Notification from 'core/notification';\nimport Ajax from 'core/ajax';\nimport SortableList from 'core/sortable_list';\nimport {add as toastAdd} from 'core/toast';\nimport jQuery from 'jquery';\nimport {get_string as getString} from 'core/str';\n\nconst SELECTORS = {\n SAVEVOTE: \"[data-action='savevote']\"\n};\n\n/**\n * Save sort vote.\n * @param {Element} saveSortVoteElement\n */\nconst saveVote = function(saveSortVoteElement) {\n saveSortVoteElement.setAttribute('disabled', true);\n var sortvotingid = document.getElementsByName('sortvotingid')[0].value;\n var options = document.getElementsByName('option[]');\n\n // Build votes and positions arrays for later processing.\n var votes = [];\n var positions = [];\n options.forEach(function(option) {\n positions.push(option.value);\n votes.push({\n 'position': option.value,\n 'optionid': option.getAttribute('data-optionid')\n });\n });\n\n // Check if all elements of the positions array are unique.\n if (new Set(positions).size !== positions.length) {\n toastAdd(getString('errorduplicatedposition', 'mod_sortvoting'), {type: 'danger'});\n saveSortVoteElement.removeAttribute('disabled');\n return;\n }\n\n // Save vote.\n var promises = Ajax.call([\n {methodname: 'mod_sortvoting_save_vote', args: {sortvotingid: sortvotingid, votes: votes}}\n ]);\n promises[0].done(function(result) {\n if (result.success) {\n toastAdd(getString('votesuccess', 'mod_sortvoting'), {type: 'success'});\n } else {\n toastAdd(getString('voteerror', 'mod_sortvoting'), {type: 'danger'});\n }\n if (result.seeresultsupdated) {\n // Reload the whole page so the results tab is visible now but wasn't before (or viceversa).\n window.location.reload();\n } else if (result.allowupdate) {\n saveSortVoteElement.removeAttribute('disabled');\n } else {\n saveSortVoteElement.style.display = 'none';\n jQuery('ul#sortvotinglist > li.optionitem').each(function(index, element) {\n element.classList.remove('optionitem', 'draggable');\n element.classList.add('bg-gray-100');\n element.removeAttribute('draggable');\n element.removeAttribute('data-drag-type');\n });\n }\n }).fail(Notification.exception);\n};\n\n/**\n * Sets up sortable list in the column sort order page.\n */\nconst setupSortableLists = () => {\n new SortableList('#sortvotinglist', {\n moveHandlerSelector: '.optionitem',\n });\n // Listen to the events when element is dragged.\n jQuery('ul#sortvotinglist > *').on(SortableList.EVENTS.DROP, function(evt, info) {\n if (info.positionChanged) {\n // Get the ul element and loop into the li elements.\n var list = info.sourceList[0];\n var lis = list.getElementsByTagName('li');\n for (var i = 0; i < lis.length; i++) {\n // Set the value of the input to the position of the li element.\n lis[i].getElementsByTagName('input')[0].value = i + 1;\n }\n }\n });\n};\n\n/**\n * Init page\n */\nexport function init() {\n setupSortableLists();\n document.addEventListener('click', event => {\n\n // Save sort vote.\n const saveSortVoteElement = event.target.closest(SELECTORS.SAVEVOTE);\n if (saveSortVoteElement) {\n event.preventDefault();\n saveVote(saveSortVoteElement);\n }\n });\n}\n"],"names":["SortableList","moveHandlerSelector","on","EVENTS","DROP","evt","info","positionChanged","lis","sourceList","getElementsByTagName","i","length","value","document","addEventListener","event","saveSortVoteElement","target","closest","SELECTORS","preventDefault","setAttribute","sortvotingid","getElementsByName","options","votes","positions","forEach","option","push","getAttribute","Set","size","type","removeAttribute","Ajax","call","methodname","args","done","result","success","seeresultsupdated","window","location","reload","allowupdate","style","display","each","index","element","classList","remove","add","fail","Notification","exception","saveVote"],"mappings":";;;;;;;0FA4FQA,uBAAa,kBAAmB,CAChCC,oBAAqB,oCAGlB,yBAAyBC,GAAGF,uBAAaG,OAAOC,MAAM,SAASC,IAAKC,SACnEA,KAAKC,wBAGDC,IADOF,KAAKG,WAAW,GACZC,qBAAqB,MAC3BC,EAAI,EAAGA,EAAIH,IAAII,OAAQD,IAE5BH,IAAIG,GAAGD,qBAAqB,SAAS,GAAGG,MAAQF,EAAI,KAWhEG,SAASC,iBAAiB,SAASC,cAGzBC,oBAAsBD,MAAME,OAAOC,QAAQC,oBAC7CH,sBACAD,MAAMK,iBAjFD,SAASJ,qBACtBA,oBAAoBK,aAAa,YAAY,OACzCC,aAAeT,SAASU,kBAAkB,gBAAgB,GAAGX,MAC7DY,QAAUX,SAASU,kBAAkB,YAGrCE,MAAQ,GACRC,UAAY,MAChBF,QAAQG,SAAQ,SAASC,QACrBF,UAAUG,KAAKD,OAAOhB,OACtBa,MAAMI,KAAK,UACKD,OAAOhB,eACPgB,OAAOE,aAAa,sBAKpC,IAAIC,IAAIL,WAAWM,OAASN,UAAUf,6BAC7B,mBAAU,0BAA2B,kBAAmB,CAACsB,KAAM,gBACxEjB,oBAAoBkB,gBAAgB,YAKzBC,cAAKC,KAAK,CACrB,CAACC,WAAY,2BAA4BC,KAAM,CAAChB,aAAcA,aAAcG,MAAOA,UAE9E,GAAGc,MAAK,SAASC,QAClBA,OAAOC,wBACE,mBAAU,cAAe,kBAAmB,CAACR,KAAM,4BAEnD,mBAAU,YAAa,kBAAmB,CAACA,KAAM,WAE1DO,OAAOE,kBAEPC,OAAOC,SAASC,SACTL,OAAOM,YACd9B,oBAAoBkB,gBAAgB,aAEpClB,oBAAoB+B,MAAMC,QAAU,2BAC7B,qCAAqCC,MAAK,SAASC,MAAOC,SAC7DA,QAAQC,UAAUC,OAAO,aAAc,aACvCF,QAAQC,UAAUE,IAAI,eACtBH,QAAQjB,gBAAgB,aACxBiB,QAAQjB,gBAAgB,yBAGjCqB,KAAKC,sBAAaC,WAmCbC,CAAS1C,sNA1FfG,mBACQ"} \ No newline at end of file +{"version":3,"file":"sortvoting.min.js","sources":["../src/sortvoting.js"],"sourcesContent":["// This file is part of the mod_sortvoting plugin for 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 * AMD module used when saving a new sort voting.\n *\n * @module mod_sortvoting/sortvoting\n * @copyright 2023 Odei Alba \n * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n */\n\nimport Notification from 'core/notification';\nimport Ajax from 'core/ajax';\nimport SortableList from 'core/sortable_list';\nimport {add as toastAdd} from 'core/toast';\nimport jQuery from 'jquery';\nimport {get_string as getString} from 'core/str';\n\nconst SELECTORS = {\n SAVEVOTE: \"[data-action='savevote']\"\n};\n\n/**\n * Save sort vote.\n * @param {Element} saveSortVoteElement\n */\nconst saveVote = function(saveSortVoteElement) {\n saveSortVoteElement.setAttribute('disabled', true);\n var sortvotingid = document.getElementsByName('sortvotingid')[0].value;\n var options = document.getElementsByName('option[]');\n\n // Build votes and positions arrays for later processing.\n var votes = [];\n var positions = [];\n options.forEach(function(option) {\n positions.push(option.value);\n votes.push({\n 'position': option.value,\n 'optionid': option.getAttribute('data-optionid')\n });\n });\n\n // Check if all elements of the positions array are unique.\n if (new Set(positions).size !== positions.length) {\n toastAdd(getString('errorduplicatedposition', 'mod_sortvoting'), {type: 'danger'});\n saveSortVoteElement.removeAttribute('disabled');\n return;\n }\n\n // Save vote.\n var promises = Ajax.call([\n {methodname: 'mod_sortvoting_save_vote', args: {sortvotingid: sortvotingid, votes: votes}}\n ]);\n promises[0].done(function(result) {\n if (result.success) {\n toastAdd(getString('votesuccess', 'mod_sortvoting'), {type: 'success'});\n } else {\n toastAdd(getString('voteerror', 'mod_sortvoting'), {type: 'danger'});\n }\n if (result.canseeresults) {\n // If the user can see the results, redirect to the results page. Otherwise, reload the page.\n window.location.href = result.redirecturl;\n } else if (result.allowupdate) {\n saveSortVoteElement.removeAttribute('disabled');\n } else {\n saveSortVoteElement.style.display = 'none';\n jQuery('ul#sortvotinglist > li.optionitem').each(function(index, element) {\n element.classList.remove('optionitem', 'draggable');\n element.classList.add('bg-gray-100');\n element.removeAttribute('draggable');\n element.removeAttribute('data-drag-type');\n });\n }\n }).fail(Notification.exception);\n};\n\n/**\n * Sets up sortable list in the column sort order page.\n */\nconst setupSortableLists = () => {\n new SortableList('#sortvotinglist', {\n moveHandlerSelector: '.optionitem',\n });\n // Listen to the events when element is dragged.\n jQuery('ul#sortvotinglist > *').on(SortableList.EVENTS.DROP, function(evt, info) {\n if (info.positionChanged) {\n // Get the ul element and loop into the li elements.\n var list = info.sourceList[0];\n var lis = list.getElementsByTagName('li');\n for (var i = 0; i < lis.length; i++) {\n // Set the value of the input to the position of the li element.\n lis[i].getElementsByTagName('input')[0].value = i + 1;\n }\n }\n });\n};\n\n/**\n * Init page\n */\nexport function init() {\n setupSortableLists();\n document.addEventListener('click', event => {\n\n // Save sort vote.\n const saveSortVoteElement = event.target.closest(SELECTORS.SAVEVOTE);\n if (saveSortVoteElement) {\n event.preventDefault();\n saveVote(saveSortVoteElement);\n }\n });\n}\n"],"names":["SortableList","moveHandlerSelector","on","EVENTS","DROP","evt","info","positionChanged","lis","sourceList","getElementsByTagName","i","length","value","document","addEventListener","event","saveSortVoteElement","target","closest","SELECTORS","preventDefault","setAttribute","sortvotingid","getElementsByName","options","votes","positions","forEach","option","push","getAttribute","Set","size","type","removeAttribute","Ajax","call","methodname","args","done","result","success","canseeresults","window","location","href","redirecturl","allowupdate","style","display","each","index","element","classList","remove","add","fail","Notification","exception","saveVote"],"mappings":";;;;;;;0FA4FQA,uBAAa,kBAAmB,CAChCC,oBAAqB,oCAGlB,yBAAyBC,GAAGF,uBAAaG,OAAOC,MAAM,SAASC,IAAKC,SACnEA,KAAKC,wBAGDC,IADOF,KAAKG,WAAW,GACZC,qBAAqB,MAC3BC,EAAI,EAAGA,EAAIH,IAAII,OAAQD,IAE5BH,IAAIG,GAAGD,qBAAqB,SAAS,GAAGG,MAAQF,EAAI,KAWhEG,SAASC,iBAAiB,SAASC,cAGzBC,oBAAsBD,MAAME,OAAOC,QAAQC,oBAC7CH,sBACAD,MAAMK,iBAjFD,SAASJ,qBACtBA,oBAAoBK,aAAa,YAAY,OACzCC,aAAeT,SAASU,kBAAkB,gBAAgB,GAAGX,MAC7DY,QAAUX,SAASU,kBAAkB,YAGrCE,MAAQ,GACRC,UAAY,MAChBF,QAAQG,SAAQ,SAASC,QACrBF,UAAUG,KAAKD,OAAOhB,OACtBa,MAAMI,KAAK,UACKD,OAAOhB,eACPgB,OAAOE,aAAa,sBAKpC,IAAIC,IAAIL,WAAWM,OAASN,UAAUf,6BAC7B,mBAAU,0BAA2B,kBAAmB,CAACsB,KAAM,gBACxEjB,oBAAoBkB,gBAAgB,YAKzBC,cAAKC,KAAK,CACrB,CAACC,WAAY,2BAA4BC,KAAM,CAAChB,aAAcA,aAAcG,MAAOA,UAE9E,GAAGc,MAAK,SAASC,QAClBA,OAAOC,wBACE,mBAAU,cAAe,kBAAmB,CAACR,KAAM,4BAEnD,mBAAU,YAAa,kBAAmB,CAACA,KAAM,WAE1DO,OAAOE,cAEPC,OAAOC,SAASC,KAAOL,OAAOM,YACvBN,OAAOO,YACd/B,oBAAoBkB,gBAAgB,aAEpClB,oBAAoBgC,MAAMC,QAAU,2BAC7B,qCAAqCC,MAAK,SAASC,MAAOC,SAC7DA,QAAQC,UAAUC,OAAO,aAAc,aACvCF,QAAQC,UAAUE,IAAI,eACtBH,QAAQlB,gBAAgB,aACxBkB,QAAQlB,gBAAgB,yBAGjCsB,KAAKC,sBAAaC,WAmCbC,CAAS3C,sNA1FfG,mBACQ"} \ No newline at end of file diff --git a/amd/src/sortvoting.js b/amd/src/sortvoting.js index 046b860..1ee9180 100644 --- a/amd/src/sortvoting.js +++ b/amd/src/sortvoting.js @@ -69,9 +69,9 @@ const saveVote = function(saveSortVoteElement) { } else { toastAdd(getString('voteerror', 'mod_sortvoting'), {type: 'danger'}); } - if (result.seeresultsupdated) { - // Reload the whole page so the results tab is visible now but wasn't before (or viceversa). - window.location.reload(); + if (result.canseeresults) { + // If the user can see the results, redirect to the results page. Otherwise, reload the page. + window.location.href = result.redirecturl; } else if (result.allowupdate) { saveSortVoteElement.removeAttribute('disabled'); } else { diff --git a/classes/external/save_vote.php b/classes/external/save_vote.php index 9aeb1a8..5bcc547 100644 --- a/classes/external/save_vote.php +++ b/classes/external/save_vote.php @@ -23,6 +23,7 @@ use external_single_structure; use external_value; use moodle_exception; +use moodle_url; defined('MOODLE_INTERNAL') || die; require_once($CFG->dirroot . '/mod/sortvoting/lib.php'); @@ -79,16 +80,17 @@ public static function execute($sortvotingid, $votes) { $context = \context_module::instance($cm->id); self::validate_context($context); \mod_sortvoting\permission::require_can_vote($context); - $canseeresultsold = \mod_sortvoting\permission::can_see_results($sortvoting, $context); sortvoting_user_submit_response($sortvoting, $params['votes'], $course, $cm); - $canseeresultsnew = \mod_sortvoting\permission::can_see_results($sortvoting, $context); + $canseeresults = \mod_sortvoting\permission::can_see_results($sortvoting, $context); + $redirecturl = $canseeresults ? '/mod/sortvoting/report.php' : '/mod/sortvoting/view.php'; return [ 'success' => true, 'allowupdate' => (bool) $sortvoting->allowupdate, - 'seeresultsupdated' => (bool) $canseeresultsold !== $canseeresultsnew, + 'canseeresults' => (bool) $canseeresults, + 'redirecturl' => (new moodle_url($redirecturl, ['id' => $cm->id]))->out(), ]; } @@ -102,9 +104,14 @@ public static function execute_returns() { [ 'success' => new external_value(PARAM_BOOL, 'Returns true on successful vote submision or throws an error'), 'allowupdate' => new external_value(PARAM_BOOL, 'Returns true if vote can be updated', VALUE_REQUIRED), - 'seeresultsupdated' => new external_value( + 'canseeresults' => new external_value( PARAM_BOOL, - 'Returns true if the user could not see the results and can see them now (or viceversa)', + 'Returns true if the user can see the results of the vote', + VALUE_REQUIRED + ), + 'redirecturl' => new external_value( + PARAM_URL, + 'Redirect URL to the results or the vote page', VALUE_REQUIRED ), ] diff --git a/classes/output/sort_voting_form.php b/classes/output/sort_voting_form.php index 55aaf0d..2aa81a8 100644 --- a/classes/output/sort_voting_form.php +++ b/classes/output/sort_voting_form.php @@ -83,6 +83,7 @@ public function export_for_template(renderer_base $output): array { return [ 'sortvotingid' => $this->sortvoting->id, 'allowupdate' => $allowupdate, + 'alreadyvoted' => count($existingvotes) > 0, 'options' => $optionsclean, 'max' => count($options), ]; diff --git a/lang/en/sortvoting.php b/lang/en/sortvoting.php index 57ccf51..dcb4bad 100644 --- a/lang/en/sortvoting.php +++ b/lang/en/sortvoting.php @@ -46,15 +46,15 @@ $string['option'] = 'Option'; $string['optionno'] = 'Option {no}'; $string['options'] = 'Options'; -$string['position'] = 'Position'; $string['pluginadministration'] = 'Preference Sort Voting administration'; $string['pluginname'] = 'Preference Sort Voting'; +$string['position'] = 'Position'; $string['privacy'] = 'Privacy of results'; $string['privacy:metadata:sortvoting_answers'] = 'Information about the user\'s votes for a given Preference Sort Voting activity'; -$string['privacy:metadata:sortvoting_answers:sortvotingid'] = 'The ID of the Preference Sort Voting activity'; $string['privacy:metadata:sortvoting_answers:optionid'] = 'The ID of the option that the user sorted.'; -$string['privacy:metadata:sortvoting_answers:userid'] = 'The ID of the user answering this Preference Sort Voting activity'; +$string['privacy:metadata:sortvoting_answers:sortvotingid'] = 'The ID of the Preference Sort Voting activity'; $string['privacy:metadata:sortvoting_answers:timemodified'] = 'The timestamp indicating when the vote was modified by the user'; +$string['privacy:metadata:sortvoting_answers:userid'] = 'The ID of the user answering this Preference Sort Voting activity'; $string['responses'] = 'Responses'; $string['sortoptions'] = 'Preference options'; $string['sortoptions_help'] = 'Here is where you specify the options that participants have to sort with their preferences. diff --git a/mobile/js/init.js b/mobile/js/init.js index d1deeab..75fe6f0 100644 --- a/mobile/js/init.js +++ b/mobile/js/init.js @@ -43,7 +43,7 @@ class AddonModSortVotingProvider { return true; }).catch((error) => { // The WebService has thrown an error, this means that responses cannot be submitted. - return Promise.reject(error); + throw error; }); } @@ -69,7 +69,7 @@ class AddonModSortVotingProvider { var error = response && response.warnings && response.warnings[0] ? response.warnings[0] : new context.CoreError(''); - return Promise.reject(error); + throw error; } }); }); diff --git a/mobile/js/mobile_sortvoting.js b/mobile/js/mobile_sortvoting.js index 91ef00c..0ded6ff 100644 --- a/mobile/js/mobile_sortvoting.js +++ b/mobile/js/mobile_sortvoting.js @@ -85,7 +85,7 @@ this.moveUp = (id) => { var prevOption = document.querySelector("#item-" + prevId); sortVotingList.insertBefore(option, prevOption); -} +}; this.moveDown = (id) => { var options = document.getElementsByName('option[]'); @@ -114,4 +114,4 @@ this.moveDown = (id) => { var nextOption = document.querySelector("#item-" + nextId); sortVotingList.insertBefore(nextOption, option); -} +}; diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..ae1c4d1 --- /dev/null +++ b/styles.css @@ -0,0 +1,6 @@ +/*Adds a border around the cards in the preference sorting activity*/ +li.d-flex.card.p-3.mb-2.optionitem.draggable { + border-style: solid; + border-color: #c0c0c0; + border-width: thin; +} \ No newline at end of file diff --git a/templates/sort_voting_form.mustache b/templates/sort_voting_form.mustache index a9c2e9b..d69b6ed 100644 --- a/templates/sort_voting_form.mustache +++ b/templates/sort_voting_form.mustache @@ -62,7 +62,7 @@ {{! This list is drag and drop sortable. }}
    {{#options}} -
  • +
  • {{#pix}}i/dragdrop{{/pix}} @@ -73,7 +73,10 @@
{{#allowupdate}} - + {{#js}} require(['mod_sortvoting/sortvoting'], function(sortvoting) { sortvoting.init(); diff --git a/tests/lib_test.php b/tests/lib_test.php index 7000375..5ed36bd 100644 --- a/tests/lib_test.php +++ b/tests/lib_test.php @@ -31,14 +31,14 @@ * @copyright 2023 Odei Alba * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class lib_test extends \externallib_advanced_testcase { +final class lib_test extends \externallib_advanced_testcase { /** * Tests events after sortvoting is viewed. * * @covers ::sortvoting_view * @return void */ - public function test_sortvoting_view() { + public function test_sortvoting_view(): void { global $CFG; $this->resetAfterTest(); @@ -76,7 +76,7 @@ public function test_sortvoting_view() { * @covers ::mod_sortvoting_get_completion_active_rule_descriptions * @return void */ - public function test_mod_sortvoting_completion_get_active_rule_descriptions() { + public function test_mod_sortvoting_completion_get_active_rule_descriptions(): void { $this->resetAfterTest(); $this->setAdminUser(); diff --git a/tests/privacy/provider_test.php b/tests/privacy/provider_test.php index 6e6b178..0f6bcaf 100644 --- a/tests/privacy/provider_test.php +++ b/tests/privacy/provider_test.php @@ -27,7 +27,7 @@ * @copyright 2023 Odei Alba * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -class provider_test extends \core_privacy\tests\provider_testcase { +final class provider_test extends \core_privacy\tests\provider_testcase { /** @var stdClass The student object. */ protected $student; @@ -92,7 +92,7 @@ protected function setUp(): void { * Test for provider::get_metadata(). * @covers ::get_metadata */ - public function test_get_metadata() { + public function test_get_metadata(): void { $collection = new collection('mod_sortvoting'); $newcollection = provider::get_metadata($collection); $itemcollection = $newcollection->get_collection(); @@ -114,7 +114,7 @@ public function test_get_metadata() { * Test for provider::get_contexts_for_userid(). * @covers ::get_contexts_for_userid */ - public function test_get_contexts_for_userid() { + public function test_get_contexts_for_userid(): void { $cm = get_coursemodule_from_instance('sortvoting', $this->sortvoting->id); $contextlist = provider::get_contexts_for_userid($this->student->id); @@ -128,7 +128,7 @@ public function test_get_contexts_for_userid() { * Test for provider::export_user_data(). * @covers ::export_user_data */ - public function test_export_for_context() { + public function test_export_for_context(): void { $cm = get_coursemodule_from_instance('sortvoting', $this->sortvoting->id); $cmcontext = \context_module::instance($cm->id); @@ -142,7 +142,7 @@ public function test_export_for_context() { * Test for provider::delete_data_for_all_users_in_context(). * @covers ::delete_data_for_all_users_in_context */ - public function test_delete_data_for_all_users_in_context() { + public function test_delete_data_for_all_users_in_context(): void { global $DB; $sortvoting = $this->sortvoting; @@ -190,7 +190,7 @@ public function test_delete_data_for_all_users_in_context() { * Test for provider::delete_data_for_user(). * @covers ::delete_data_for_user */ - public function test_delete_data_for_user_() { + public function test_delete_data_for_user(): void { global $DB; $sortvoting = $this->sortvoting; @@ -283,7 +283,7 @@ public function test_delete_data_for_user_() { * Test for provider::get_users_in_context(). * @covers ::get_users_in_context */ - public function test_get_users_in_context() { + public function test_get_users_in_context(): void { $cm = get_coursemodule_from_instance('sortvoting', $this->sortvoting->id); $cmcontext = \context_module::instance($cm->id); @@ -300,7 +300,7 @@ public function test_get_users_in_context() { * Test for provider::get_users_in_context() with invalid context type. * @covers ::get_users_in_context */ - public function test_get_users_in_context_invalid_context_type() { + public function test_get_users_in_context_invalid_context_type(): void { $systemcontext = \context_system::instance(); $userlist = new \core_privacy\local\request\userlist($systemcontext, 'mod_sortvoting'); @@ -313,7 +313,7 @@ public function test_get_users_in_context_invalid_context_type() { * Test for provider::delete_data_for_users(). * @covers ::delete_data_for_users */ - public function test_delete_data_for_users() { + public function test_delete_data_for_users(): void { global $DB; $sortvoting = $this->sortvoting; diff --git a/version.php b/version.php index 65b4590..c4f2b58 100644 --- a/version.php +++ b/version.php @@ -25,8 +25,8 @@ defined('MOODLE_INTERNAL') || die(); $plugin->component = 'mod_sortvoting'; -$plugin->release = '1.0.8'; -$plugin->version = 2024013000; +$plugin->release = '1.0.9'; +$plugin->version = 2024050300; $plugin->requires = 2022041908; $plugin->maturity = MATURITY_STABLE; $plugin->dependencies = [];